20

How would i go about using MultipartFormDataStreamProvider and Request.Content.ReadAsMultipartAsync in a ApiController?

I have googled a few tutorials but I can't get any of them to work, I'm using .net 4.5.

Code:

public class TestController : ApiController
{
    const string StoragePath = @"T:\WebApiTest";
    public async void Post()
    {
        if (Request.Content.IsMimeMultipartContent())
        {
            var streamProvider = new MultipartFormDataStreamProvider(Path.Combine(StoragePath, "Upload"));
            await Request.Content.ReadAsMultipartAsync(streamProvider);
            foreach (MultipartFileData fileData in streamProvider.FileData)
            {
                if (string.IsNullOrEmpty(fileData.Headers.ContentDisposition.FileName))
                    throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
                    
                string fileName = fileData.Headers.ContentDisposition.FileName;
                
                if (fileName.StartsWith("\"") && fileName.EndsWith("\""))
                    fileName = fileName.Trim('"');
                    
                if (fileName.Contains(@"/") || fileName.Contains(@"\"))
                    fileName = Path.GetFileName(fileName);
                    
                File.Copy(fileData.LocalFileName, Path.Combine(StoragePath, fileName));
            }
        }
        else
        {
            throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
        }
     }
}

I get the exception

Unexpected end of MIME multipart stream. MIME multipart message is not complete.

when the await task; runs. Does anyone know what I am doing wrong or have a working example in asp.net?

spaleet
  • 838
  • 2
  • 10
  • 23
Peter
  • 37,042
  • 39
  • 142
  • 198
  • If `t.IsFaulted` is true it means there was an exception, and it will be populated in the `Exception` property. See what the exception was. Alternatively just `await task;` to simplify the code and, among other things, it will re-throw any exceptions. – Servy Dec 05 '12 at 21:39
  • after replacing the ContinueWith and the if sentence after with "await task;" i get "Unexpected end of MIME multipart stream. MIME multipart message is not complete." – Peter Dec 05 '12 at 22:25
  • I think the following post may help http://stackoverflow.com/questions/17177237/webapi-ajax-formdata-upload-with-extra-parameters – user2880706 Oct 15 '13 at 04:25

2 Answers2

36

I resolved the error, i don't understand what this has to do with end of multipart stream but here is the working code:

public class TestController : ApiController
{
    const string StoragePath = @"T:\WebApiTest";
    public async Task<HttpResponseMessage> Post()
    {
        if (Request.Content.IsMimeMultipartContent())
        {
            var streamProvider = new MultipartFormDataStreamProvider(Path.Combine(StoragePath, "Upload"));
            await Request.Content.ReadAsMultipartAsync(streamProvider);
            foreach (MultipartFileData fileData in streamProvider.FileData)
            {
                if (string.IsNullOrEmpty(fileData.Headers.ContentDisposition.FileName))
                {
                    return Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted");
                }
                string fileName = fileData.Headers.ContentDisposition.FileName;
                if (fileName.StartsWith("\"") && fileName.EndsWith("\""))
                {
                    fileName = fileName.Trim('"');
                }
                if (fileName.Contains(@"/") || fileName.Contains(@"\"))
                {
                    fileName = Path.GetFileName(fileName);
                }
                File.Move(fileData.LocalFileName, Path.Combine(StoragePath, fileName));
            }
            return Request.CreateResponse(HttpStatusCode.OK);
        }
        else
        {
            return Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted");
        }
    }
}
Peter
  • 37,042
  • 39
  • 142
  • 198
  • 2
    Can we see an example of how this Post method is called by a client? – B. Clay Shannon-B. Crow Raven Feb 25 '14 at 17:52
  • I get the following error at the line starts with `await`: "Either BinaryRead, Form, Files, or InputStream was accessed before the internal storage was filled by the caller of HttpRequest.GetBufferedInputStream." Do you have any suggestions about what might be the cause? – ciuncan Jul 24 '14 at 11:31
  • @ciuncan check the answer for http://stackoverflow.com/questions/17602845/post-error-either-binaryread-form-files-or-inputstream-was-accessed-before-t it might help you! – Peter Jul 24 '14 at 12:40
  • 1
    @Peter thanks, I saw that post, and couldn't see any suggestions for solution but rather what could reasons be. They also mention something wrong with Web API implementation, but it is a one year old post. I upgraded Web API libraries to edge version (currently 5.2.0). Actually my code is exactly the same as yours, and I also checked the tutorial code in asp.net site as well: http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2 . I can't see what else is accessing the underlying `InputStream` etc. other than `ReadAsMultipartAsync` method. – ciuncan Jul 24 '14 at 13:52
  • @ciuncan im sorry i have no idea whats wrong, im not even using this code any more so i can't say if its in working condition or not.. – Peter Jul 24 '14 at 18:50
  • @Peter thanks anyways, I appreciate your response. I don't use it anymore either, i replaced the action argument with `IEnumerable`, and I could get it work. :D – ciuncan Jul 24 '14 at 20:18
  • What is the different? What did you change? the path combine? – Dvir Dec 10 '14 at 11:09
  • 2
    The problem with the original code was that the API didn't (and had no way to) know when the `async void` method finishes. That's why it managed to dispose of the stream on your way to read it. With `Task<>`-returning method, the calling code has a chance to know when you are ready with the request, and closes the streams when you really don't need them any more. Every time you see `async void` you should feel bad. – Vlad Nov 09 '15 at 15:46
  • for users just arriving, code in this answer indeed works as-is.. copy/paste – bkwdesign Dec 14 '17 at 18:51
7

At first, you should define enctype to multipart/form-data in ajax request header.

[Route("{bulkRequestId:int:min(1)}/Permissions")]
[ResponseType(typeof(IEnumerable<Pair>))]
public async Task<IHttpActionResult> PutCertificatesAsync(int bulkRequestId)
{
    if (Request.Content.IsMimeMultipartContent("form-data"))
    {
        string uploadPath = HttpContext.Current.Server.MapPath("~/uploads");

        var streamProvider = new MyStreamProvider(uploadPath);

        await Request.Content.ReadAsMultipartAsync(streamProvider);

        List<Pair> messages = new List<Pair>();
        
        foreach (var file in streamProvider.FileData)
        {
            FileInfo fi = new FileInfo(file.LocalFileName);
            messages.Add(new Pair(fi.FullName, Guid.NewGuid()));
        }

        //if (_biz.SetCertificates(bulkRequestId, fileNames))
        //{
            return Ok(messages);
        //}
        //return NotFound();
    }
    return BadRequest();
}

public class MyStreamProvider : MultipartFormDataStreamProvider
{
    public MyStreamProvider(string uploadPath) : base(uploadPath)
    {
    }
    public override string GetLocalFileName(HttpContentHeaders headers)
    {
        string fileName = Guid.NewGuid().ToString() + Path.GetExtension(headers.ContentDisposition.FileName.Replace("\"", string.Empty));
        return fileName;
    }
}
spaleet
  • 838
  • 2
  • 10
  • 23
Hamid
  • 962
  • 3
  • 9
  • 21