Multipart form POST using ASP.Net Web API

2019-01-23 07:12发布

问题:

I have a POST ASP.Net Web Api method adapted from A guide to asynchronous file uploads in ASP.NET Web API RTM.

I am running into faulted Task problem with all the requests fired after the first request is fired and complete.

Here’s the scenario: I have a sample page that posts a file along with other parameters to the Web API Post method. It works fine the first time and the file is uploaded. But, all subsequent requests end up the task in a faulted state. I get “Unexpected end of MIME multipart stream. MIME multipart message is not complete.” Any ideas why?

Pasted below is the source code of my Post method, sample html form and the Aggregate Exception.

    public Task<HttpResponseMessage> Post([FromUri]string memberNumber)
    {
        // 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);

        // Read the form data and return an async task.
        var task = Request.Content.ReadAsMultipartAsync(provider).
            ContinueWith(t =>
            {
                if (t.IsFaulted || t.IsCanceled)
                {
                    throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception));
                }

                return Request.CreateResponse(HttpStatusCode.OK, new MyModel());
            });

        return task;
    }

I am firing this web api using a sample form like this:

<form name="form1" method="post" enctype="multipart/form-data" action="api/claims/asd123" style="margin:auto;width:500px;">
    <div>
        <label for="HCPracticeNumber">HC Pratice Number:</label>
        <input type="text" name="HCPracticeNumber" id="HCPracticeNumber"/>
    </div>
    <div>
        <label for="ServiceDate">Service/Treatment date:</label>
        <input type="text" name="ServiceDate" id="ServiceDate"/>
    </div>
    <div>
        <label for="AmountClaimed">Amount Claimed:</label>
        <input type="text" name="AmountClaimed" id="AmountClaimed"/>
    </div>
    <div>
        <label for="Image">Image Attachment:</label>
        <input name="Image" type="file" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

The AggregateException that is returned is as follows:

<Error>
<Message>An error has occurred.</Message>
    <ExceptionMessage>One or more errors occurred.</ExceptionMessage>
    <ExceptionType>System.AggregateException</ExceptionType>
    <StackTrace/>
    <InnerException>
        <Message>An error has occurred.</Message>
        <ExceptionMessage>
            Unexpected end of MIME multipart stream. MIME multipart message is not complete.
        </ExceptionMessage>
        <ExceptionType>System.IO.IOException</ExceptionType>
        <StackTrace>
            at System.Net.Http.Formatting.Parsers.MimeMultipartBodyPartParser.<ParseBuffer>d__0.MoveNext() at System.Net.Http.HttpContentMultipartExtensions.MoveNextPart(MultipartAsyncContext context)
        </StackTrace>
    </InnerException>
</Error>

Update:

After Filip's suggestion on his blog site, I modified the post method to reset the stream position to 0 like this:

        Stream reqStream = Request.Content.ReadAsStreamAsync().Result;
        if (reqStream.CanSeek)
        {
            reqStream.Position = 0;
        }
        var task = Request.Content.ReadAsMultipartAsync(provider).
            ContinueWith(t =>
            {
                if (t.IsFaulted || t.IsCanceled)
                {
                    throw new HttpResponseException(
                    Request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                    t.Exception));
                }

                return Request.CreateResponse(HttpStatusCode.OK, new MyModel());

            });

But, this is very temperamental code. It works sometimes it doesn't other times. In other words, it doesn't fix the problem fully.

回答1:

It turns out, as Filip suggested in the comments, that the Web API Usage Handler that I adapted from Implementing Message Handlers To Track Your ASP .net Web API Usage was reading the content body and hence messing with the position seeker on the stream when the request was being processed in my POST method.

So, I added a conditional statement to the WebApiUsageHandler to not read the request body if the request is of type IsMimeMultipartContent. This fixed the problem.

Update

I want to update the answer with another option, suggested to me by Filip via email, so that it is documented:

If you use this code inside the API usage handler, just before reading the body:

   //read content into a buffer
   request.Content.LoadIntoBufferAsync().Wait();

   request.Content.ReadAsStringAsync().ContinueWith(t =>
   {
       apiRequest.Content = t.Result;
       _repo.Add(apiRequest);
   });

The request will be buffered and will be possible to read it twice, and therefore the upload will be possible further down the pipeline. Hope this helps.



回答2:

This is not an answer to the original poster's question. However, calling the ReadAsMultipartAsync() method more than one time in my codes also caused the same exception:

public async Task<IHttpActionResult> PostFiles()
{

     // Check if the request contains multipart/form-data.
     if (!Request.Content.IsMimeMultipartContent())
     {
         return Content(HttpStatusCode.BadRequest, "Unsupported media type. ";
    }
     try
     {
        var provider = new CustomMultipartFormDataStreamProvider(workingFolder);

        await Request.Content.ReadAsMultipartAsync(provider); // OK
        await Request.Content.ReadAsMultipartAsync(provider); // calling it the second time causes runtime exception "Unexpected end of MIME multipart stream. MIME multipart message is not complete"
        ... 

    }
    catch(Exception ex)
    {
        ...
    }
}