How to use MultipartContent without file? - c#

[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.

Related

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.

.NET Core forward a local API form-data post request to remote API

I have an AJAX form which post a form-data to a local API url: /api/document. It contains a file and a custom Id. We simply want to take the exact received Request and forward it to a remote API at example.com:8000/document/upload.
Is there a simple way of achieve this "forward" (or proxy?) of the Request to a remote API using Asp.NET Core?
Below we had the idea to simply use Web API Http client to get the request and then resend it (by doing so we want to be able to for example append a private api key from the backend), but it seems not to work properly, the PostAsync doesn't accept the Request.
Raw request sent by Ajax
POST http://localhost:62640/api/document HTTP/1.1
Host: localhost:62640
Connection: keep-alive
Content-Length: 77424
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn1BS5IFplQcUklyt
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fr;q=0.6
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="fileToUpload"; filename="test-document.pdf"
Content-Type: application/pdf
...
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="id"
someid
------WebKitFormBoundaryn1BS5IFplQcUklyt--
Backend Code
Our .NET Core backend has a simple "forward to another API" purpose.
public class DocumentUploadResult
{
public int errorCode;
public string docId;
}
[Route("api/[controller]")]
public class DocumentController : Controller
{
// POST api/document
[HttpPost]
public async Task<DocumentUploadResult> Post()
{
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
HttpResponseMessage response = await client.PostAsync("/document/upload", Request.Form);
if (response.IsSuccessStatusCode)
{
retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
}
return retValue;
}
}
We have a GET request (not reproduced here) which works just fine. As it doesn't have to fetch data from locally POSTed data.
My question
How to simply pass the incoming local HttpPost request and forwarding it to the remote API?
I searched A LOT on stackoverflow or on the web but all are old resources talking about forwarding Request.Content to the remote.
But on Asp.NET Core 1.0, we don't have access to Content. We only are able to retrieve Request.Form (nor Request.Body) which is then not accepted as an argument of PostAsync method:
Cannot convert from Microsoft.AspNetCore.Http.IformCollection to
System.Net.Http.HttpContent
I had the idea to directly pass the request to the postAsync:
Cannot convert from Microsoft.AspNetCore.Http.HttpRequest to
System.Net.Http.HttpContent
I don't know how to rebuild expected HttpContent from the local request I receive.
Expected response
For information, When we post a valid form-data with the custom Id and the uploaded file, the remote (example.com) API response is:
{
"errorCode": 0
"docId": "585846a1afe8ad12e46a4e60"
}
Ok first create a view model to hold form information. Since file upload is involved, include IFormFile in the model.
public class FormData {
public int id { get; set; }
public IFormFile fileToUpload { get; set; }
}
The model binder should pick up the types and populate the model with the incoming data.
Update controller action to accept the model and proxy the data forward by copying content to new request.
[Route("api/[controller]")]
public class DocumentController : Controller {
// POST api/document
[HttpPost]
public async Task<IActionResult> Post(FormData formData) {
if(formData != null && ModelState.IsValid) {
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
var multiContent = new MultipartFormDataContent();
var file = formData.fileToUpload;
if(file != null) {
var fileStreamContent = new StreamContent(file.OpenReadStream());
multiContent.Add(fileStreamContent, "fileToUpload", file.FileName);
}
multiContent.Add(new StringContent(formData.id.ToString()), "id");
var response = await client.PostAsync("/document/upload", multiContent);
if (response.IsSuccessStatusCode) {
var retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
return Ok(reyValue);
}
}
//if we get this far something Failed.
return BadRequest();
}
}
You can include the necessary exception handlers as needed but this is a minimal example of how to pass the form data forward.

Web API: multipart/form-data post not working

I've read every internet post on this and I still don't know what I'm doing wrong. From Fiddler, I'm trying to post multiple items and it's not working. I'm not able to post a .jpeg, but a .txt is no working. First requests to the method are successful, but all subsequent requests aren't and will generate "Unexpected end of MIME multipart stream. MIME multipart message is not complete" or "An asynchronous module or handler completed while an asynchronous operation was still pending". If I just send text entries in the parts/boundaries, it will only see the first one - the others are ignored
Web API
[HttpPost]
[MimeMultipart]
public async Task<IHttpActionResult> PostAsync()
{
var uploadPath = HttpContext.Current.Server.MapPath("~/Uploads");
Directory.CreateDirectory(uploadPath);
var provider = new MultipartFormDataStreamProvider(uploadPath);
// THIS IS WHERE THE ERRORS HAPPEN
await Request.Content.ReadAsMultipartAsync(provider);
// NEVER HAS A NAME(S),, EVEN WHEN NO ERROR
var localFileName = provider.FileData.Select(multiPartData => multiPartData.LocalFileName).FirstOrDefault();
foreach (var stream in provider.Contents)
{
var fileBytes = stream.ReadAsByteArrayAsync();
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent("Successful upload", Encoding.UTF8, "text/plain");
response.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue(#"text/html");
}
// omitted code
Fidder Headers
Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
User-Agent: Fiddler
Host: localhost:1703
Content-Length: 3199
Fiddler Body
---------------------------acebdf13572468
Content-Disposition: form-data; name="description"
Content-Type: text
the_text_is_here
---------------------------acebdf13572468
Content-Disposition: form-data; name="fieldNameHere"; filename="today.txt"
Content-Type: text/plain
<#INCLUDE *C:\Users\loser\Desktop\today.txt*#>
---------------------------acebdf13572468-
Should I use a different content-type? I'm dying over here.

WebApi can't read chunked request

I am writing a web service that will be used to consume some data. The 3rd party that is sending it is using a multipart request and when I look at the request in WireShark, it is chunked.
When I attempt to run the exact same request through fiddler as a standard (unchunked) request, it works fine, however, I can't seem to read the chunked request.
I've tried 2 different approaches. First:
public HttpResponseMessage ImportEstimate(HttpRequestMessage request)
{
var data = request.Content.ReadAsMultipartAsync().Result;
IEnumerable<HttpContent> parts = data.Contents;
return request.CreateResponse(HttpStatusCode.OK, parts.Count());
}
This doesn't return anything. It just sits until the request times out.
The second approach is to do:
public HttpResponseMessage ImportEstimate(HttpRequestMessage request)
{
IEnumerable<HttpContent> parts = null;
Task.Factory
.StartNew(() => parts = Request.Content.ReadAsMultipartAsync().Result.Contents,
CancellationToken.None,
TaskCreationOptions.LongRunning, // guarantees separate thread
TaskScheduler.Default)
.Wait();
return request.CreateResponse(HttpStatusCode.OK, parts.Count());
}
Which returns an error:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
What am I missing here?
Edited: Here is the request from WireShark
POST /myservice/importestimate HTTP/1.1
Host: devapi.mydomain.com
Content-Type: multipart/related; type="application/xop+xml"; start="<start.xml>"; start-info="text/xml"; boundary="--MIME_boundary"
Transfer-Encoding: chunked
SOAPAction: "importestimate"
X-Forwarded-For: xxx.xx.xxx.xxx
Connection: close
94
----MIME_boundary
Content-Type: application/xop+xml; type="text/xml; charset=UTF-8"
Content-Transfer-Encoding: 8bit
Content-Id: <start.xml>
170
<?xml version='1.0' encoding='UTF-8' ?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xop="http://www.w3.org/2004/08/xop/include" ><soap:Body><XDOC><XNET_INFO transactionId="001P92V" ><ATTACHMENTS><ATTACHMENT><xop:Include href="cid:3203#xactware.com" /></ATTACHMENT></ATTACHMENTS></XNET_INFO></XDOC></soap:Body></soap:Envelope>
B3
----MIME_boundary
Content-Type: application/zip
Content-Transfer-Encoding: binary
Content-Disposition: attachment; filename="XDOC.ZIP"
Content-Id: <3203#x.com>
5BC
... lots of data here removed for brevity...
15
----MIME_boundary--
0
After much research, I've figured out the problem!
Apparently my request did not end with a CRLF, which .Net must require to signal the end of the request.
I ended up reading the entire request, adding on a CRLF, and creating my own ReadAsMultipartAsync from MemoryStream. This seems to work.
Edited to add code:
private byte[] ProcessInput(HttpRequestMessage request)
{
List<HttpContent> parts = new List<HttpContent>();
byte[] result;
result = request.Content.ReadAsByteArrayAsync().Result;
// Stupid chunked requests remove the final CRLF, which makes .Net puke on the request.
// So add our own CRLF.
List<byte> crlfTemp = result.ToList();
crlfTemp.Add(0x0D);
crlfTemp.Add(0x0A);
result = crlfTemp.ToArray();
// Convert stream to MIME
using (var stream = new MemoryStream(result))
{
// note: StreamContent has no Content-Type set by default
// set a suitable Content-Type for ReadAsMultipartAsync()
var content = new StreamContent(stream);
content.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse(
"multipart/related; boundary=--MIME_boundary");
content.Headers.ContentLength = result.Length;
bool isMPC = content.IsMimeMultipartContent();
Task.Factory
.StartNew(() => parts = content.ReadAsMultipartAsync().Result.Contents.ToList(),
CancellationToken.None,
TaskCreationOptions.LongRunning, // guarantees separate thread
TaskScheduler.Default)
.Wait();
}
}

WebAPI cannot parse multipart/form-data post

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;
}

Categories

Resources