覆盖WebHostBufferPolicySelector以进行非缓冲文件上载

时间:2013-02-16 01:40:57

标签: c# file-upload asp.net-web-api

在尝试创建非缓冲文件上传时,我扩展了System.Web.Http.WebHost.WebHostBufferPolicySelector,覆盖了本文所述的函数UseBufferedInputStream():http://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/。当一个文件被POST到我的控制器时,我可以在跟踪输出中看到被覆盖的函数UseBufferedInputStream()肯定会按预期返回FALSE。但是,使用诊断工具,我可以看到内存随着文件上传而增长。

大量内存使用似乎发生在我的自定义MediaTypeFormatter中(类似于FileMediaFormatter:http://lonetechie.com/)。在这个格式化程序中我想逐步将传入的文件写入磁盘,但我还需要解析json并使用Content-Type:multipart / form-data upload执行其他操作。因此我使用HttpContent方法ReadAsMultiPartAsync(),这似乎是内存增长的来源。我已经在“await”之前/之后放置了跟踪输出,并且看起来当任务阻塞时,内存使用量会相当快地增加。

一旦我在ReadAsMultiPartAsync()返回的部分中找到文件内容,我就使用Stream.CopyTo()将文件内容写入磁盘。这按预期写入磁盘,但不幸的是,此时源文件已经在内存中了。

有没有人对可能出现的问题有任何想法?似乎ReadAsMultiPartAsync()正在缓冲整个帖子数据;如果这是真的,为什么我们需要var fileStream = await fileContent.ReadAsStreamAsync()来获取文件内容?是否有另一种方法来完成部件的拆分而不将其读入内存?我的MediaTypeFormatter中的代码如下所示:

// save the stream so we can seek/read again later
Stream stream = await content.ReadAsStreamAsync();  

var parts = await content.ReadAsMultipartAsync(); // <- memory usage grows rapidly

if (!content.IsMimeMultipartContent())
{
    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);               
}

//
// pull data out of parts.Contents, process json, etc.
//

// find the file data in the multipart contents
var fileContent = parts.Contents.FirstOrDefault(
x => x.Headers.ContentDisposition.DispositionType.ToLower().Trim() == "form-data" && 
x.Headers.ContentDisposition.Name.ToLower().Trim() == "\"" + DATA_CONTENT_DISPOSITION_NAME_FILE_CONTENTS + "\"");

// write the file to disk
using (var fileStream = await fileContent.ReadAsStreamAsync())
{
    using (FileStream toDisk = File.OpenWrite("myUploadedFile.bin"))
    {
        ((Stream)fileStream).CopyTo(toDisk);
    }
}

1 个答案:

答案 0 :(得分:11)

WebHostBufferPolicySelector仅指定基础请求是否为无缓存。这就是Web API将在幕后做的事情:

IHostBufferPolicySelector policySelector = _bufferPolicySelector.Value;
bool isInputBuffered = policySelector == null ? true : policySelector.UseBufferedInputStream(httpContextBase);
    Stream inputStream = isInputBuffered
                  ? requestBase.InputStream
          : httpContextBase.ApplicationInstance.Request.GetBufferlessInputStream();

因此,如果您的实现返回false,则请求是无缓冲的。

但是,ReadAsMultipartAsync()会将所有内容加载到MemoryStream中 - 因为如果您未指定提供程序,则默认为MultipartMemoryStreamProvider。

要在处理每个部分时自动将文件保存到磁盘,请使用MultipartFormDataStreamProvider(如果处理文件和表单数据)或MultipartFileStreamProvider(如果只处理文件)。

asp.nethere上有一个示例。在这些例子中,一切都发生在控制器中,但没有理由不在格式化器中使用它。

另一个选择,如果你真的想要使用流,那就是实现一个继承自MultipartStreamProvider的自定义类,它会在抓取部分流时触发你想要的任何处理。用法与上述提供者类似 - 您需要将其传递给ReadAsMultipartAsync(provider)方法。

最后 - 如果你感到有自杀倾向 - 因为理论上基础请求流是无缓冲的,你可以在你的控制器或格式化程序中使用这样的东西:

            Stream stream = HttpContext.Current.Request.GetBufferlessInputStream();
            byte[] b = new byte[32*1024];
            while ((n = stream.Read(b, 0, b.Length)) > 0)
            {
                //do stuff with stream bit
            }

但当然,由于缺乏更好的词语,“贫民窟”。