ASP.NET Core 5客户端提交带文件对象到API失败解决方法
核心错误点
- 客户端使用
application/json格式序列化发送包含IFormFile的对象,JSON格式无法承载二进制文件内容,与API端[FromForm]要求的multipart/form-data表单格式不匹配,是请求失败的直接原因。 - 代码中混用
async/await与阻塞式.Result调用,ASP.NET Core上下文下易触发线程死锁。 - 每次请求新建
HttpClient实例存在套接字耗尽风险,生产环境需调整。
可正常运行的实现代码
1. 模型定义
原有模型定义无需修改,可直接使用:
public class ABC { public int Id { get; set; } public string Image { get; set; } public string Name { get; set; } [NotMapped] public IFormFile File { get; set; } }
注意:客户端上传页面的表单必须添加enctype="multipart/form-data"属性,否则客户端控制器无法接收到前端上传的文件,表单参考示例:
<form asp-action="Create" enctype="multipart/form-data" method="post"> @Html.AntiForgeryToken() <div> <label>名称</label> <input asp-for="Name" /> </div> <div> <label>图片</label> <input asp-for="File" type="file" /> </div> <button type="submit">提交</button> </form>
2. 客户端控制器修正代码
改用MultipartFormDataContent构造表单请求,全程使用异步调用避免死锁:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create(ABC aBC) { // 生产环境建议使用IHttpClientFactory创建HttpClient复用实例,此处为简化演示直接实例化 using var httpClient = new HttpClient(); using var formContent = new MultipartFormDataContent(); // 拼接普通业务字段 formContent.Add(new StringContent(aBC.Name ?? string.Empty, Encoding.UTF8), nameof(ABC.Name)); formContent.Add(new StringContent(aBC.Image ?? string.Empty, Encoding.UTF8), nameof(ABC.Image)); // 拼接上传文件 if (aBC.File != null && aBC.File.Length > 0) { var fileContent = new StreamContent(aBC.File.OpenReadStream()); fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(aBC.File.ContentType); formContent.Add(fileContent, nameof(ABC.File), aBC.File.FileName); } // 异步发送请求 var response = await httpClient.PostAsync($"{BaseURLManager.BaseUrl}ABCs", formContent); response.EnsureSuccessStatusCode(); var responseStr = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject<ABC>(responseStr); return View(result); }
3. API端控制器代码
补充请求类型标注、文件存储逻辑,原有绑定逻辑可正常工作:
// POST: api/ABCs [HttpPost] [Consumes("multipart/form-data")] // 明确指定接口接收表单类型请求 public async Task<ActionResult<ABC>> PostABC([FromForm] ABC aBC) { // 处理文件存储:示例为保存到服务端wwwroot/uploads目录,可按需替换为云存储逻辑 if (aBC.File != null && aBC.File.Length > 0) { var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads"); if (!Directory.Exists(uploadDir)) Directory.CreateDirectory(uploadDir); // 建议对文件重命名(如加GUID前缀)避免同名覆盖,此处简化演示直接用原文件名 var saveFileName = Path.GetFileName(aBC.File.FileName); var savePath = Path.Combine(uploadDir, saveFileName); using var fs = new FileStream(savePath, FileMode.Create); await aBC.File.CopyToAsync(fs); // 将文件访问路径赋值给Image字段存入数据库 aBC.Image = $"/uploads/{saveFileName}"; } _context.ABC.Add(aBC); await _context.SaveChangesAsync(); return CreatedAtAction("GetABC", new { id = aBC.Id }, aBC); }
如果需要调整允许上传的文件大小限制,可在项目Startup.cs的ConfigureServices方法中添加配置:
services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 1024 * 1024 * 100; // 最大允许100MB文件,可按需调整 });
额外注意事项
- 生产环境不要每次请求新建
HttpClient,请通过IHttpClientFactory获取实例复用,避免套接字耗尽问题。 - 正式环境必须增加文件类型校验、文件名校验逻辑,防止恶意脚本文件上传攻击。
- 若客户端与API部署在不同域名下,需在API端配置CORS策略允许客户端跨域请求。
内容的提问来源于stack exchange,提问作者Ibrahim
相关产品推荐
相关产品推荐

