我正在使用干净的架构开发一个 ASP.NET Core 3.1 API 项目,并且我有以下类库(层):
- 基础设施(安全的东西和上传助手等......)
- 持久性(DA 层)
- 域(域模型)
- 应用程序(用例 - 业务逻辑)
- API(API项目作为我的启动项目)
我希望能够将大文件上传到服务器(例如 2Gb 或更大的文件大小)并在此之后下载它们,并且希望在将来不会出现内存溢出等问题。
任何帮助,将不胜感激。
我正在使用干净的架构开发一个 ASP.NET Core 3.1 API 项目,并且我有以下类库(层):
我希望能够将大文件上传到服务器(例如 2Gb 或更大的文件大小)并在此之后下载它们,并且希望在将来不会出现内存溢出等问题。
任何帮助,将不胜感激。
如果您有那么大的文件,请永远不要在您的代码中使用byte[]
或。MemoryStream
如果您下载/上传文件,则仅对流进行操作。
你有几个选择:
StreamContent
类发送它们。同样,不要使用 aMemoryStream
作为源,而是使用其他类似 a 的东西FileStream
。var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead)
. 否则,HttpClient 会将整个响应缓冲在内存中。然后,您可以通过 将响应文件作为流处理var stream = response.Content.ReadAsStreamAsync()
。ASP.NET Core 具体建议:
[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.
}
在此示例中,我们将整个文件流式传输到另一个服务。在某些情况下,最好将文件临时保存到磁盘上。
有时问题是我们使用 Nginx 作为我们在 ubuntu/Linux 环境的 docker 中部署的 asp.net Core 应用程序的前端代理。这正是我尝试在 docker 或 .net 核心端调试时的情况,但实际的解决方案是通过将 Nginx 配置配置为
client_max_body_size 50M;
此行可以添加到您遇到问题的站点的 Nginx 配置的位置或服务器设置部分。
可能对某人有帮助。
问题是您必须处理长文件,无论您在哪里使用它们,您都需要大量资源才能阅读。一种可能的解决方案是根据信息将文件划分为不同的块,或者在单独的作业或线程中处理它,或者使用 .net 中的并行性来处理它。您可以指定文件的大小也请阅读以下对您非常有用的博客。
我发现这篇文章很有用 - 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);
}