ASP.NET WebAPI - File Upload with FormData doesn't have all the form data fields - react-native

I'm trying to upload a file with some form data using axios in React-Native as multipart/form-data to ASP.NET Web API.
I've followed this - https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/sending-html-form-data-part-2
and I'm reading the file and form data using MultipartFormDataStreamProvider which seems to be getting both the file and form data. However, the form data isn't complete. For example, I'm sending about 14 fields to the server, but I'm only getting 7 when I try to read it through provider.FormData.
My React Native / Axios Code
var dataToSubmit = new FormData();
// Have about 14 fields in the FormData
dataToSubmit.append('Key1', 'value1');
dataToSubmit.append('Key2', 'value2');
dataToSubmit.append('Key3', 'value3');
.
.
dataToSubmit.append('Key14', 'value14');
// Have 1 file in the FormData
dataToSubmit.append('File', {
uri: filePath,
type: 'image/jpeg',
name: fileName
});
axios({
method: 'POST',
url: 'URL to post',
data: dataToSubmit,
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((response) => {
console.log(response);
});
My Request
I have a debugger on the phone and I saw the following request (with all the 14 fields and the file)
POST /api/urltopost HTTP/1.1
accept: application/json, text/plain, */*
Content-Type: multipart/form-data;
boundary=BOUNDARY_GUID
Content-Length: 53038
Host: HOST_IP
Connection: Keep-Alive
Accept-Encoding: gzip
--BOUNDARY_GUID
content-disposition: form-data; name="Key1"
Content-Length: 24
--BOUNDARY_GUID
content-disposition: form-data; name="Key2"
Content-Length: 24
..
..
--BOUNDARY_GUID
content-disposition: form-data; name="File"
filename="test.jpg",
Content-Type: image/jpeg
Content-Length: 50626
My ASP.NET Web API Code
public async Task<HttpResponseMessage> PostFormData(DTO obj)
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
// Show all the key-value pairs.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
When I look at provider, it shows Contents with count 8, FileData with count 1 and FormData with 7 fields. But I seem to be missing other 7 fields.
NOTE:
I'm using OWIN
I tried uploading files from 21 KB to 5.2 MB. I always get the 8 contents. I even tried multipart/form-data submission without the file and I still get only those fields.
I have the following in my web.config to set request limit, which seems to have no impact with the buffer size of the provider -
<!-- Under system.webServer -->
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="2147483648" />
</requestFiltering>
</security>
<!-- Under system.web -->
<httpRuntime targetFramework="4.5.2" maxRequestLength="2097152" />
I tried using MultipartMemoryStreamProvider and it still gets the 8 contents.
I tried using Postman and I'm still getting the form data in chunk / 8 fields.
I tried setting the buffer size to 5120000 (since it's 4098 by default) for MultipartFormDataStreamProvider and I still get only those 8 fields. I can't seem to find why the other form fields aren't showing up / I can't get the value for them on the server side. I'm pretty sure I'm not doing anything wrong on the client side, since it's not working with Postman either. So I'm guessing it's just the way I'm reading Multipart data on the API.
Can someone tell me what I'm missing / doing wrong? Why am I not seeing all the fields that are sent through the form?

I couldn't figure out why the entire form data was not being submitted. So here is my workaround -
I'm making 2 calls to save the data -
A x-www-form-urlencoded request to save the FormData. I get the record ID / key from this save.
A multipart/form-data request to save the file with the key / identifier

Related

PostAsJsonAsync posts null

I am trying to post an object using PostAsJsonAsync, but it is always null at the receiving API. The calling code is here:
public async Task UploadDocument(FileDto model)
{
var response = await _httpClient.PostAsJsonAsync("file/UploadDocument", model);
response.EnsureSuccessStatusCode();
}
The signature of the receiving code is here:
[HttpPost]
[Route("UploadDocument")]
public async Task<IHttpActionResult> UploadDocument(FileDto document)
FileDto is identical in both projects and only contains one string property "FileName"
The problem is that the document is always null.
I can use PostAsync which works fine:
public async Task UploadDocument(FileDto model)
{
string inputJson = Newtonsoft.Json.JsonConvert.SerializeObject(model);
HttpContent inputContent = new StringContent(inputJson, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("file/UploadDocument", inputContent);
response.EnsureSuccessStatusCode();
}
Looking at Fiddler, with the first (not working) example, the request looks like this:
POST http://localhost:59322/api/file/UploadDocument HTTP/1.1
Accept: application/json
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Host: localhost:59322
28
{"FileName":"File-0000004157.jpg"}
0
The second (working) example looks like this in Fiddler:
POST http://localhost:59322/api/file/UploadDocument HTTP/1.1
Accept: application/json
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Host: localhost:59322
{"FileName":"File-0000004157.jpg"}
The first example appears to have added extra text - see the "28" and "0".
Where is this text coming from. I can just go with PostAsync, but it seems a shame to add the extra code when PostAsJsonAsync does it for you.
Anybody have any ideas?
You need to return IActionResult instead of IHttpActionResult in asp.net core
[HttpPost]
[Route("UploadDocument")]
public async Task<IActionResult> UploadDocument(FileDto document)
{
return Ok(document);
}
https://learn.microsoft.com/en-us/aspnet/core/migration/webapi?view=aspnetcore-3.0
ASP.NET Core Web Api Error for IhttpActionResult
There seems to be an issue with PostAsJsonAsync in .net core : It returns a content-length of 0 causing the receiving ends to ignore the declared empty body.
https://github.com/aspnet/AspNetWebStack/issues/252
HttpClient PostAsJsonAsync behaving different in .NET Core and Classic .NET
The workaround is to use... PostAsync

Ionic CORS Error, But Server Has CORS Enabled

I have an Ionic 4 app that uses a lambda API hosted on AWS. CORS is enabled on the API Gateway. The following snippet is from a curl request to the API.
< content-type: application/json
< content-length: 42
< date: Sat, 16 Feb 2019 02:19:25 GMT
< x-amzn-requestid: 47a5fcac-3191-11e9-af42-d387861aa6ad
< access-control-allow-origin: *
< access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
< x-amz-apigw-id: VK7vFGc4oAMFTqg=
< access-control-allow-methods: POST,OPTIONS
This post discusses a few possible workarounds (change content type, etc.), but they don't work.
Changing the Content-Type header to text/plain or removing that header altogether makes no difference.
The following error is also presented on the Ionic console
Cross-Origin Read Blocking (CORB) blocked cross-origin response
https://mycoolapi.com/GetLegal with MIME type application/json.
See https://www.chromestatus.com/feature/5629709824032768 for more details.
The following is my service code.
getLegal(data: any) {
return new Promise((resolve, reject) => {
let httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
this.httpClient.post(this.apiUrl+'/GetLegal', JSON.stringify(data), {
headers: httpHeaders,
})
.subscribe(res => {
resolve(new LegalResponse(res));
}, (err) => {
console.log("Oops, there has been an error")
reject(err);
});
});
}
Help?
This ended up being a bug on the Amazon side. The curl snippet was from a GET method, which was sending the CORS headers. The POST method was not. After redeploying the API without changing anything, the GET method was no longer sending the CORS headers and the POST method was. The application is working, for now.

Multi-Part upload has no payload

I am trying to do a multi-part upload from my javascript application and it works fine in the chrome browser but fails without error when run in electron.
I can't find information, why it fails in electron, but maybe some of you have an idea? :)
var fileContent = new Uint8Array([64,65,66,67]);
var fileBlob = new Blob([fileContent], {type: 'application/octet-stream'});
var data = new FormData();
data.set('file', fileBlob, 'blob.bin');
fetch('http://my.end.point', {
method: 'POST',
body: data
});
When I run that code in Chrome, I can see the 4 bytes payload in the network tab and my end point receives the 4 bytes. If I do the same in electron, the payload in the network tab is empty and the end point receives the multi-part request with the payload missing. I also tried using XMLHttpRequest instead of fetch but that shows exactly the same behavior.
Request Payload from within electron:
------WebKitFormBoundaryNXzPiiAvBttdDATr
Content-Disposition: form-data; name="file"; filename="blob.bin"
Content-Type: application/octet-stream
------WebKitFormBoundaryNXzPiiAvBttdDATr--
Request payload from withon chrome browser:
------WebKitFormBoundarywTEtXn4z3NFt3sAb
Content-Disposition: form-data; name="file"; filename="blob.bin"
Content-Type: application/octet-stream
#ABC
------WebKitFormBoundarywTEtXn4z3NFt3sAb--
Does someone know, why it doesn't work from within electron?

Change header on s3 file

If I have a file on s3 how can I change metadata of that file?
It looks like I can "copy" it to the same location with new headers which would effectively be the same thing.
I'm using knox as the node client to do this. The file in question already has the Content-Type header set to video/mp4 but I want to change it to application/octet-stream. The reason for this is so that this link will trigger the browser to download the resource instead of displaying it in the browser window.
Link to knox source for this function
var filename = "/example/file.mp4",
headers = {'Content-Type': "application/octet-stream"};
client.copyFile(filename, filename, headers, function(error, resp) {
//response is successful
});
The response is successful, but when I reload the resource in s3 I don't see that headers have changed.
I can see that the underlying API call is this:
'PUT /example/file.mp4 HTTP/1.1\r\nContent-Type: application/octet-stream
x-amz-copy-source: /bucket/example/file.mp4
Content-Length: 0\r\nDate: Thu, 28 Jan 2016 21:13:12 GMT
Host: cc-video-archives-dev.s3.amazonaws.com
Authorization: <redacted>=\r\nConnection: close\r\n\r\n',
I was missing this header:
"x-amz-metadata-directive": "REPLACE"
var filename = "/example/file.mp4",
headers = {
"x-amz-metadata-directive": "REPLACE",
'Content-Type': "application/octet-stream"
};
client.copyFile(filename, filename, headers, function(error, resp) {
//response is successful
});

Sending binary data using multipart/form-data from a worker with IE11

I'm trying to send a multipart/form-data from a worker with IE. I've already done it with Chrome, Firefox, Safari using formData objects (not supported IE, I need a manual one)
The binary data I'm sending is a crypto-js encrypted data. With formData objects I do:
var enc = new Buffer(encrypted.ciphertext.toString(CryptoJS.enc.Base64), 'base64');
formData.append("userFile" , new Blob([finalEncrypted], {type: 'application/octet-binary'}), 'encrypted')
this works fine generating a multipart like this(missed some parts of it):
request headers:
Accept:*/*
Accept-Encoding:gzip, deflate
Cache-Control:no-cache
Connection:keep-alive
Content-Length:30194
Content-Type:multipart/form-data; boundary=WebKitFormBoundary0.gjepwugw5cy58kt9
body:
--WebKitFormBoundary0.gjepwugw5cy58kt9
Content-Disposition: form-data; name="userFile"; filename="encrypted"
Content-Type: binary
all binary data
--WebKitFormBoundary0.cpe3c80eodgc766r--
With the manual multipart/form-data:
IE11 doesn't accept readAsBinaryString(deprecated)
I would like to avoid sending base64 encoded data(readAsDataURL)(33% payload)
The binary data I'm sending is a crypto-js encrypted data.
I'm trying:
finalEncrypted = new Buffer(encrypted.ciphertext.toString(CryptoJS.enc.Base64), 'base64');
then in my manual multipart I tried to convert the buffer to a binary string:
item.toString('binary')
the multipart result looks looks this:
--WebKitFormBoundary642013568702052
Content-Disposition: form-data; name="userfile"; filename="encrypted"
Content-Type: binary
all binary data
ÐçÀôpRö3§]g7,UOÂmR¤¼ÚS"Ê÷UcíMÆÎÚà/,hy¼øsËÂú#WcGvºÆÞ²i¨¬Ç~÷®}éá?'é·J]þ3«áEÁÞ,4üBçðºÇª bUÈú4
T\Ãõ=òEnýR _[1J\O-ïǹ C¨\Ûøü^%éÓÁóJNÓï¹LsXâx>\aÁV×Þ^÷·{|­'
On the .NET server we check the hash calculated on client versus calculated on server. Server reply that hashes doesn't match. This makes me think that I'm not sending the file correctly.
It looks like you did not yet get a solution, at least you did not post it here if you had one.
On my end I use jQuery which handles the low level nitty gritty of the actual post.
It may be that you are doing one small thing wrong and IE fails on it. Since you do not show what you used with FormData. It is rather difficult to see whether you had a mistake in there.
// step 1. setup POST data
var data = new FormData();
data.append("some_variable_name", "value_for_that_variable");
data.append("some_blob_var_name", my_blob);
data.append("some_file_var_name", my_file);
// step 2. options
var ajax_options =
{
method: "POST",
processData: false,
data: data,
contentType: false,
error: function(jqxhr, result_status, error_msg)
{
// react on errors
},
success: function(data, result_status, jqxhr)
{
// react on success
},
complete: function(jqxhr, result_status)
{
// react on completion (after error/success callbacks)
},
dataType: "xml" // server is expected to return XML only
};
// step 3. send
jQuery.ajax(uri, ajax_options);
Step 1.
Create a FormData object and fills the form data, that includes variables and files. You may even add blobs (JavaScript objects, will be transformed to JSON if I'm correct.)
Step 2.
Create an ajax_options object to your liking. Although here I show your the method, processData, data, contentType as they must be in case you want to send a FormData. At least, that works for me... It may be possible to change some of those values.
The dataType should be set to whatever type you expect in return.
Step 3.
Send the request using the ajax() function from the jQuery library. It will build the proper header and results as required for the client's browser.