Loaded "multipart / form-data" in ASP.NET Web API activity that cannot be read in no buffer mode

I am creating an ASP.NET Web API endpoint that accepts "multipart / form-data" requests. I implemented it as described in this article using .NET Framework 4.5 and Web API 2.1. A simplified version of the action method I created looks like this:

public async Task<HttpResponseMessage> PostFile()
{
    if (!Request.Content.IsMimeMultipartContent()) throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    var rootPath = System.Configuration.ConfigurationManager.AppSettings["StorageLocation"].ToString();
    var provider = new MultipartFormDataStreamProvider(rootPath);
    var response = Request.CreateResponse(HttpStatusCode.OK);
    try
    {
        await Request.Content.ReadAsMultipartAsync(provider);
        // Imagine awesome logic here, unicorns and rainbows! Instead of that, we do the following:
        response.Content = new StringContent("You uploaded " + provider.FileData.Count.ToString() + " files.");
    }
    catch (Exception e) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e)); }
    return response;
}

      

Since the uploaded files can be very large (up to 2GiB), I want my requests not to be buffered by ASP.NET, thus avoiding high memory usage. To implement this, I told the Web API to stream incoming requests instead of buffering them as described in this article . A custom WebHostBufferPolicySelector looks something like this:

public class CustomWebHostBufferPolicySelector : WebHostBufferPolicySelector
{
    public override bool UseBufferedInputStream(object hostContext)
    {
        System.Web.HttpContextBase contextBase = hostContext as System.Web.HttpContextBase;
        if (contextBase != null && contextBase.Request.ContentType != null && contextBase.Request.ContentType.Contains("multipart")) return false;
        else return base.UseBufferedInputStream(hostContext);
    }

    public override bool UseBufferedOutputStream(System.Net.Http.HttpResponseMessage response)
    {
        return base.UseBufferedOutputStream(response);
    }
}

      

I am loading this guy into Global.asax when starting the application, for example:

protected void Application_Start(object sender, EventArgs e)
{
    // Here, other stuff got did.
    GlobalConfiguration.Configuration.Services.Replace(typeof(IHostBufferPolicySelector), new CustomWebHostBufferPolicySelector());
}

      

Okay, the board is set up, lets you move the pieces. If I don't use my CustomWebHostBufferPolicySelector everything works fine. However, when it is used, I get the following exception:

Message: "An error has occurred."
ExceptionMessage: "Error reading MIME multipart body part."
ExceptionType: "System.IO.IOException"
StackTrace: " at System.Net.Http.HttpContentMultipartExtensions.<ReadAsMultipartAsync>d__0`1.MoveNext()\ \ --- End of stack trace from previous location where exception was thrown ---\ \ at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\ \ at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\ \ at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\ \ at ..."

      

With the following internal exception:

Message: "An error has occurred."
ExceptionMessage: "Unable to read the entity body in Bufferless mode. The request stream has already been buffered."
ExceptionType: "System.InvalidOperationException"
StackTrace: " at System.Web.Http.WebHost.HttpControllerHandler.<>c__DisplayClass13.<GetStreamContent>b__10()\ \ at System.Web.Http.WebHost.HttpControllerHandler.LazyStreamContent.get_StreamContent()\ \ at System.Web.Http.WebHost.HttpControllerHandler.LazyStreamContent.CreateContentReadStreamAsync()\ \ at System.Net.Http.HttpContent.ReadAsStreamAsync()\ \ at System.Net.Http.HttpContentMultipartExtensions.<ReadAsMultipartAsync>d__0`1.MoveNext()"

      

It looks like the request is still buffered in some way, something else. Is there a place in the ASP.NET pipeline that I should be looking for? Or even IIS? What are the other places in this request lifecycle where it can be buffered and how can I control them?

+3


source to share


1 answer


In an attempt to make the problem clearer and more accessible to others, I created a simple project to try to reproduce the problem. By doing this, I found the answer: disable all tracing.

In my case, I have enabled my own ASP.NET tracing feature as well as Glimpse . Both of these buffers will be requested before they arrive at the web API action.

For completeness, here's the correct way to disable them in your Web.Config during testing and production.



<configuration>
  <system.web>
    <trace enabled="false" />
  </system.web>
  <glimpse defaultRuntimePolicy="Off">
  </glimpse>
</configuration>

      

In my case, these two were the culprit, but I can imagine there might be others, so be careful.

+5


source







All Articles