C# IFormFile上传大文件方法 C#如何处理大文件流式上传

来源:这里教程网 时间:2026-02-21 17:40:59 作者:

为什么
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<filesection>()</filesection>
—— 这个扩展方法内部仍会尝试缓冲

客户端和中间件要同步调整的三个硬性配置

光改后端没用,前端上传控件、反向代理、Kestrel 都可能提前掐断大文件。

前端
<input type="file">
必须配
enctype="multipart/form-data"
;用
fetch
时禁止
JSON.stringify()
包装文件
Kestrel 配置(
Program.cs
):
options.Limits.MaxRequestBodySize = 500_000_000;
(设为 0 表示不限,但生产环境慎用)
IIS(web.config):
<requestlimits maxallowedcontentlength="500000000"></requestlimits>
(单位是字节,注意不是 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 可靠得多

真正难的不是代码,是前后端对“块序号”“校验和”“合并时机”的约定一致——很多团队卡在这一步,最后又退回到调大超时和内存阈值的老路。

相关推荐