.NET Core单例缓存IFormFile报错:无法访问已关闭流
问题根源
IFormFile 是 ASP.NET Core 请求上下文的绑定对象,请求处理完成后,框架会自动释放它关联的流资源。而你的 OrderService 是单例生命周期,其存活时长远超过单次请求,当后续从单例中取出订单并调用 OpenReadStream() 时,原 IFormFile 的流已经被销毁,因此抛出 ObjectDisposedException。
解决方案
核心原则:不要在单例或长生命周期对象中直接存储 IFormFile 实例,需要将文件内容持久化后再存储,常见两种实现方式:
方案1:将文件内容转为字节数组存储
适合小文件场景,直接把文件内容存在订单模型中:
- 修改订单相关模型,替换 IFormFile 为字节数组+元数据:
public class Watch { public byte[] DialDesignFileContent { get; set; } public string DialDesignFileName { get; set; } public string DialDesignFileContentType { get; set; } }
- 添加订单时,读取 IFormFile 内容到字节数组:
// 控制器接收订单请求示例 [HttpPost] public IActionResult CreateOrder([FromForm] OrderCreateDto dto) { var watch = new Watch(); if (dto.DialDesignFile != null) { using var stream = new MemoryStream(); dto.DialDesignFile.CopyTo(stream); watch.DialDesignFileContent = stream.ToArray(); watch.DialDesignFileName = dto.DialDesignFile.FileName; watch.DialDesignFileContentType = dto.DialDesignFile.ContentType; } var order = new Order { Id = Guid.NewGuid().ToString(), Watch = watch }; _orderService.AddOrder(order); return Ok(); }
- 生成邮件附件时,从字节数组创建流:
if (order.Watch.DialDesignFileContent != null) { var attachment = new MimePart(order.Watch.DialDesignFileContentType) { Content = new MimeContent(new MemoryStream(order.Watch.DialDesignFileContent)), ContentDisposition = new ContentDisposition(ContentDisposition.Attachment), ContentTransferEncoding = ContentEncoding.Base64, FileName = order.Watch.DialDesignFileName }; bodyBuilder.Attachments.Add(attachment); }
方案2:将文件保存到本地/云存储,订单存储路径元数据
适合大文件场景,避免占用过多内存:
- 添加订单时,将文件保存到服务器本地或云存储:
// 控制器接收订单请求示例 [HttpPost] public IActionResult CreateOrder([FromForm] OrderCreateDto dto) { var watch = new Watch(); if (dto.DialDesignFile != null) { // 定义文件存储路径(示例为wwwroot/uploads) var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads"); Directory.CreateDirectory(uploadDir); // 生成唯一文件名避免冲突 var uniqueFileName = Guid.NewGuid().ToString() + Path.GetExtension(dto.DialDesignFile.FileName); var filePath = Path.Combine(uploadDir, uniqueFileName); using var stream = new FileStream(filePath, FileMode.Create); dto.DialDesignFile.CopyTo(stream); // 订单中存储文件路径和元数据 watch.DialDesignFilePath = filePath; watch.DialDesignFileName = dto.DialDesignFile.FileName; watch.DialDesignFileContentType = dto.DialDesignFile.ContentType; } var order = new Order { Id = Guid.NewGuid().ToString(), Watch = watch }; _orderService.AddOrder(order); return Ok(); }
- 生成邮件附件时,从存储路径读取文件:
if (!string.IsNullOrEmpty(order.Watch.DialDesignFilePath) && File.Exists(order.Watch.DialDesignFilePath)) { var attachment = new MimePart(order.Watch.DialDesignFileContentType) { Content = new MimeContent(new FileStream(order.Watch.DialDesignFilePath, FileMode.Open, FileAccess.Read)), ContentDisposition = new ContentDisposition(ContentDisposition.Attachment), ContentTransferEncoding = ContentEncoding.Base64, FileName = order.Watch.DialDesignFileName }; bodyBuilder.Attachments.Add(attachment); }
注意事项
- 字节数组方案仅适合小文件,大文件建议用文件/云存储方案,避免内存溢出
- 使用文件存储时,需考虑订单删除时同步清理对应文件,避免磁盘空间浪费
- 任何场景下都不要将 IFormFile 直接存入单例、Session 等长生命周期对象
内容的提问来源于stack exchange,提问作者Hickori
相关产品推荐
相关产品推荐

