ASP.NET Core 文件上传:深入理解 Buffering 和 Streaming 模式

https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-9.0

https://www.cnblogs.com/lindexi/p/19192031

在 ASP.NET Core 中处理文件上传时,框架提供了两种核心方法:Buffering(缓冲)Streaming(流式传输)。选择正确的方法对应用的性能、资源消耗和可扩展性至关重要。

什么是 Buffering 模式?

Buffering 模式是文件上传的默认方式。在这种模式下,整个文件会被读取到内存或临时磁盘文件中,然后作为 IFormFile 对象提供给应用程序处理。

Buffering 的基本用法

[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
    if (file.Length > 0)
    {
        var filePath = Path.Combine(_storagePath, Path.GetRandomFileName());
        using (var stream = System.IO.File.Create(filePath))
        {
            await file.CopyToAsync(stream);
        }
    }
    return Ok();
}

Buffering 模式的底层运作机制

当客户端上传文件时,Buffering 模式在底层经历以下流程:

  1. 网络接收:HTTP 请求数据通过网卡 DMA 传输到内核的 Socket 接收缓冲区
  2. 内核缓冲:ASP.NET Core 请求处理管道读取 HTTP 请求体到内核空间
  3. 内存缓冲(小文件)
    • 如果文件 ≤ 64 KB,数据保留在内存中(托管堆)
    • 此时数据从内核空间复制到用户空间(进程内存)
  4. 磁盘缓冲(大文件)
    • 如果文件 > 64 KB,数据被写入临时文件
    • 临时文件位置:ASPNETCORE_TEMP 环境变量指定的目录
    • 这是为了避免大文件耗尽应用程序内存
  5. IFormFile 封装:框架将缓冲的数据封装为 IFormFile 对象供开发者使用

关键点:在控制器方法执行前,整个文件已经完整存在于内存或临时磁盘中。

Buffering 的优缺点

优点:

  • 代码简单直观,易于实现
  • 适合小文件上传(< 64 KB 性能最佳)
  • 完整的模型绑定支持和验证

缺点:

  • 并发上传大量文件可能耗尽内存
  • 大文件会占用磁盘空间(临时文件)
  • 不适合超大文件(如视频、数据库备份)

重要配置

services.Configure<FormOptions>(options =>
{
    // 单个文件大小限制(默认 128 MB)
    options.MultipartBodyLengthLimit = 268435456; // 256 MB
    
    // 内存缓冲阈值(默认 64 KB)
    options.MemoryBufferThreshold = 65536;
});

什么是 Streaming 模式?

Streaming 模式直接处理 HTTP 请求流,无需先将整个文件缓冲到内存或磁盘。文件数据以数据块的形式逐步接收和处理。

Streaming 的基本概念

在 Streaming 模式下:

  • 请求体被逐段读取(multipart sections)
  • 每个数据块立即处理或写入目标位置
  • 不依赖框架的模型绑定

Streaming 的实现示例

[HttpPost]
[DisableFormValueModelBinding] // 禁用默认模型绑定
public async Task<IActionResult> UploadStream()
{
    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        lengthLimit: 70);
        
    var reader = new MultipartReader(boundary, Request.Body);
    var section = await reader.ReadNextSectionAsync();
    
    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, 
                out var contentDisposition);
                
        if (hasContentDispositionHeader && 
            contentDisposition.IsFileDisposition())
        {
            // 直接流式写入目标位置
            var filePath = Path.Combine(_storagePath, Path.GetRandomFileName());
            using (var targetStream = System.IO.File.Create(filePath))
            {
                await section.Body.CopyToAsync(targetStream);
            }
        }
        
        section = await reader.ReadNextSectionAsync();
    }
    
    return Ok();
}

Streaming 的优缺点

优点:

  • 内存占用极低,与文件大小无关
  • 支持超大文件上传(GB 级别)
  • 更好的并发处理能力

缺点:

  • 实现复杂,需要手动解析 multipart 请求
  • 需要禁用默认模型绑定
  • 更容易出错,需要更多的错误处理

如何选择?

场景 推荐模式 原因
头像、图标(< 1 MB) Buffering 简单、性能好
文档、图片(1-10 MB) Buffering 在合理范围内
视频、备份(> 50 MB) Streaming 避免资源耗尽
高并发上传 Streaming 减少内存压力
需要立即处理 Streaming 边接收边处理

安全建议

无论使用哪种模式,都应该:

  1. 验证文件大小
if (file.Length > _fileSizeLimit)
{
    return BadRequest("文件过大");
}
  1. 验证文件扩展名
var ext = Path.GetExtension(fileName).ToLowerInvariant();
if (!_permittedExtensions.Contains(ext))
{
    return BadRequest("不支持的文件类型");
}
  1. 使用安全的文件名
// 永远不要直接使用用户提供的文件名
var safeFileName = Path.GetRandomFileName();
  1. 配置请求大小限制
[RequestSizeLimit(52428800)] // 50 MB
[RequestFormLimits(MultipartBodyLengthLimit = 52428800)]
public async Task<IActionResult> Upload(IFormFile file)
{
    // ...
}

总结

  • Buffering 适合大多数常规场景,代码简单,性能足够
  • Streaming 是处理大文件和高并发的必要手段
  • 理解底层机制有助于做出正确的架构决策
  • 始终要考虑安全性,无论选择哪种模式

选择正确的文件上传策略,能让你的应用在性能、可靠性和可扩展性之间取得最佳平衡。

posted @ 2025-11-06 10:16  talentzemin  阅读(28)  评论(0)    收藏  举报