应使用 IFormFileCollection 或 IEnumerable<IFormFile> 接收多图,避免 Request.Form.Files;前者参与模型绑定、支持参数共存和验证,后者更易 LINQ 操作和 Mock;需校验 file.Length、用唯一文件名防目录遍历。

直接用 IFormFileCollection 或 IEnumerable<iformfile></iformfile> 接收多图,别碰 Request.Form.Files —— 它不走模型绑定,容易漏文件、拿不到参数,且在 .NET 6+ 中已不推荐。
为什么不能用 Request.Form.Files[0] 拿多图
这是最常踩的坑:写 var file = Request.Form.Files[0] 看似能取到第一个,但实际会绕过整个模型绑定流程。后端参数(比如 IFormFile[] files)根本不会被赋值,files 始终为空;Request.Form.Files.Count 也可能为 0,即使前端明明传了 3 张。
根本原因:Request.Form.Files 是原始解析结果,依赖 Kestrel 的 multipart 解析器,但不参与 ASP.NET Core 的模型绑定链。一旦你手动读它,后续绑定就可能失效或错位。
- 只适用于极简场景(如无其他表单字段、不关心模型验证)
- 无法和 JSON 参数/其他字段共存(比如你要同时传图片 + 文本模型)
- 并发上传时临时文件名冲突风险更高(Kestrel 默认用原始文件名暂存)
IFormFileCollection 和 IEnumerable 怎么选
二者在控制器方法签名中行为一致,都支持模型绑定,但语义和扩展性有差别:
-
IFormFileCollection是 ASP.NET Core 内置类型,自带Count、索引器,适合需要快速判断数量或按序处理的场景 -
IEnumerable<iformfile></iformfile>更通用,配合 LINQ(如.Where(f => f.Length > 0))更自然,也便于单元测试 Mock - 别用
List<iformfile></iformfile>:默认模型绑定器不识别,参数永远为空 - 必须确保前端
<input name="files" multiple>的name和后端参数名完全一致(大小写敏感)
示例签名:
[HttpPost("upload")]
public async Task<IActionResult> Upload([FromForm] IEnumerable<IFormFile> files, [FromForm] string modelJson)
文件流读取和保存的硬约束
IFormFile.OpenReadStream() 返回的流是一次性的,且可能长度为 0 —— 直接 CopyToAsync 前不检查,会抛 ObjectDisposedException 或写入空文件。
- 必须先判断
if (file.Length == 0),跳过或返回错误 - 每个文件只能调用一次
OpenReadStream();重复调用会返回已关闭流 - 写磁盘建议用
using var stream = file.OpenReadStream(); await stream.CopyToAsync(fileStream),别手写Read/Write循环 - 生成唯一文件名(如
$"{Guid.NewGuid():N}.{Path.GetExtension(file.FileName)}"),避免路径拼接file.FileName导致目录遍历
前端 FormData 多图上传必须这样写
uni-app、微信小程序、Vue 都一样:要让后端收到多个 IFormFile,就必须对每张图单独 append("files", file),而不是把 FileList 或数组整体 append。
- 错误写法:
formData.append("files", fileArray)→ 后端只收到一个字符串[object File] - 正确写法:
for (const file of tempFiles) formData.append("files", file)(name必须相同) - 如果还传模型数据(如
{title: "xxx", desc: "yyy"}),需转成 JSON 字符串:formData.append("model", JSON.stringify(model)) - 用
fetch时别手动设Content-Type,浏览器会自动生成正确的multipart/form-data; boundary=...
调试时打开浏览器 Network → Payload → Form Data,确认每个文件是独立条目,且 key 名统一为 files。
最容易被忽略的是流生命周期和文件名清理——前者导致上传成功却存了个空文件,后者可能让攻击者写入 ../../web.config 这类路径。别省那几行校验代码。


















