One additional duplicate file gets uploaded - spring

I have my FileUpload Controller like this:
#PostMapping("/uploadFile")
public AppUserDocumentUploadResponse uploadFile(#RequestParam("file") MultipartFile file) {
AppUserDocument dbFile = appUserDocumentStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(dbFile.getId().toString())
.toUriString();
return new AppUserDocumentUploadResponse(dbFile.getDbfileName(), fileDownloadUri,
file.getContentType(), file.getSize());
}
#PostMapping("/uploadMultipleFiles")
public List<AppUserDocumentUploadResponse> uploadMultipleFiles(#RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
//.peek(fileBeingProcessed -> log.info("Processing File (PEEK2): {} ", fileBeingProcessed))
.map(file -> uploadFile(file))
.collect(Collectors.toList());
}
My Service is:
public AppUserDocument storeFile(MultipartFile file) {
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
if (fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
AppUserDocument dbFile = new AppUserDocument(UUID.randomUUID().toString(), fileName, file.getContentType(),
file.getSize(), file.getBytes());
return dbFileRepository.save(dbFile);
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
Model goes like this:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "AppDBFiles")
#Table(name = "app_db_files")
public class AppUserDocument extends Auditable<String> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "dbfileuuid", unique = true)
#GenericGenerator(name = "uuid", strategy = "uuid4")
private String dbfileuuid;
#Column(name = "dbfilename")
private String dbfileName;
#Column(name = "dbfiletype")
private String dbfileType;
#Column(name = "dbfilesize")
private long dbfileSize;
#Lob
#Column(name = "dbfiledata")
private byte[] dbfileData;
#Version
#Column(name="optlock")
private Integer version;
public AppUserDocument(String dbfileName, String dbfileType, long dbfileSize, byte[] dbfileData) {
super();
this.dbfileName = dbfileName;
this.dbfileType = dbfileType;
this.dbfileSize = dbfileSize;
this.dbfileData = dbfileData;
}
public AppUserDocument(String dbfileuuid, String dbfileName, String dbfileType, long dbfileSize, byte[] dbfileData) {
super();
this.dbfileName = dbfileName;
this.dbfileType = dbfileType;
this.dbfileSize = dbfileSize;
this.dbfileData = dbfileData;
}
}
And my (vanilla) JS goes like this:
function uploadMultipleFiles(files) {
var formData = new FormData();
for (var index = 0; index < files.length; index++) {
formData.append("files", files[index]);
}
const fileField = document.querySelector('input[type="file"]');
//formData.append('filecusomName', 'abc123');
formData.append('files', fileField.files[0]);
fetch('/uploadMultipleFiles', {
method: 'POST',
body: formData
})
.then((response) => response.json())
.then((response) => {
console.log('Success:', response);
multipleFileUploadError.style.display = "none";
var content = "<p>All Files Uploaded Successfully</p>";
document.getElementById("multipleFileUploadInput").value = null;
for (var i = 0; i < response.length; i++) {
content += "<p>DownloadUrl : <a href='" + response[i].fileDownloadUri + "' target='_blank'>" + response[i].fileDownloadUri + "</a></p>";
}
multipleFileUploadSuccess.innerHTML = content;
multipleFileUploadSuccess.style.display = "block";
})
.catch((error) => {
multipleFileUploadSuccess.style.display = "none";
multipleFileUploadError.innerHTML = (response && response.message) || "Some Error Occurred";
console.error('Error:', error);
});
}
All goes well except one additional file gets uploaded which is duplicate of the first file.
This is what my console logs show:
Success:
(3) [{…}, {…}, {…}]
0
:
fileDownloadUri
:
"http://localhost:8080/downloadFile/39"
fileName
:
"sample.pdf"
fileType
:
"application/pdf"
size
:
357896
[[Prototype]]
:
Object
1
:
fileDownloadUri
:
"http://localhost:8080/downloadFile/40"
fileName
:
"sample2.pdf"
fileType
:
"application/pdf"
size
:
357896
[[Prototype]]
:
Object
2
:
fileDownloadUri
:
"http://localhost:8080/downloadFile/41"
fileName
:
"sample.pdf"
fileType
:
"application/pdf"
size
:
357896
[[Prototype]]
:
Object
length
:
3
And my DB looks like this:
Uncommenting the .peek(fileBein in my controller also shows this (twice for the duplicate file):
2022-12-07T00:23:25.971-05:00 INFO 11344 --- [nio-8080-exec-2] c.c.s.c.AppUserDocumentRestController : Processing File
What I am doing wrong here? Also not sure where to debug this situation. Any pointers/directions will be greatly appreciated.

Please once go through your JS file:
function uploadMultipleFiles(files) {
var formData = new FormData();
for (var index = 0; index < files.length; index++) {
formData.append("files", files[index]);
}
const fileField = document.querySelector('input[type="file"]');
//formData.append('filecusomName', 'abc123');
formData.append('files', fileField.files[0]);
fetch('/uploadMultipleFiles', {
method: 'POST',
body: formData
})
...
...
}
The 9th line formData.append('files', fileField.files[0]); is adding duplicate of First file in FormData.

Related

SpringBoot cannot parse MultipartFile properly

Hello and thank you for your time in advance.
I have really simple code below for downloading/uploading files:
This is frontend:
function upload() {
let data = new FormData();
data.append("file", refs.inputFile.current.files[0]);
let config = {
headers: {
"Content-Type" : "multipart/form-data"
}
};
console.log(data);
axios.post("http://localhost:8080/upload",
data)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err.message);
});
}
When i open request in Firefox i can see file content just fine, content in files is just fine on this side. But backend returns to me totally different thing.
So this is backend code:
Controller:
#PostMapping(path = "/upload")
public String upload(
#RequestParam("file") MultipartFile file) throws IOException {
if(repo.existsByFilename(file.getOriginalFilename())) {
return "File already exists.";
} else {
Model m = new Model(
file.getOriginalFilename(),
file.getBytes()
);
repo.save(m);
return "File named " + file.getOriginalFilename() + " is saved.";
}
Model entity:
#Entity
#Table(name = "test")
public class Model {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String filename;
#Lob
#Column(columnDefinition = "longblob")
private byte[] data;
public Model() {
super();
}
public Model(String filename, byte[] data) {
super();
this.filename = filename;
this.data = data;
}
public Model(Long id, String filename, byte[] data) {
super();
this.id = id;
this.filename = filename;
this.data = data;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
I when i try to upload a simple text file named testtext.txt with "TESTTEXT" i can open request in firefox and i get this:
-----------------------------3431304631829148335501350923
Content-Disposition: form-data; name="file"; filename="testtext.txt"
Content-Type: text/plain
TESTTEXT
-----------------------------3431304631829148335501350923--
BUT when i access MySQL DB directly i have this content:
0x54455354544558540A
When you convert it to ASCII it doesn't make sense. Its the same when i try this code with images also. It always returns me more than i send it.
For almost two days i cannot get simple file up/down working and i don't understand where am i making mistake. This is just minimal and straightforward code, it should work...
Download function:
function download(filename) {
console.log(filename);
axios({
url: "http://localhost:8080/api/dataupdown/download",
method: "GET",
params: {
filename: filename
}
}).then((res) => {
const url = window.URL.createObjectURL(new Blob([res.data.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", res.data.filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
Your lob is encoded in hexadecimal. if you convert it to string, you will find the result you are looking for 'TESTTEST' :
function hex2a(hexx) {
var hex = hexx.toString();//force conversion
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
console.log(hex2a('54455354544558540A')); // returns 'TESTTEST'
EDIT: response to second question added by edit about the download
You exposed the controller data encoded in base64. If you decoded, you will find back 'TESTTEXT'
var decoded = atob("VEVTVFRFWFQK");
console.log(decoded); //returns 'TESTTEXT'
Thank you very much Thomas for helping me. My problem is completely solved. I can now upload all files(images, videos, text, documents) and i can download them just exactly as they are originally.
So apparently after following tons of tutorials no one mentioned even once that data was encoded in Base64 scheme, and to be honest i didn't know anything about Base64 before... I used js-base64 lib because it has nice prebuilt stuff that should be(in my opinion) base in JS.
Summary:
Data you send to SQL is raw HEX/BINARY code.
When you GET/POST data from SQL, browser(or JS?) converts it to Base64 scheme.
After getting data you need to convert it raw binary format.
Convert Base64 data to Uint8Array if you are working with media,documents or anything else literally. Plain text files(ASCII) are fine without it.
So here is updated download code(i use node-js but there is js-base64 for plain JS also):
import {Base64} from "js-base64";
function download(filename) {
axios({
url: "http://192.168.0.149:8080/api/dataupdown/download",
method: "GET",
params: {
filename: filename
},
})
.then((res) => {
console.log(res.data);
const url = window.URL.createObjectURL(new Blob([Base64.toUint8Array(res.data.data)]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", res.data.filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}

Error passing multiple input files, Design Automation Revit dot net core

I had asked my last question regarding this. I have one rfa file and a bigger JSon file as input. When I debug, it looks perfectly passing all values until last line:
WorkItemStatus workItemStatus = await _designAutomation.CreateWorkItemAsync(workItemSpec);
Looks like workItemSpec has some problem. Please have a look at these code fractions and please let me know what is wrong here.
code fraction from ForgeDesignAutomation.js as below:
{
var inputFileField = document.getElementById('inputFile');
if (inputFileField.files.length === 0) { alert('Please select an input file'); return; }
if ($('#activity').val() === null) { alert('Please select an activity'); return };
var file = inputFileField.files[0];
var inputFileField2 = document.getElementById('inputJsonFile');
if (inputFileField2.files.length === 0) { alert('Please select an input file'); return; }
var file2 = inputFileField2.files[0];
let activityId = $('#activity').val();
if (activityId == null)
{
alert('Please select an activity'); return
};
if (activityId.toLowerCase() === "_da4ractivity+dev")
{
startConnection(function () {
var formData = new FormData();
formData.append('inputFile', file);
formData.append('inputJsonFile', file2);
formData.append('data', JSON.stringify({
activityName: $('#activity').val(),
browerConnectionId: connectionId
}));
writeLog('Uploading input file...');
$.ajax({
url: 'api/forge/designautomation/workitems',
data: formData,
processData: false,
contentType: false,
type: 'POST',
success: function (res)
{
writeLog('Workitem started: ' + res.workItemId);
}
});
});
}
}```
code fraction from "CreateActivity" in DesignAutomationController.cs
``` Parameters = new Dictionary<string, Parameter>()
{
{ "inputFile", new Parameter() { Description = "input file", LocalName = "$(inputFile)", Ondemand = false, Required = true, Verb = Verb.Get, Zip = false } },
{ "inputJsonFile", new Parameter() { Description = "input Json file", LocalName = "$(inputJsonFile)", Ondemand = false, Required = true, Verb = Verb.Get, Zip = false } },
{ "outputFile", new Parameter() { Description = "output file", LocalName = "outputFile." + engineAttributes.extension, Ondemand = false, Required = true, Verb = Verb.Put, Zip = false } }
}```
code fraction from "StartWorkitem" in DesignAutomationController.cs
``` [HttpPost]
[Route("api/forge/designautomation/workitems")]
public async Task<IActionResult> StartWorkitem([FromForm]StartWorkitemInput input)
{
// basic input validation
JObject workItemData = JObject.Parse(input.data);
string activityName = string.Format("{0}.{1}", NickName, workItemData["activityName"].Value<string>());
string browerConnectionId = workItemData["browerConnectionId"].Value<string>();
// save the file on the server
var fileSavePath0 = Path.Combine(_env.ContentRootPath, Path.GetFileName(input.inputFile.FileName));
using (var stream0 = new FileStream(fileSavePath0, FileMode.Create)) await input.inputFile.CopyToAsync(stream0);
var fileSavePath1 = Path.Combine(_env.ContentRootPath, Path.GetFileName(input.inputJsonFile.FileName));
using (var stream1 = new FileStream(fileSavePath1, FileMode.Create)) await input.inputJsonFile.CopyToAsync(stream1);
//---------------------------------------------------------------------------------------------------------------------
// OAuth token
dynamic oauth = await OAuthController.GetInternalAsync();
// upload file to OSS Bucket
// 1. ensure bucket existis
string bucketKey = NickName.ToLower() + "-designautomation";
BucketsApi buckets = new BucketsApi();
buckets.Configuration.AccessToken = oauth.access_token;
try
{
PostBucketsPayload bucketPayload = new PostBucketsPayload(bucketKey, null, PostBucketsPayload.PolicyKeyEnum.Transient);
await buckets.CreateBucketAsync(bucketPayload, "US");
}
catch { };
// in case bucket already exists
// 2. upload inputFile
string inputFileNameOSS0 = string.Format("{0}_input_{1}", DateTime.Now.ToString("yyyyMMddhhmmss"), Path.GetFileName(input.inputFile.FileName)); // avoid overriding
string inputFileNameOSS1 = string.Format("{0}_inputJson_{1}", DateTime.Now.ToString("yyyyMMddhhmmss"), Path.GetFileName(input.inputJsonFile.FileName)); // avoid overriding
//string inputFileNameOSS = Path.GetFileName(input.inputFile.FileName); // avoid overriding
ObjectsApi objects = new ObjectsApi();
objects.Configuration.AccessToken = oauth.access_token;
using (StreamReader streamReader = new StreamReader(fileSavePath0))
await objects.UploadObjectAsync(bucketKey, inputFileNameOSS0, (int)streamReader.BaseStream.Length, streamReader.BaseStream, "application/octet-stream");
System.IO.File.Delete(fileSavePath0);// delete server copy
// prepare workitem arguments
// 1. input file
XrefTreeArgument inputFileArgument = new XrefTreeArgument()
{
Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", bucketKey, inputFileNameOSS0),
Headers = new Dictionary<string, string>()
{
{ "Authorization", "Bearer " + oauth.access_token }
}
};
using (StreamReader streamReader1 = new StreamReader(fileSavePath1))
await objects.UploadObjectAsync(bucketKey, inputFileNameOSS1, (int)streamReader1.BaseStream.Length, streamReader1.BaseStream, "application/octet-stream");
System.IO.File.Delete(fileSavePath1);// delete server copy
// 1. input Json file
XrefTreeArgument inputJsonArgument = new XrefTreeArgument()
{
Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", bucketKey, inputFileNameOSS1),
Headers = new Dictionary<string, string>()
{
{ "Authorization", "Bearer " + oauth.access_token }
}
};
// 3. output file
string outputFileNameOSS = string.Format("{0}_output_{1}", DateTime.Now.ToString("yyyyMMddhhmmss"), Path.GetFileName(input.inputFile.FileName)); // avoid overriding
XrefTreeArgument outputFileArgument = new XrefTreeArgument()
{
Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", bucketKey, outputFileNameOSS),
Verb = Verb.Put,
Headers = new Dictionary<string, string>()
{
{"Authorization", "Bearer " + oauth.access_token }
}
};
// prepare & submit workitem
string callbackUrl = string.Format("{0}/api/forge/callback/designautomation?id={1}&outputFileName={2}", OAuthController.GetAppSetting("FORGE_WEBHOOK_URL"), browerConnectionId, outputFileNameOSS);
WorkItem workItemSpec = new WorkItem()
{
ActivityId = activityName,
Arguments = new Dictionary<string, IArgument>()
{
{ "inputFile", inputFileArgument },
{ "inputJsonFile", inputJsonArgument },
{ "outputFile", outputFileArgument },
{ "onComplete", new XrefTreeArgument { Verb = Verb.Post, Url = callbackUrl } }
}
};
WorkItemStatus workItemStatus = await _designAutomation.CreateWorkItemAsync(workItemSpec);
return Ok(new { WorkItemId = workItemStatus.Id });
}```
```/// <summary>
/// Input for StartWorkitem
/// </summary>
public class StartWorkitemInput[![enter image description here][1]][1]
{
public IFormFile inputFile { get; set; }
public IFormFile inputJsonFile { get; set; }
public string data { get; set; }
} ```
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/GktUC.png
I noticed your activity defines the "localName" of "inputFile"/"inputJsonFile" to be "$(inputFile)"/"$(inputJsonFile)"
Parameters = new Dictionary<string, Parameter>()
{
{ "inputFile", new Parameter() { Description = "input file", LocalName = "$(inputFile)", Ondemand = false, Required = true, Verb = Verb.Get, Zip = false } },
{ "inputJsonFile", new Parameter() { Description = "input Json file", LocalName = "$(inputJsonFile)", Ondemand = false, Required = true, Verb = Verb.Get, Zip = false } }
}
This could cause some issue when resolving the naming of inputs during the job process. I would suggest you provide a concrete filename as WorkItem argument's localname and try again. For example:
XrefTreeArgument inputJsonArgument = new XrefTreeArgument()
{
Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", bucketKey, inputFileNameOSS1),
Headers = new Dictionary<string, string>()
{
{ "Authorization", "Bearer " + oauth.access_token }
},
// Note: if your appbunldes(addin) uses this json, this is the name your addin should know
LocalName = "inputJson.json"
};

i cant export my list to csv using mvc c#

I have been dealing with a problem for hours,kindly help me,the following is my ajax which post data to controller :
$.ajax({
dataType: "json",
type: "POST",
url: "#Url.Action("CreateCSVFile ","Turbine ")",
contentType: "application/json;charset=utf-8",
data: JSON.stringify(data),
success: function(result) {}
})
It posts the result I want to controller,but problem starts from here that in my controller after making the export file,i don't see any thing in the browser to save it,i have no idea where is going wrong,the following is my controller:
public FileContentResult CreateCSVFile(string turbinename, string frm_date, string to_date)
{
var eventResult = (from c in DB.Events
where (c.m_turbine_id == turbineid.turbineID) && (c.m_time_stamp >= frmDate && c.m_time_stamp <= toDate)
select new EventLogPartialViewModel
{
Timestamp = c.m_time_stamp,
Description = c.m_event_log_description,
WindSpeed = c.m_wind_speed,
RPM = c.m_rpm,
Power = c.m_power
}).ToList().Select(x => new
{
Timestamp = x.Timestamp.ToString("dd/MM/yyyy H:mm:ss"),
Description = x.Description,
WindSpeed = x.WindSpeed,
RPM = x.RPM,
Power = x.Power
}).ToList().OrderByDescending(m => m.Timestamp);
var st = DataTag.NoExclusion;
string csv = "Charlie, Chaplin, Chuckles";
byte[] csvBytes = ASCIIEncoding.ASCII.GetBytes(CSVExport.GetCsv(eventResult.ToList(), st));
return File(new System.Text.UTF8Encoding().GetBytes(csv), "text/csv", "Report123.csv");
}
To fit your particular function, you can do :
public FileContentResult CreateCSVFile()
{
string csv = "Charlie, Chaplin, Chuckles";
byte[] csvBytes = Encoding.UTF8.GetBytes(csv); // or get your bytes the way you want
string contentType = "text/csv";
var result = new FileContentResult(csvBytes, contentType);
return result;
}
This is how you can create a FileContentResult.
However, I prefer to use this to send a response with a file :
[HttpGet]
public HttpResponseMessage GetYourFile()
{
string csv = "Charlie, Chaplin, Chuckles";
byte[] csvBytes = Encoding.UTF8.GetBytes(csv); // or get your bytes the way you want
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(csvBytes);
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "YourFileName.csv"
};
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("text/csv");
return result;
}
(adapted from How to return a file (FileContentResult) in ASP.NET WebAPI).
Extracting the "csv" part in bytes is another problem completely, I hope your problem was about the "create a file" part.

How to upload picture in Spring and Angular2

I want to upload picture in my app this is my Angular 2 code
constructor () {
this.progress = Observable.create(observer => {
this.progressObserver = observer
}).share();
}
public makeFileRequest (url: string, params: string[], files: File[]):
Observable<any> {
return Observable.create(observer => {
let formData: FormData = new FormData(),
xhr: XMLHttpRequest = new XMLHttpRequest();
for (let i = 0; i < files.length; i++) {
formData.append("uploads[]", files[i], files[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
xhr.upload.onprogress = (event) => {
this.progress = Math.round(event.loaded / event.total * 100);
this.progressObserver.next(this.progress);
};
xhr.open('POST', url, true);
xhr.send(formData);
});
}
And this is my spring controller
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<?> uploadFile(
#RequestParam("file") MultipartFile uploadfile) {
try {
// Get the filename and build the local file path (be sure that the
// application have write permissions on such directory)
String filename = uploadfile.getOriginalFilename();
String directory = "/assets/images";
String filepath = Paths.get(directory, filename).toString();
// Save the file locally
BufferedOutputStream stream =
new BufferedOutputStream(new FileOutputStream(new File(filepath)));
stream.write(uploadfile.getBytes());
stream.close();
}
catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
And I get this error
ERROR {"timestamp":1498220487868,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/webapp/api/picture/upload"}
You specify #RequestParam("file"), which means Spring waits for an argument with the key file, but you append your data with the key uploads.
Also, as said by #ledniov, your are receiving an array of multipart files, not only one.
Corrections if you want to use the key file:
// Front-End: change "uploads" to "file"
formData.append("file[]", files[i], files[i].name);
// Back-End: takes a MultipartFile array
#RequestParam("file") MultipartFile[] uploadfile
Corrections if you want to use the key uploads:
// Back-End: change "file" to "uploads" and takes a MultipartFile array
#RequestParam("uploads") MultipartFile[] uploadfile

post image to asp.net API 2 and angular 2

i get 415 (Unsupported Media Type) when i try to post image to ASP.Net API 2
The request entity's media type 'multipart/form-data' is not supported for this resource.","exceptionMessage": "No MediaTypeFormatter is available to read an object of type 'Attachment' from content with media type 'multipart/form-data.
my Backend Post method :
[HttpPost]
[Route("api/listing/{listingId}/Attachment/")]
public async Task<dynamic> Post([FromBody] Attachment model)
{
var Attachments = new Attachment
{
ListingId = model.ListingId,
Content = model.Content,
Description = model.Description,
Name = model.Name,
MimeType = model.MimeType,
Size = model.Size,
CreatedOn = DateTime.UtcNow,
UpdatedOn = model.UpdatedOn,
};
return await DataStore.InsertDynamicAsync(Attachments);
}
and my front-end method :
onChangeImage(e: any) {
console.log(this.className + 'onChangeImage.event=' +JSON.stringify(event));
console.log(this.className + 'onChangeImage._listingAttachmentService undefined?: ' + (this._listingAttachmentService === undefined));
const inputElement = this.fileInput.nativeElement;
const fileList = inputElement.files;
const files = [];
console.log('AttachmentsTabComponent: file count = ' + fileList.length);
if (fileList.length > 0) {
for (let i = 0; i < fileList.length; i++) {
// get item
const file = fileList.item(i);
files.push(file);
const model: Attachment = {
listingId: this.listing.id,
mimeType: file.type,
name: file.name,
size: file.size,
updatedOn: file.lastModifiedDate
};
const reader = new FileReader();
reader.readAsDataURL(file);
console.log(this.className + 'onChangeImage.listingAttachmentService (before reader.onload) undefined?: ' + (this._listingAttachmentService === undefined));
reader.onload = (readerEvt: any) => {
const binaryString = readerEvt.target.result;
//base-64 encoded ASCII string
model.content = btoa(binaryString);
console.log(this.className + 'onChangeImage.listingAttachmentService (during reader.onload) undefined?: ' + (this._listingAttachmentService === undefined));
console.log(this.className + 'ListingAttachmentModel.content.length=' + JSON.stringify(model.content.length));
// this._listingAttachmentService.add(model);
};
}
// try to clear the file input
try {
// TODO: fix this
this.fileForm.nativeElement.reset();
inputElement.value = '';
if (inputElement.value) {
inputElement.type = 'text';
inputElement.type = 'file';
}
} catch (e) { }
this._listingAttachmentService.upload(this.listing.id, files)
.subscribe(data => {
this.listing.attachments = data;
});
}
}
and my listingAttachmentService
upload(listingId: number, files: Array<File>) {
this._logger.debug('method upload() entered');
this._logger.debug('upload() listingId=' + listingId);
this._logger.debug('this.fileToUpload.length=' + files.length);
var self = this;
return Observable.create(observer => {
console.log('creating Observable');
let formData: FormData = new FormData(),
xhr: XMLHttpRequest = new XMLHttpRequest();
formData.append('listingId', listingId);
for (let i = 0; i < files.length; i++) {
formData.append('uploads[]', files[i], files[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
let newbaseUrl = self.baseUrl + listingId + '/attachment/' ;
xhr.open('POST', newbaseUrl, true);
xhr.send(formData);
})
.catch(this.handleError);
}
You should use a custom MediaTypeFormatter. More information here: http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters and a sample here http://blog.marcinbudny.com/2014/02/sending-binary-data-along-with-rest-api.html#.V5MDDzV7qYg
public class CustomFormatter : MediaTypeFormatter
{
public CustomFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
}
public override bool CanReadType(Type type)
{
return type == typeof(Attachment);
}
public override bool CanWriteType(Type type)
{
return false;
}
public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var provider = await content.ReadAsMultipartAsync();
var modelContent = provider.Contents
.FirstOrDefault(c => c.Headers.ContentType.MediaType == "application/json"); // Can also use ContentDisposition.Name.Normalize == "attachment"
var attachment = await modelContent.ReadAsAsync<Attachment>();
var fileContents = provider.Contents
.Where(c => c.Headers.ContentType.MediaType == "image/jpeg").FirstOrDefault(); // can also use ContentDisposition.Name.Normalize() == "image"
attachment.Content = await fileContents.ReadAsByteArrayAsync();
return attachment;
}
}
Register the custom media formatter:
private void ConfigureWebApi(HttpConfiguration config)
{
//other code here
config.Formatters.Add(new CustomFormatter());
}
A post will like below
POST http://localhost/api/FileUpload HTTP/1.1
Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
User-Agent: Fiddler
Content-Length: 88778
---------------------------acebdf13572468
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=attachment
{"ListingId":"testl"}
---------------------------acebdf13572468
Content-Disposition: form-data; name=image; filename=image.jpg
Content-Type: image/jpeg
Image content
---------------------------acebdf13572468--
Hope this helps.

Resources