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

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

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

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

任何帮助将不胜感激。

【问题讨论】:

  • 文件流 FTW。

标签: c# asp.net-core file-io asp.net-core-webapi large-file-upload


【解决方案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

    【讨论】:

      猜你喜欢
      • 2020-08-25
      • 2021-05-27
      • 2020-07-19
      • 2021-03-15
      • 1970-01-01
      • 1970-01-01
      • 2021-04-08
      • 2020-10-23
      • 1970-01-01
      相关资源
      最近更新 更多