IFormFile 直接读取大文件会内存溢出,因其默认在模型绑定阶段将整个文件加载进内存;须禁用模型绑定、启用 DisableBuffering、用 MultipartReader 流式解析 multipart 请求体。

为什么 IFormFile 直接读取大文件会内存溢出
因为 IFormFile 默认把整个上传内容加载进内存(MemoryStream),哪怕你只调用一次 file.OpenReadStream(),ASP.NET Core 仍可能在模型绑定阶段就完成全量缓冲。100MB 文件 ≈ 100MB 内存占用,多并发时极易触发 OutOfMemoryException 或被 IIS/Kestrel 主动中断。
关键不是“怎么读”,而是“别让框架先全读进来”——必须绕过默认模型绑定,用原始请求流直接处理。
- 禁用模型绑定:路由参数不要写
IFormFile file,改用HttpRequest.Body - 启用流式解析:在
Startup.cs或Program.cs中配置DisableBuffering = true - 手动解析 multipart boundary:用
MultipartReader逐段读,不缓存全文
如何用 MultipartReader 流式提取文件段
MultipartReader 是 ASP.NET Core 内置的底层工具,能边读边处理,不囤积数据。它依赖请求头里的 Content-Type: multipart/form-data; boundary=...,所以前端必须发标准 multipart 请求(不能用 application/json 包二进制)。
示例片段:
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).ToString();
var reader = new MultipartReader(boundary, Request.Body);
MultipartSection section;
while ((section = await reader.ReadNextSectionAsync()) != null)
{
var contentDisposition = section.ContentDisposition;
if (contentDisposition?.FileName.HasValue == true)
{
// 找到文件字段,开始流式写入磁盘或云存储
await using var fileStream = System.IO.File.Create($"upload/{contentDisposition.FileName.Value}");
await section.Body.CopyToAsync(fileStream); // 零拷贝,不进内存
}
}
- 务必检查
section.ContentDisposition.FileName而非Name,否则可能误取文本字段 -
section.Body是原始Stream,可直接传给CloudBlob.UploadFromStreamAsync()或分块写入本地 - 不用
section.As—— 这个扩展方法内部仍会尝试缓冲()
客户端和中间件要同步调整的三个硬性配置
光改后端没用,前端上传控件、反向代理、Kestrel 都可能提前掐断大文件。
一款WordPress内核的物流公司网站主题,适合各大物流公司企业建站用,商业主题,免费分享,本主题分享目的旨在学习参考之用,无任何收费行为。 wordpress官方网站上下载并安装wordpress3.32及以上版本。安装方法:上传后进者wp主题至wp-content\themes文件夹,进入后台"外观-主题-选择主题-启用"激活本主题。此为作者在Chinaz投稿第三版,请保
- 前端
必须配enctype="multipart/form-data";用fetch时禁止JSON.stringify()包装文件 - Kestrel 配置(
Program.cs):options.Limits.MaxRequestBodySize = 500_000_000;(设为 0 表示不限,但生产环境慎用) - IIS(web.config):
(单位是字节,注意不是 MB)
漏掉任意一项,请求会在到达控制器前就被 400 或 413 拦截,根本不会进你的 MultipartReader 逻辑。
分块上传更稳,但得前后端一起实现
如果网络不稳定或需断点续传,流式上传仍可能失败。这时应放弃单次 multipart,改用基于 Range 头的分块上传协议(如 tus.io 标准)。
- 后端只需暴露三个接口:
POST /upload(初始化)、PATCH /upload/{id}(追加块)、HEAD /upload/{id}(查进度) - 每块限制 5–10MB,用
Request.Body直接写临时文件,不走MultipartReader - 前端用
File.slice()+fetch控制每块重试,比单次传 2GB 可靠得多
真正难的不是代码,是前后端对“块序号”“校验和”“合并时机”的约定一致——很多团队卡在这一步,最后又退回到调大超时和内存阈值的老路。









