已上传的' multipart / form-data'到ASP.NET Web API操作,在无缓冲模式下无法读取

时间:2014-09-03 16:16:04

标签: asp.net iis asp.net-web-api

我正在构建一个接受“multipart / form-data”请求的ASP.NET Web API端点。我使用.NET Framework 4.5和Web API 2.1按照in this article所述实现了它。我创建的操作方法的简化版本如下所示:

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

因为上传的文件可能非常大(高达2GiB),所以我希望我的请求不被ASP.NET缓存,从而避免高内存使用。为了实现这一点,我告诉Web API流式传入请求,而不是缓冲它们,如in this article所述。自定义 WebHostBufferPolicySelector 看起来像这样:

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

我在应用程序启动时将这个人加载到Global.asax中,如下所示:

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

好吧,电路板已经设置好,让它们移动。如果我不使用我的 CustomWebHostBufferPolicySelector ,一切正常。但是,当它被使用时,我得到以下异常:

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

具有以下内部异常:

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()"

看起来请求仍以某种方式通过其他方式缓冲。我应该在ASP.NET管道中找到另一个地方吗?甚至IIS也许?此请求的生命周期中可以缓冲的其他位置是什么,以及如何控制它们?

1 个答案:

答案 0 :(得分:5)

为了使问题更清晰,可与他人共享,我创建了一个简单的项目来尝试重现问题。在这样做时,我找到了答案:禁用所有类型的跟踪。

在我的情况下,我启用了ASP.NET's own tracing functionality,还有Glimpse。这两个缓冲区都会在请求到达Web API操作之前对其进行缓冲。

为了完整性&#39;这里,在测试和生产过程中,这是在Web.Config中关闭它们的正确方法。

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

在我的情况下,这两个人是罪魁祸首,但我可以想象可能还有其他人,所以要小心这一点。