上传文件到服务器时出现内存不足异常

15

我正试图使用ASP.NET Web API实现文件上传系统,但遇到了一个问题。我试图将多部分表单数据放入内存流中,以便根据服务层实现将其写入磁盘或Blob存储。但问题在于,对于小文件它可以正常运作,但我正在尝试上传291 MB的文件,结果出现了内存不足异常。以下是代码:

if (!Request.Content.IsMimeMultipartContent())
{
    Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, "Request must be multipart.");
}

var provider = new MultipartMemoryStreamProvider();

try
{
    await Request.Content.ReadAsMultipartAsync(provider);

    var infoPart = provider.Contents.Where(x => x.Headers.ContentDisposition.Name.Replace("\"", string.Empty) == "fileInfo").SingleOrDefault();
    var filePart = provider.Contents.Where(x => x.Headers.ContentDisposition.Name.Replace("\"", string.Empty) == "filePart" && x.Headers.ContentDisposition.FileName != null).Single();
    byte[] file = null;

    using (Stream stream = filePart.ReadAsStreamAsync().Result)
    {
        using (MemoryStream memory = new MemoryStream())
        {
            stream.CopyTo(memory);
            file = memory.ToArray();
        }
    }

    string fileContentType = filePart.Headers.ContentType.MediaType;

    FileDto result = _fileService.AddFileToResource(Variables);
    string uri = Url.Link("DefaultGet", new { id = result.ID });
    return Request.CreateResponse(HttpStatusCode.OK);

引起错误的部分在

await Request.Content.ReadAsMultipartAsync(provider);

确切的错误是:

将MIME多部分正文部分写入输出流时发生错误。

并且有一个内部异常:

引发了类型为 'System.OutOfMemoryException' 的异常。

我尝试过创建自定义的 BufferPolicySelector,就像这篇帖子中第二个答案所示以及其他许多地方,但这似乎毫无帮助。

我还在我的 web.config 文件中添加了以下内容:

<httpRuntime targetFramework="4.5" maxRequestLength="307200"/>

<security>
  <requestFiltering>
    <requestLimits maxAllowedContentLength="367001600"/>
  </requestFiltering>
</security>

1
我可能理解有误,但根据我对你的代码的理解,在这段代码中,你将在内存中拥有3个该文件的副本:请求内容流、内存流以及字节数组文件。也许请求结果流确实是流式传输的,但“内存”和“文件”都将同时存在于RAM中。 - dkackman
谢谢你的建议。我该如何改进呢?这并没有帮助到我的问题,因为它在读取 await Request.Content.ReadAsMultipartAsync(provider); 中的内容时会抛出错误,但我肯定想了解如何使它尽可能高效。 - GBreen12
请验证maxRequestLength是否大于或等于分配给RequestLengthDiskThreshold的值。 - fnostro
是的,我做了,但是没有成功。 - GBreen12
@ gmoney12 很难为您重构此代码,因为很难确定您在其中确切地做了什么。 'stream' 和 'file' 似乎没有被使用,因此据我所见,整个代码块都可以删除。 - dkackman
你不应该使用 using (Stream stream = filePart.ReadAsStreamAsync().Result),而是需要使用 using (Stream stream = await filePart.ReadAsStreamAsync())。如果你使用第一种方式,可能会导致服务器死锁。在声明为 async 的函数中,几乎永远不应该有 .Result.Wait() - Scott Chamberlain
3个回答

9

一个解决方案是使用MultipartFormDataStreamProvider而不是MultipartMemoryStreamProvider,以避免调用时出现内存不足异常。

Request.Content.ReadAsMultipartAsync(..)

我在使用MemoryStreamProvider读取大于100 MB的文件的MultiPart文件内容时,遇到了类似的问题。对我有用的解决方法是使用MultipartFormDataStreamProvider。在ReadAsMultipartAsync调用期间,文件被写入磁盘,并且如果需要在内存中加载,可以稍后重新加载。
以下是一个示例,摘自:在Web API中发送HTML表单数据:文件上传和多部分MIME
    string root = HttpContext.Current.Server.MapPath("~/App_Data");
    var provider = new MultipartFormDataStreamProvider(root);

    try
    {
        // Read the form data.
        await Request.Content.ReadAsMultipartAsync(provider);

        // This illustrates how to get the file names.
        foreach (MultipartFileData file in provider.FileData)
        {
            Trace.WriteLine(file.Headers.ContentDisposition.FileName);
            Trace.WriteLine("Server file path: " + file.LocalFileName);
        }
        return Request.CreateResponse(HttpStatusCode.OK);
    }
    catch(...)

1
这就是我最终做的事情。不确定它是否是最好的方法,但我从来没有想出如何让它以其他方式工作。 - GBreen12
该URL不再存在。 - Sunny Drall
有没有办法在asp.net core中实现这个?我只有Request.Body而没有Request.Content,也没有ReadAsMultipartAsync方法。 - Christopher Painter

0

请检查您的Web配置文件以获取文件上传限制。

<system.webServer>
        <security>
            <requestFiltering>
                <requestLimits maxAllowedContentLength="524288000"/>
            </requestFiltering>
        </security>
</system.webServer>

来自 OP 的编辑:

<httpRuntime targetFramework="4.5" maxRequestLength="307200"/>

maxRequestLength表示缓冲阈值,单位为千字节(Kb),您正在请求一个300MB的缓冲区,这可能不太好。请将其设置得更低。


1
我已经在web.config文件中有了那个,我会编辑以添加那些信息。 - GBreen12
请注意,上面的值以字节为单位,最大值为UINT,约为4GB。上面的值约为500MB。 - fnostro
没错,但我只上传了一个290MB的文件,所以它应该可以正常上传,对吧?或者我的想法有误? - GBreen12
这就是为什么我不明白为什么它不起作用,因为我有一个300 MB的最大内容长度,甚至尝试将其设置为像您一样的500 MB,但仍然会抛出内存越界的错误。 - GBreen12
这只是请求内容大小限制的设置,但您的问题是您的进程已经耗尽了内存。 - Matt
听起来好像是尝试将整个文件塞进内存流中 - 也许需要确认上传文件缓冲区是否被正确刷新。 - fnostro

-1

验证服务器上文件夹的权限。

或者

例如,在web.config中放置httpRuntime maxRequestLength:

<httpRuntime maxRequestLength="5242880" />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接