在ASP.NET Core开发中,处理文件上传通常会用到IFormFile接口,但当上传文件体积较大时,默认的IFormFile会将整个文件加载到内存或临时磁盘中,容易引发内存溢出、请求超时等问题,此时采用流式上传是更合理的选择。

默认IFormFile上传的局限性
默认的IFormFile上传逻辑会将上传的文件内容先完整读取到内存或者服务器的临时目录中,对于超过几十MB的大文件,这种方式会占用大量服务器内存,甚至导致应用程序崩溃。同时,ASP.NET Core默认对请求体大小有30MB的限制,大文件上传会直接触发400错误。
流式上传的核心思路
流式上传的核心是避免一次性加载整个文件,而是边接收请求流边写入目标文件,同时需要提前调整请求大小限制和表单配置。具体步骤如下:
- 修改请求体大小限制,允许大文件上传请求通过
- 禁用默认的模型绑定,避免IFormFile提前加载文件内容
- 手动读取HTTP请求的原始流,分块写入到目标存储路径
- 处理上传过程中的异常,确保资源正确释放
具体实现步骤
1. 配置请求大小限制
首先需要在接口或者全局配置中调整请求体的最大大小,这里以接口级别配置为例,在控制器方法上添加特性:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System.IO;
using System.Threading.Tasks;
[ApiController]
[Route("api/upload")]
public class FileUploadController : ControllerBase
{
// 禁用默认的表单模型绑定,避免IFormFile提前加载文件
[HttpPost("large-file")]
[DisableFormValueModelBinding]
// 设置请求体最大为1GB,可根据实际需求调整
[RequestSizeLimit(1073741824)]
[RequestFormLimits(MultipartBodyLengthLimit = 1073741824)]
public async Task<IActionResult> UploadLargeFile()
{
// 流式上传逻辑
}
}
其中DisableFormValueModelBinding是一个自定义特性,用于禁用默认的表单绑定,代码如下:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class DisableFormValueModelBindingAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.Clear();
factories.Add(new FormValueProviderFactory());
factories.Add(new JQueryFormValueProviderFactory());
}
}
2. 实现流式上传核心逻辑
接下来在接口方法中手动读取请求流,解析multipart/form-data格式的内容,将文件流写入到目标路径:
private async Task<IActionResult> UploadLargeFile()
{
// 目标存储路径,实际开发中可根据需求调整
var uploadPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads");
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}
// 获取请求内容类型,验证是否为multipart/form-data
var contentType = Request.ContentType;
if (string.IsNullOrEmpty(contentType) || !contentType.Contains("multipart/form-data"))
{
return BadRequest("请求格式错误,仅支持multipart/form-data格式");
}
// 解析boundary
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(contentType).Boundary).Value;
if (string.IsNullOrEmpty(boundary))
{
return BadRequest("请求boundary解析失败");
}
var reader = new MultipartReader(boundary, Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
// 判断当前section是否为文件内容
var contentDisposition = section.ContentDisposition;
if (string.IsNullOrEmpty(contentDisposition))
{
section = await reader.ReadNextSectionAsync();
continue;
}
var hasFileName = contentDisposition.Contains("filename");
if (hasFileName)
{
// 获取文件名,实际开发中可以添加文件名校验逻辑
var fileName = GetFileNameFromContentDisposition(contentDisposition);
var filePath = Path.Combine(uploadPath, fileName);
// 分块写入文件,避免一次性占用大量内存
using (var targetStream = System.IO.File.Create(filePath))
{
await section.Body.CopyToAsync(targetStream);
}
}
section = await reader.ReadNextSectionAsync();
}
return Ok("文件上传成功");
}
// 从Content-Disposition中提取文件名
private string GetFileNameFromContentDisposition(string contentDisposition)
{
var elements = contentDisposition.Split(';');
foreach (var element in elements)
{
var trimmedElement = element.Trim();
if (trimmedElement.StartsWith("filename="))
{
return trimmedElement.Substring("filename=".Length).Trim('"');
}
}
return Guid.NewGuid().ToString() + ".tmp";
}
注意事项
- 流式上传过程中不要对文件流进行多次读取,避免流位置偏移导致内容丢失
- 生产环境中需要添加文件名合法性校验,避免路径遍历攻击
- 如果上传的文件需要存储到对象存储等远程服务,可直接将流对接到对应服务的上传接口,无需先落盘
- 上传完成后及时释放流资源,避免文件句柄泄漏
- 可添加上传进度回调,方便前端展示上传进度
总结
通过流式上传的方式处理C# IFormFile大文件上传,能够有效避免内存占用过高的问题,提升大文件上传的稳定性。核心在于禁用默认的模型绑定,手动解析请求流并分块写入,同时配合合理的请求大小配置,即可实现高效的大文件上传功能。