在 ASP.NET Core 3.1 中上传和下载大文件?

Posted

技术标签:

【中文标题】在 ASP.NET Core 3.1 中上传和下载大文件?【英文标题】:Uploading and Downloading large files in ASP.NET Core 3.1? 【发布时间】:2022-02-01 22:36:33 【问题描述】:

我正在开发一个使用干净架构的 ASP.NET Core 3.1 API 项目,并且我有以下类库(层):

基础设施(安全材料和上传助手等...) 持久性(DA 层) 域(域模型) 应用程序(用例 - 业务逻辑) API(API项目作为我的启动项目)

我希望能够将大文件上传到服务器(例如 2Gb 甚至更大的文件大小)并在此之后下载它们,并且希望这样做以后不会出现内存问题溢出和一切。

任何帮助将不胜感激。

【问题讨论】:

文件流 FTW。 【参考方案1】:

如果您有这么大的文件,切勿在代码中使用byte[]MemoryStream。仅当您下载/上传文件时才对流进行操作。

你有几个选择:

如果您同时控制客户端和服务器,请考虑使用tus 之类的东西。 .NET 有客户端实现和服务器实现。这可能是最简单、最可靠的选择。 如果您使用 HttpClient 上传大文件,只需使用 StreamContent 类发送它们。同样,不要使用 MemoryStream 作为源,而应使用 FileStream 之类的其他东西。 如果您使用 HttpClient 下载大文件,请务必指定 HttpCompletionOptions,例如 var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead)。否则,HttpClient 会将整个响应缓冲在内存中。然后,您可以通过var stream = response.Content.ReadAsStreamAsync() 将响应文件作为流处理。

ASP.NET Core 具体建议:

如果你想通过HTTP POST接收文件,你需要增加请求大小限制:[RequestSizeLimit(10L * 1024L * 1024L * 1024L)][RequestFormLimits(MultipartBodyLengthLimit = 10L * 1024L * 1024L * 1024L)]。另外,需要禁用表单值绑定,否则整个请求会被缓存到内存中:
   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
   public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
   
       public void OnResourceExecuting(ResourceExecutingContext context)
       
           var factories = context.ValueProviderFactories;
           factories.RemoveType<FormValueProviderFactory>();
           factories.RemoveType<FormFileValueProviderFactory>();
           factories.RemoveType<JQueryFormValueProviderFactory>();
       

       public void OnResourceExecuted(ResourceExecutedContext context)
       
       
   
要从控制器返回文件,只需通过File 方法返回它,该方法接受一个流:return File(stream, mimeType, fileName);

示例控制器如下所示(请参阅 https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1 了解缺少的辅助类):

private const MaxFileSize = 10L * 1024L * 1024L * 1024L; // 10GB, adjust to your need

[DisableFormValueModelBinding]
[RequestSizeLimit(MaxFileSize)]
[RequestFormLimits(MultipartBodyLengthLimit = MaxFileSize)]
public async Task ReceiveFile()

    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
        throw new BadRequestException("Not a multipart request");

    var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType));
    var reader = new MultipartReader(boundary, Request.Body);

    // note: this is for a single file, you could also process multiple files
    var section = await reader.ReadNextSectionAsync();

    if (section == null)
        throw new BadRequestException("No sections in multipart defined");

    if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
        throw new BadRequestException("No content disposition in multipart defined");

    var fileName = contentDisposition.FileNameStar.ToString();
    if (string.IsNullOrEmpty(fileName))
    
        fileName = contentDisposition.FileName.ToString();
    

    if (string.IsNullOrEmpty(fileName))
        throw new BadRequestException("No filename defined.");

    using var fileStream = section.Body;
    await SendFileSomewhere(fileStream);


// This should probably not be inside the controller class
private async Task SendFileSomewhere(Stream stream)

    using var request = new HttpRequestMessage()
    
        Method = HttpMethod.Post,
        RequestUri = new Uri("YOUR_DESTINATION_URI"),
        Content = new StreamContent(stream),
    ;
    using var response = await _httpClient.SendAsync(request);
    // TODO check response status etc.


在此示例中,我们将整个文件流式传输到另一个服务。在某些情况下,最好将文件临时保存到磁盘。

【讨论】:

不需要手动分块文件。但是,请注意,如果您要发送大文件并且存在网络错误,则需要重新发送整个文件(这是tus 试图解决的问题)。如果你对它们进行分块,你只需要发送失败的块,但那时,我会简单地使用 tus 而不是重新发明***。 @Aspian 添加了一个示例。 非常感谢您抽出宝贵时间帮助我。 tus 库只处理上传文件。 @ManuelAllenspach 非常感谢你的回答,你能帮忙下载大文件的功能吗?我正在使用 return File(stream, mimeType, fileName) 并且流是 memoryStream 但我希望将文件流式传输到客户端【参考方案2】:

我发现这篇文章很有用 - https://www.tugberkugurlu.com/archive/efficiently-streaming-large-http-responses-with-httpclient

这里是下载大文件的代码版本:

static public async Task HttpDownloadFileAsync(HttpClient httpClient, string url, string fileToWriteTo) 
  using HttpResponseMessage response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
  using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(); 
  using Stream streamToWriteTo = File.Open(fileToWriteTo, FileMode.Create); 
  await streamToReadFrom.CopyToAsync(streamToWriteTo);

【讨论】:

这是一种在客户端下载大文件的方法。这不是服务器解决方案。【参考方案3】:

有时问题是我们使用 nginx 作为我们在 ubuntu/Linux 环境的 docker 中部署的 asp.net Core 应用程序的前端代理。这正是我尝试在 docker 或 .net 核心端进行调试时的情况,但实际的解决方案是通过将 Nginx 配置配置为

client_max_body_size 50M;

此行可以添加到您遇到问题的站点的 Nginx 配置的位置或服务器设置部分。

可能对某人有帮助。

【讨论】:

【参考方案4】:

问题是您必须处理长文件,无论您在哪里消耗它们,您都需要大量资源才能阅读。一种可能的解决方案是根据信息将文件划分为不同的块,或者在单独的作业或线程中处理它,或者使用 .net 中的并行性来处理它。您可以指定文件的大小也请阅读以下对您非常有用的博客。

Upload Large Files

【讨论】:

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

使用 Asp.NET Core 3.1 框架将文件上传到服务器时如何使用 IFormFile 作为属性?

asp.net core下载文件支持配置,上传文件访问支持下载

asp.net core下载文件支持配置,上传文件访问支持下载

如何在 ASP.NET Core 3.1 中使用 Java?

Wcf 服务在 .NET Core 3.1 控制台应用程序中工作,但在 ASP.NET Core 3.1 Web API 中无法工作

如何在我的 asp.net core 3.1 应用程序中播种用户和角色?