在 ASP.NET Core 1.0 上处理大文件上传

Posted

技术标签:

【中文标题】在 ASP.NET Core 1.0 上处理大文件上传【英文标题】:Dealing with large file uploads on ASP.NET Core 1.0 【发布时间】:2016-04-05 21:21:09 【问题描述】:

当我在 ASP.NET Core 中将大文件上传到我的 web api 时,运行时会在我的处理和存储上传的函数被触发之前将文件加载到内存中。对于大型上传,这成为一个问题,因为它既慢又需要更多内存。有关如何禁用请求缓冲的 ASP.NET there are some articles 的早期版本,但我无法找到有关如何使用 ASP.NET Core 执行此操作的任何信息。是否可以禁用请求缓冲,这样我的服务器上的内存就不会一直用完?

【问题讨论】:

根据flowjs的api编写我的文件上传后端支持小块上传文件 你好@jltrem,你能分享处理使用flowjs上传的文件的asp.net核心控制器和角度代码吗? 【参考方案1】:

使用Microsoft.AspNetCore.WebUtilities.MultipartReader,因为它...

可以解析任何 [with] 最小缓冲的流。它一次为您提供每个部分的标题和正文,然后您对该部分的正文执行您想要的操作(缓冲、丢弃、写入磁盘等)。

这是一个中间件示例。

app.Use(async (context, next) =>

    if (!IsMultipartContentType(context.Request.ContentType))
    
        await next();
        return;
    

    var boundary = GetBoundary(context.Request.ContentType);
    var reader = new MultipartReader(boundary, context.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    
        // process each image
        const int chunkSize = 1024;
        var buffer = new byte[chunkSize];
        var bytesRead = 0;
        var fileName = GetFileName(section.ContentDisposition);

        using (var stream = new FileStream(fileName, FileMode.Append))
        
            do
            
                bytesRead = await section.Body.ReadAsync(buffer, 0, buffer.Length);
                stream.Write(buffer, 0, bytesRead);

             while (bytesRead > 0);
        

        section = await reader.ReadNextSectionAsync();
    

    context.Response.WriteAsync("Done.");
);

这里是帮手。

private static bool IsMultipartContentType(string contentType)

    return 
        !string.IsNullOrEmpty(contentType) &&
        contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;


private static string GetBoundary(string contentType)

    var elements = contentType.Split(' ');
    var element = elements.Where(entry => entry.StartsWith("boundary=")).First();
    var boundary = element.Substring("boundary=".Length);
    // Remove quotes
    if (boundary.Length >= 2 && boundary[0] == '"' && 
        boundary[boundary.Length - 1] == '"')
    
        boundary = boundary.Substring(1, boundary.Length - 2);
    
    return boundary;


private string GetFileName(string contentDisposition)

    return contentDisposition
        .Split(';')
        .SingleOrDefault(part => part.Contains("filename"))
        .Split('=')
        .Last()
        .Trim('"');

外部参考

https://github.com/aspnet/HttpAbstractions/pull/146 https://github.com/aspnet/HttpAbstractions

【讨论】:

看来此代码在 dnx451 上运行良好,但在 dnxcore50 上存在内存泄漏。可能是正在为 RC2 修复的问题。 GetFileName() 从上面的 sn-p 导致服务器缓冲整个文件。我用随机文件名替换了它,但是在将所有块写入文件后,我发现内存使用量增加了等于文件大小的数量。它实际上可能没问题,如果进一步的 GC 会摆脱它。但是好像不行 @EvAlex GetFileName 只是解析一个字符串。在这之前或之后发生的其他事情是否可能导致服务器缓冲整个文件? @ShaunLuttin 感谢分享代码。这也适用于大于 4 GB 的文件吗? 如何使用这个中间件上传文件?【参考方案2】:

Shaun Luttin 的回答很棒,现在他演示的大部分工作都是由 ASP.Net Core 2.2 提供的。

获取边界:

// Microsoft.AspNetCore.Http.Extensions.HttpRequestMultipartExtensions
var boundary = Request.GetMultipartBoundary();

if (string.IsNullOrWhiteSpace(boundary))
  return BadRequest();

你仍然得到如下部分:

var reader = new MultipartReader(boundary, Request.Body);
var section = await reader.ReadNextSectionAsync();

检查配置并转换为 FileMultipartSection:

if (section.GetContentDispositionHeader())

     var fileSection = section.AsFileSection();
     var fileName = fileSection.FileName;

     using (var stream = new FileStream(fileName, FileMode.Append))
         await fileSection.FileStream.CopyToAsync(stream);

【讨论】:

编辑器在此行标记错误if (section.GetContentDispositionHeader()) 无法将 ContentDepositionHeaderValue 转换为 bool 看起来那里缺少空检查【参考方案3】:

在您的Controller 中,您可以简单地使用Request.Form.Files 访问文件:

[HttpPost("upload")]
public async Task<IActionResult> UploadAsync(CancellationToken cancellationToken)

    if (!Request.HasFormContentType)
        return BadRequest();

    var form = Request.Form;
    foreach(var formFile in form.Files)
    
        using(var readStream = formFile.OpenReadStream())
        
            // Do something with the uploaded file
        
    


    return Ok();

【讨论】:

使用 Request.Form.Files 会导致上传的文件在大于一定大小时保存到磁盘。 ISTR 看到某处提到了 64MB。 我相信它是 64KB。

以上是关于在 ASP.NET Core 1.0 上处理大文件上传的主要内容,如果未能解决你的问题,请参考以下文章

.NET Core & ASP.NET Core 1.0

.NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布

如何在 ASP.NET Core 1.0 中获取 bin 文件夹

ASP.NET 5 改名 ASP.NET Core 1.0

.NET Core 1.0ASP.NET Core 1.0和EF Core 1.0简介

ASP.NET Core 1.0 部署 HTTPS