C#流式上传文件 C#如何在不加载到内存的情况下处理大文件上传

1次阅读

asp.net core 的 request.form.files 默认将大文件全载入内存,因其依赖 multipartreader 且默认启用缓冲;需禁用自动模型绑定、设置 disablemultipartbuffering = true,并用 multipartreader 流式解析 multipart 数据边读边转存。

C#流式上传文件 C#如何在不加载到内存的情况下处理大文件上传

为什么 Request.Form.Files 会把大文件全塞进内存

ASP.NET Core 默认用 FormFile 处理上传,它底层依赖 MultipartReader,但一旦调用 file.OpenReadStream()file.CopyToAsync(),框架就会把整个文件先缓存到内存(或临时磁盘),取决于 WebHostOptions.MemoryLimitFormOptions.MultipartBodyLengthLimit 设置。这不是你代码写得不对,是默认行为本身就不适合 GB 级文件。

HttpContext.Request.Body 直接流式读取原始 multipart 数据

绕过 IFormFile,自己解析 multipart boundary,边读边转存。关键点:必须禁用自动模型绑定、关闭缓冲、手动控制读取节奏。

  • Startup.ConfigureServices 中设 options.DisableMultipartBuffering = true
  • Controller 方法参数去掉 [FromForm],改用 HttpContext 入参
  • 检查 Request.ContentType 是否以 "multipart/form-data" 开头,并提取 boundary
  • new MultipartReader(boundary, Request.Body) 创建读取器,逐段调用 ReadNextSectionAsync()
  • 对每个 section,检查 ContentDispositionFileName 字段,匹配目标文件字段名(如 "file"
  • 拿到对应 section 的 Body 流后,直接 await section.Body.CopyToAsync(outputStream) —— 这里 outputStream 可以是 FileStreamHttpClient.PostAsync 的请求体,或云存储 SDK 的上传流

MultipartReader 容易漏掉的坑

它不自动跳过 preamble(boundary 前的空白),也不校验 header 编码,出错时异常信息极简,常表现为 InvalidDataException 或静默截断。

  • 务必用 Request.Headers["Content-Length"] 校验总长度,防止客户端传一半就断连
  • 设置 Request.EnableBuffering() 仅在调试时临时开启,上线必须关 —— 否则又回到内存加载老路
  • boundary 要从 ContentType 中安全提取:MediaTypeHeaderValue.Parse(Request.ContentType).Parameters.FirstOrDefault(p => p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase))?.Value
  • 不要用 StreamReader 读 section body —— 它是二进制文件,不是文本;必须用 Stream 原始读取

上传到云存储(如 azure Blob / AWS S3)时怎么保持流式

核心是别把文件落地本地磁盘,而是让 MultipartReader 解出的 section.Body 直接作为上传 SDK 的输入流。多数 SDK 支持流式上传,但注意超时和分块策略。

  • Azure Blob:用 BlobClient.UploadAsync(stream, new BlobHttpHeaders { ContentType = "application/octet-stream" }),内部会自动分块,无需你处理
  • AWS S3:PutObjectRequest.InputStream = section.Body,但需确保 AutoCloseStream = false,否则 SDK 关闭流会导致后续 section 读取出错
  • 若目标服务不支持流式(如某些旧版 FTP),只能落地临时文件 + FileStream 分块读取 + 手动删除,此时至少避免一次性 ReadAllBytes

真正难的不是“怎么读”,而是边界条件:网络中断时如何续传、并发上传多个大文件时如何限流、用户取消上传时如何及时释放连接。这些没法靠一个 MultipartReader 解决,得结合前端 fetchAbortController 和后端连接生命周期管理。

text=ZqhQzanResources