调用OpenReadStream()时出现Cannot access closed stream错误排查
我正在实现一个功能:让网站用户通过Web表单提交单个或多个文件,将这些文件作为附件发送给指定用户。已创建接收List<IFormFile>类型附件的Email Service,包含EmailModel类、PrepareEmailAttachments方法及测试代码。但调用attachment.OpenReadStream()时抛出System.ObjectDisposedException: Cannot access a closed Stream错误,已确认attachment中存在文件,但无法打开流,期望附件能加载到MimeKit用于邮件发送。
相关代码
EmailModel类
public class EmailModel { public EmailAddressModel FromUser { get; private set; } public List<EmailAddressModel> ToAddresses { get; private set; } public List<EmailAddressModel> CcAddresses { get; private set; } public List<EmailAddressModel> BccAddresses { get; private set; } public List<IFormFile> Attachments { get; private set; } public string Subject { get; private set; } public string Body { get; private set; } public bool isHTML { get; private set; } public EmailModel(EmailAddressModel fromUser, List<EmailAddressModel> toAddresses, List<EmailAddressModel> ccAddresses, List<EmailAddressModel> bccAddresses, List<IFormFile> attachments, string subject, string body, bool isHTML) { FromUser = fromUser; ToAddresses = toAddresses; CcAddresses = ccAddresses; BccAddresses = bccAddresses; Attachments = attachments; Subject = subject; Body = body; this.isHTML = isHTML; } }
PrepareEmailAttachments方法
private void PrepareEmailAttachments() { foreach (IFormFile attachment in Email.Attachments) { if (attachment.Length > 0) { var stream = attachment.OpenReadStream(); using (MemoryStream mStream = new MemoryStream()) { stream.CopyTo(mStream); var data = mStream.ToArray(); Builder.Attachments.Add(attachment.FileName, data); } } } }
测试代码
List<IFormFile> Attachments = new List<IFormFile>(); using (var stream = File.OpenRead("TestFile\\Test Document.pdf")) { var file = new FormFile(stream, 0, stream.Length, null, Path.GetFileName(stream.Name)) { Headers = new HeaderDictionary(), ContentType = "application/pdf" }; Attachments.Add(file); } EmailModel Email = new EmailModel( From, To, CC, BCC, Attachments, "Test Email", "This is a build test", true);
错误信息
Message: Test method BA.Common.Tests.SendMail.SendEmail threw exception: System.ObjectDisposedException: Cannot access a closed Stream. Stack Trace: BufferedFileStreamStrategy.Seek(Int64 offset, SeekOrigin origin) FileStream.set_Position(Int64 value) ReferenceReadStream.ctor(Stream inner, Int64 offset, Int64 length) FormFile.OpenReadStream() EmailService.PrepareEmailAttachments() line 111 EmailService.SendMail() line 43 SendMail.SendEmail() line 58
问题原因
测试代码中的using (var stream = File.OpenRead(...))块是罪魁祸首:当using块执行完毕,底层的FileStream会被自动释放,但你创建的FormFile对象持有了这个流的引用。后续调用OpenReadStream()时,尝试访问已经被释放的流,就会抛出ObjectDisposedException。
解决方案
方案1:测试中使用MemoryStream存储文件内容
修改测试代码,提前将文件内容读取到MemoryStream中,再用这个流创建FormFile。MemoryStream不依赖外部文件句柄,即使原文件流被释放,依然能正常读取:
List<IFormFile> Attachments = new List<IFormFile>(); // 先读取文件内容到字节数组 byte[] fileContent = File.ReadAllBytes("TestFile\\Test Document.pdf"); // 用字节数组创建MemoryStream var memoryStream = new MemoryStream(fileContent); // 创建FormFile对象 var formFile = new FormFile(memoryStream, 0, fileContent.Length, null, "Test Document.pdf") { Headers = new HeaderDictionary(), ContentType = "application/pdf" }; Attachments.Add(formFile);
方案2:优化PrepareEmailAttachments方法(可选)
虽然核心问题在测试代码,但可以优化附件处理逻辑,直接从IFormFile的流读取,避免依赖OpenReadStream()(本质上两者是同一个流,但写法更直观):
private void PrepareEmailAttachments() { foreach (IFormFile attachment in Email.Attachments) { if (attachment.Length > 0) { using (MemoryStream mStream = new MemoryStream()) { // 直接复制IFormFile的流,无需调用OpenReadStream() attachment.CopyTo(mStream); var data = mStream.ToArray(); Builder.Attachments.Add(attachment.FileName, data); } } } }
生产环境说明
在实际Web请求中,ASP.NET Core会自动管理IFormFile的流生命周期,请求处理期间流始终保持打开状态,不会出现这个问题。该错误仅出现在手动创建FormFile的测试场景中,只要正确处理底层流的生命周期即可避免。
内容的提问来源于stack exchange,提问作者Shawn

