WebAPI cannot parse multipart/form-data post - c#

I'm trying to accept a post from a client (iOS app) and my code keeps failing on reading the stream. Says the message is not complete. I've been trying to get this working for hours it seems like something is wrong in my message format or something. All I'm trying to do is read a string but the developer I'm working with who is doing the iOS part only knows how to send multipart/form-data not content-type json.
Here is exact error:
Unexpected end of MIME multipart stream. MIME multipart message is not complete."
It fails here: await Request.Content.ReadAsMultipartAsync(provider);
Headers:
POST http://localhost:8603/api/login HTTP/1.1
Host: localhost:8603
Accept-Encoding: gzip,deflate
Content-Type: multipart/form-data; boundary=------------nx-oauth216807
Content-Length: 364
Accept-Language: en-us
Accept: */*
Connection: keep-alive
Body:
--------------nx-oauth216807
Content-Disposition: form-data; name="token"
CAAH5su8bZC1IBAC3Qk4aztKzisZCd2Muc3no4BqVUycnZAFSKuleRU7V9uZCbc8DZCedYQTIFKwJbVZCANJCs4ZCZA654PgA22Nei9KiIMLsGbZBaNQugouuLNafNqIOTs9wDvD61ZA6WSTd73AVtFp9tQ1PmFGz601apUGHSimYZCjLfGBo40EBQ5z6eSMNiFeSylym1pK4PCvI17fXCmOcRix4cs96EBl8ZA1opGKVuWizOsS0WZCMiVGvT
--------------nx-oauth216807--
Here is the WebAPI code:
public async Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
try
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}

My application was experiencing this error periodically too. Upgrading to WEB API 2.1 did nothing and the exception message is completely useless.
I think what was actually happening though is it was choking on large files. Increasing my max request limits in web.config seemed to fix it right up.
<system.web>
<httpRuntime maxRequestLength="30000" />
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="30000" />
</requestFiltering>
</security>
</system.webServer>
(This sets the ceiling to 30 megs. Set it to whatever you need. More info here)

I encountered this error too. The InnerException is Cannot access a disposed object. This means that something is reading your stream before your call to ReadAsMultipartAsync.
Somewhere before this call Request.Content.ReadAsMultipartAsync(provider), you can call
Request.Content.LoadIntoBufferAsync().Wait(), which will load this tream into a buffer and allow you to read it more than once.
This is not an optimal solution, but it works.

I am leaving this here since it took me quite some time trying other workarounds until I bumped onto the following helpful answer and some people having this issue may end up on this post.
A \r\n needs to be appended at the end of request content stream.
Instead of using this line to read the data:
await Request.Content.ReadAsMultipartAsync(provider);
You will need to:
load the request stream to memory
append the \r\n string that is required
create a stream content from the memory content
manually add the request headers to the stream content
Finally use this instead:
streamContent.ReadAsMultipartAsync(provider);
Check the answer of Landuber Kassa here for the complete code: ASP.NET Web API, unexpected end of MIME multi-part stream when uploading from Flex FileReference

Just a modification for Shaw Levin answer in case anyone wants to use it.
boundary = value.Substring(0, value.IndexOf("\r\n")); will find the first occurance of the CRLF, you should change it to boundary = value.Substring(0, value.LastIndexOf("\r\n")); so it looks for the last occurance. Otherwise if the content includes a CRLF somewhere in the middle you will lose part of the data in the request.

There were similar error posts, for some, the solution worked is: to mention Id="", name="" attribute to file upload html control, thanks to WebAPI upload error. Expected end of MIME multipart stream. MIME multipart message is not complete
But in my case, it did not resolve with above simple tweak :(

I would not recommend this answer - hopefully there is a better way available now.
Someone asked so here is my custom parser which has been working fine:
Boundary comes from here:
string value;
using (var reader = new StreamReader(tempStream, Encoding.UTF8))
{
value = reader.ReadToEnd();
// Do something with the value
}
boundary = value.Substring(0, value.IndexOf("\r\n"));
And then we parse the content of the request here:
public Dictionary<string, BodyContent> ParseContent(string content)
{
string[] list = content.Split(new string[] { boundary }, StringSplitOptions.RemoveEmptyEntries);
string name="", val="";
Dictionary<string, BodyContent> temp = new Dictionary<string, BodyContent>();
foreach (String s in list)
{
if (s == "--" || s == "--\r\n")
{
//Do nothing.
}
else
{
string[] token = s.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
val = "";
name = "";
foreach (string x in token)
{
if(x.StartsWith("Content-Disposition"))
{
//Name
name = x.Substring(x.IndexOf("name=")+5, x.Length - x.IndexOf("name=")-5);
name = name.Replace(#"\","");
name = name.Replace("\"","");
}
if (x.StartsWith("--"))
{
break;
}
if (!x.StartsWith("--") && !x.StartsWith("Content-Disposition"))
{
val = x;
}
}
if (name.Length > 0)
{
BodyContent b = new BodyContent();
b.content = name;
if (val.Length == 0)
{
b.value = "";
}
else
{
b.value = val;
}
temp.Add(name, b);
}
}
}
return temp;
}

Related

C# StreamContent and File.OpenRead() Not Producing HTTP'able multipart content

I have a .Net Core 2.0 application that is sending files to a Web API endpoint, using multipart content. Everywhere I've looked, such as C# HttpClient 4.5 multipart/form-data upload, makes it seem that it should be as easy as passing a FileStream to a StreamContent. However, when I make the post, it looks like the file is attaching as text, not bits.
Actual code:
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri( "http://localhost:10442/filetest" )
};
var multiContent = new MultipartFormDataContent();
var filestream = File.OpenRead( path );
var filename = Path.GetFileName( path );
var streamContent = new StreamContent( filestream );
streamContent.Headers.Add( "Content-Type", "application/octet-stream" );
streamContent.Headers.Add( "Content-Disposition", $"form-data; name=\"file1\"; filename=\"{filename}\"" );
multiContent.Add( streamContent, "file", filename );
request.Content = multiContent;
var response = await new HttpClient().SendAsync( request );
The request looks like this which, as you may notice, is not all on one line (which I think is a/THE problem):
POST http://localhost:10442/filetest HTTP/1.1
Content-Type: multipart/form-data; boundary="c5295887-425d-4ec7-8638-20c6254f9e4b"
Content-Length: 88699
Host: localhost:10442
--c5295887-425d-4ec7-8638-20c6254f9e4b
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file1"; filename="somepdf.pdf"
%PDF-1.7
%
1 0 obj
<</Type/Catalog/Version/1.7/Pages 3 0 R/Outlines 2 0 R/Names 8 0 R/Metadata 31 0 R>>
endobj
Fiddler shows the entire post all the way down to the end boundary, but await Request.Content.ReadAsStringAsync() in the endpoint only shows the first couple dozen bytes (it looks as if the stream wasn't finished, but if Fiddler got it all, shouldn't my endpoint have too?).
I was having similar trouble trying to hit a remote endpoint; I built this endpoint to test locally.
The exception I'm getting is:"Unexpected end of MIME multipart stream. MIME multipart message is not complete." To me, this makes sense both if I'm really only getting part of my stream, or if the line breaks are throwing something off.
I have also tried throwing some of the Idisposables into Usings but, as expected, that closes the streams and I get exceptions that way.
And for completeness's sake, here's the endpoint I'm calling:
public async void ReceiveFiles()
{
// exception happens here:
var mpData = await Request.Content.ReadAsMultipartAsync();
await Task.FromResult( 0 );
}
Try something like this:
static int Main(string[] args)
{
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://localhost:10442/filetest")
};
var path = "c:\\temp\\foo.bak";
using (var filestream = File.OpenRead(path))
{
var length = filestream.Length.ToString();
var streamContent = new StreamContent(filestream);
streamContent.Headers.Add("Content-Type", "application/octet-stream");
streamContent.Headers.Add("Content-Length", length);
request.Content = streamContent;
Console.WriteLine($"Sending {length} bytes");
var response = new HttpClient().SendAsync(request).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
return 0;
}
and
[HttpPost]
public async Task<IActionResult> Upload()
{
var buf = new byte[1024 * 64];
long totalBytes = 0;
using (var rs = Request.Body)
{
while (1 == 1)
{
int bytesRead = await rs.ReadAsync(buf, 0, buf.Length);
if (bytesRead == 0) break;
totalBytes += bytesRead;
}
}
var uploadedData = new
{
BytesRead = totalBytes
};
return new JsonResult(uploadedData) ;
}
I'm trying to solve a similar issue, and I'm not 100% to a solution yet, but maybe some of my research can help you.
It was helpful to me to read through the microsoft docs for .NET core file uploads, specifically for large files that use streams and multipart form data:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1#uploading-large-files-with-streaming
You already referenced it, but there's some relevant useful information in this answer:
C# HttpClient 4.5 multipart/form-data upload
This explains the details of the content-disposition header and how it is used with multipart form data requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#As_a_header_for_a_multipart_body
As to your specific problem of the file being sent as text instead of bits, since http is text-based, it can only be sent as text, but that text can be encoded as you see fit. Perhaps your StreamContent needs a specific encoding to be used, like base64 encoding or similar? I do believe the newlines are significant in the multipart request, so hopefully setting the encoding for the file content as needed would be enough.
Another possibility: could it be that you need to set additional information on the file section's headers or in the definition of the StreamContent to indicate that it should expect to continue, or that the boundary information isn't put in correctly? See Multipart forms from C# client
I use this lib : https://github.com/jgiacomini/Tiny.RestClient
It's make easier to send multiplart file to send multipart files.
Here a sample :
await client.PostRequest("MultiPart/Test").
AsMultiPartFromDataRequest().
AddStream(stream1, "request", "request2.bin").
AddStream(stream2, "request", "request2.bin")
ExecuteAsync();

How to submit a file to an ASP.NET Core application

I have an ASP.NET application that presents a simple form to upload files (images). That looks like this:
public IActionResult Process()
{
return View();
}
[HttpPost]
public IActionResult Process(List<IFormFile> files)
{
var telemetry = new TelemetryClient();
try
{
var result = files.Count + " file(s) processed " + Environment.NewLine;
foreach (var file in files)
{
result += file.FileName + Environment.NewLine;
var memoryStream = new MemoryStream();
file.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
var binaryReader = new BinaryReader(memoryStream);
var bytes = binaryReader.ReadBytes((int)memoryStream.Length);
var imageInformation = ImageService.ProcessImage(bytes);
ImageService.SaveImage(imageInformation.Result, bytes, file.FileName.Substring(file.FileName.LastIndexOf(".", StringComparison.Ordinal) + 1));
}
return View((object)result);
}
catch (Exception ex)
{
telemetry.TrackException(ex);
throw;
}
}
This form in the application works fine. The problem is that I want to use Microsoft Flow to submit files that come into a SharePoint library over to the web application defined above.
I have the file flow setup and it runs and doesn't error out, but when I look at the body of the HTTP action's result it says 0 files processed and nothing gets done.
The Flow that I have setup is
When a file is created (SharePoint) (this is pointing to a specific document library
Http (Http), Method: Post, Uri (pointing to my app), Body: File Content from the SharePoint step above.
As I mentioned this is posting to the site, but must not be passing in the file in a way that the ASP.NET method can handle, so it is not processing anything. How can I change either the flow or the Post method, so that it will work.
Updated with new information
I have tried this with a very small image, so I can get some additional Request information. Using the form in the browser I tried this and going the following Request Raw result using Fiddler:
POST https://os-gbsphotoretain.azurewebsites.net/Image/Process HTTP/1.1
Host: os-gbsphotoretain.azurewebsites.net
Connection: keep-alive
Content-Length: 924
Pragma: no-cache
Cache-Control: no-cache
Origin: https://os-gbsphotoretain.azurewebsites.net
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySjQVgrsvAqJYXmST
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://os-gbsphotoretain.azurewebsites.net/Image/Process
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: _ga=GA1.3.955734319.1501514097; ai_user=UkqSf|2017-07-31T15:17:38.409Z; ARRAffinity=1628d46398b292eb2e3ba76b4b0f1eb1e30abd9bd1036d7a90b9c51f7baa2306; ai_session=/fPFh|1502738361594.15|1502738361594.15
------WebKitFormBoundarySjQVgrsvAqJYXmST
Content-Disposition: form-data; name="files"; filename="printer.jpg"
Content-Type: image/jpeg
JFIF ` ` C
$.' ",#(7),01444'9=82<.342 C
2!!22222222222222222222222222222222222222222222222222 "
} !1AQa "q2 #B R $3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
w !1AQ aq"2 B #3R br
$4 % &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?
+X K 21 c Z ] ӥg v ; : P I f > m;] ֬u nm ` Q 1 P6 s 9 |b r| G
------WebKitFormBoundarySjQVgrsvAqJYXmST--
Doing the same image through flow I get the following as the body in flow:
{
"$content-type": "image/jpeg",
"$content": "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD1C9EMuqzGK1juS+3P7rccgc4yMYxjv1q/ol0I4bfTpQVniXaoyDuQHjoTg7ccGsDU7O+0+xEdoJfMUKiKE84MB/dJ5B9mzj6VneFtO1271qx1G+hubaGBjmCSUfMSMZZQNoxzgDnPfGKqcnypEJW1R//Z"
}
So it looks like flow is submitting as JSON. I'm going to try some additional processing now as a test, but if anybody knows what I can put in the Web app to handle this I would greatly appreciate it.
I added a new method see below that works when I run it locally passing in the string that Flow says is the body. But when I run it from flow I get value cannot be null error in the DeserializeObject line. How can I get the information that Flow is passing in.
[HttpPost]
public IActionResult ProcessJson(string json)
{
var telemetry = new TelemetryClient();
try
{
var result = "JSON processed " + Environment.NewLine;
var details = (dynamic)Newtonsoft.Json.JsonConvert.DeserializeObject(json);
var content = (string) details["$content"];
var bytes = Convert.FromBase64String(content);
ProcessBytes(bytes, "jpeg");
return View("Process", result);
}
catch (Exception ex)
{
telemetry.TrackException(ex);
throw;
}
}
I have also tried a method with this signature, but no luck there either it comes in as null
[HttpPost]
public IActionResult ProcessJson([FromBody]FlowFile file)
{
...
}
public class FlowFile
{
[JsonProperty(PropertyName = "$content-type")]
public string ContentType { get; set; }
[JsonProperty(PropertyName = "$content")]
public string Content { get; set; }
}
I added some middleware, so that I could get the raw Request.Body and the end result that comes from that is this. I'm not sure what this equates to.
����JFIF``��C 

 $.' ",#(7),01444'9=82<.342��C 

2!!22222222222222222222222222222222222222222222222222��"��
���}!1AQa"q2���#B��R��$3br�
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz���������������������������������������������������������������������������
���w!1AQaq"2�B���� #3R�br�
$4�%�&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz��������������������������������������������������������������������������?�����+X�K�����21�c�Z��]�ӥg�v��;�:����������P����I�f�>���m;]�֬u�nm���`�Q�1�P6�s�9�|b�r|���G�
Well it is a little bit unclear to me how exactly the file is send: is it a json object where the file is converted previously as base64 string or is it a file content? (html headers are the indicators)
If you have json theory you could do:
var parsedFileContent = Newtonsoft.Json.JsonConvert.DeserializeObject<FlowFile>(json);
instead of
var details = (dynamic)Newtonsoft.Json.JsonConvert.DeserializeObject(json);
and it should work, if and only if ;), what you posted is correct
{
"$content-type": "image/jpeg",
"$content": "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD1C9EMuqzGK1juS+3P7rccgc4yMYxjv1q/ol0I4bfTpQVniXaoyDuQHjoTg7ccGsDU7O+0+xEdoJfMUKiKE84MB/dJ5B9mzj6VneFtO1271qx1G+hubaGBjmCSUfMSMZZQNoxzgDnPfGKqcnypEJW1R//Z"
}
just make sure the json is in single line string (make sure there are no hidden chars like \n or similar)
On the other hand, in your Fidler capture you have:
Content-Type: multipart/form-data;
So the correct way to go with IFormFile
So the information provided are a little bit misleading. Can you try and pass a bigger chuck of the error logs? "object reference not sent to an instance of an object error" is very general and usually those kinds of errors are narrowed down with the stack trace.
I finally got this work. What I needed to do was to read the Raw Request Stream directly and that stream is just the image. Everything that Flow was saying about submitting the image in a Base64 encoded JSON string was not correct. I could not get it to bind to any parameters or as a Request.Form.Files, but I could read the stream directly and save the image directly from that.
Instead of having the List<IFormFile> parameter, access the Files property in HttpContext.Request.

HttpRequest.Files is empty when sending through POSTMAN

I'm following this blog post to upload an image using C# Web API.
The article explains how to do it using ARC and it works fine.
But when I'm trying to do the same using POSTMAN it's failing.
Here is my request snapshot.
TL;DR: You are doing everything correct except that you are setting the Content-Type header explicitly in the tool. Get rid of the header and your issue will be resolved.
Detailed Explanation: To begin with, you attach the files using form-data option in the Body tab in the tool:
The moment you select the file(s), Postman auto-detects the Content-Type. Then behind the scenes, Postman inserts the automatically detected Content-Type into the POST request without us knowing it. So setting up Content-Type header explicitly on our own messes up the request.
Setting the value of Content-Type header to multipart/form-data involves a complex concept of setting up boundaries of multiple parts of the file as detailed here. It can be error-prone. So heavy lifting of setting up the boundaries is done automatically for us by the tool itself. This is the reason why it doesn't expect us to set the content-type header explicitly in this case. Please see how I've set only Authorization header while uploading the image file on my PC (Refer screenshot):
Authorization header is an optional header. It is required only if you've setup some sort of authentication on the web server. So if anonymous access is allowed on your website then Headers tab in your case should be empty i.e. no key value pairs at all.
Note: Just for information, the correct content type for image files in a POST request is multipart/form-data even though we don't need to set it explicitly in the tool. Screenshot in the question shows that Content-Type header is being set as application/x-www-form-urlencoded which is not right.
In the post you referrer to the data is being uploaded as "x-www-form-url-encoded"
Your Postman screen shot shows you are uploading it as "form-data"
Additionally, you adding a key "image01" where the ARC example doesn't appear to be sending a key.
If you want to upload the file using form-data you need a different approach:
// POST api/files
public async Task<HttpResponseMessage> Post()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
string value;
try
{
// Read the form data and return an async data.
var result = await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the form data.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
// return multiple value from FormData
if (key == "value")
value = val;
}
}
if (result.FileData.Any())
{
// This illustrates how to get the file names for uploaded files.
foreach (var file in result.FileData)
{
FileInfo fileInfo = new FileInfo(file.LocalFileName);
if (fileInfo.Exists)
{
//do somthing with file
}
}
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, value);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = files.Id }));
return response;
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}

How to use MultipartContent without file?

[HttpPost]
[Route("Profile")]
public async Task<IHttpActionResult> SubmitPhotos()
{
// Check if the request contains multipart/form-data. // shivam agarwal
if (!Request.Content.IsMimeMultipartContent())
{
ModelState.AddModelError("MIME not supported", "MIME not supported");
return BadRequest();
}
string root = HttpContext.Current.Server.MapPath("~/Content/ProfileImages/");
var provider = new MultipartFormDataStreamProvider(root);
try
{
var newResponse = await Request.Content.ReadAsMultipartAsync(provider);
// pleae check here
//provider=new FormDataCollection(Request.Content);
if (provider.FormData.GetValues("UserId").First() == null || provider.FormData.GetValues("UserId").First() == "")
{
return BadRequest("UserId is required");
}
else if (provider.FormData.GetValues("DisplayName").First() == null || provider.FormData.GetValues("DisplayName").First() == "")
{
return BadRequest("DisplayName is required");
}
foreach (MultipartFileData fileData in provider.FileData)
{
// Image saving process
model.ImageKey = keyvalue[0];
// call business function
if (!response.Exceptions.Any())
{
//saving to database
return Ok(resultResponse);
}
}
return Ok();
}
catch (System.Exception e)
{
ModelState.AddModelError("Exception", e.Message);
return BadRequest();
}
finally
{
FlushTempFiles();
}
}
I am using webapi to submit the form-collection values to the database. The main inputs are File(Photo),Displayname,UserID From the mobile application user submits the three inputs it saves the image into the folder and other two values into the database. till now everything is fine.
My problem is: the file input is optional in my case, so the real thing is they may or may not upload the file input (Filedata)
So, I am getting error in this line
var newResponse = await Request.Content.ReadAsMultipartAsync(provider);
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
I have read the answers to add the \r\n to the stream but it is not working in my case. Please tell me your opinion how can I handle the optional file input and just get the values of userid and displayname?
I think you only need to check your mobile client app. If your mobile app is an Android app, then you can refer to the following sample codes:
If using OkHttp:
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
// .addPart(
// Headers.of("Content-Disposition", "form-data; name=\"file\"; filename=\"myfilename.png\""),
// RequestBody.create(MEDIA_TYPE_PNG, bitmapdata))
.build();
final Request request = new Request.Builder()
.url("http://myserver/api/files")
.post(requestBody)
.build();
If using Volley, you can refer to My GitHub sample, there you should pay attention to
// send multipart form data necesssary after file data
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
My debug screen at Web API, you can see FileData.Count is 0:
UPDATE:
IMO, you should also read the following:
Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart
MIME
There, you will find:
The format of a multipart MIME message is easiest to understand by looking at an example request:
POST http://localhost:50460/api/values/1 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------41184676334
Content-Length: 29278
-----------------------------41184676334
Content-Disposition: form-data; name="caption"
Summer vacation
-----------------------------41184676334
Content-Disposition: form-data; name="image1"; filename="GrandCanyon.jpg"
Content-Type: image/jpeg
(Binary data not shown)
-----------------------------41184676334--
So, if your client app did not send the last -----------------------------41184676334--, for example, your Web API will throw Unexpected end of MIME multipart stream. MIME multipart message is not complete.

Uploading file to Web API timeouts on the server

I've been having this problem all day, and i'm pretty lost as to why it happens.
I'm posting a file (a .xls file) from the client side in a React JS component in response to a click on a button like this:
upload: function (e) {
e.preventDefault();
var model = this.state.model;
var xlsFile = this.refs.xlsFile.getDOMNode();
var file = xlsFile.files[0];
var fd = new FormData();
fd.append('file', file);
var request = {
groupId: this.state.vkNumber,
payload: fd
};
model.importRequest = request;
model.setImportProcessing();
MultiOrderActions.updateUsers(request);
this.setState(model.toState());
}
The request as it appears in Chrome (POST-request):
Accept:/
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8,da;q=0.6
Connection:keep-alive
Content-Length:3799243
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryBVYYdSXRza1yRuW4
And the payload of the request:
------WebKitFormBoundaryBVYYdSXRza1yRuW4
Content-Disposition: form-data; name="file"; filename="bruger_caas.xls"
Content-Type: application/vnd.ms-excel
------WebKitFormBoundaryBVYYdSXRza1yRuW4--
Should there not be any binary data in the request? The content length is correct, and so is the content-type and filename.
In WebAPI i'm doing the following, where the ReadAsMultiPartAsync never returns:
[HttpPost]
[Route("bc/s/{*url}")]
public async Task<IHttpActionResult> Post(string url)
{
var service = CreateClient<GenericService>();
if (Request.Content.IsMimeMultipartContent())
{
try
{
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var content in provider.Contents)
{
//Do stuff
;
}
}
catch (Exception e)
{
;
}
}
return InternalServerError();
}
When it reaches the await keyword it just dies, and times out after 2 minutes, because Chrome cancels the request.
What am i doing wrong here? I'm really not to sure if the problem is that the file is never really sent from the client side, or if it's my server side code that's the problem.
Any input would be much appreciated.
for those googling...
I was also troubleshooting timeouts for file uploads.
In my case, I was getting a deadlocked thread when using the MultipartMemoryStreamProvider and ReadAsMultipartAsync
In the end this other post had a solution for me:
Request.Content.ReadAsMultipartAsync never returns
which uses "TaskCreationOptions.LongRunning" to ensure a distinct thread and prevent a deadlock. The comments of that issue have a helpful explanation.

Categories

Resources