根本原因是mailmessage发送时自动调用流的dispose(),且流position未置0导致内容为空;须用memorystream并设position=0,传入正确contenttype,避免using或提前关闭。

MailMessage.Attachments.Add() 为什么加不进流附件?
直接 new Attachment(stream, name) 后 Add() 失败,常见报错是 ObjectDisposedException 或附件内容为空——根本原因是 MailMessage 发送时会自动调用流的 Dispose(),而多数流(如 MemoryStream)默认在被释放后无法再次读取。
- 必须确保流在整个邮件发送完成前保持打开状态,且
Position在开头(否则读不到内容) - 不能用 using 包裹流,也不能在 Add() 前就调用
stream.Dispose()或stream.Close() - 推荐用
MemoryStream并显式设置stream.Position = 0,避免因写入后位置在末尾导致附件为空
如何用 MemoryStream 添加 PDF/Excel 等二进制附件?
从文件、数据库或 API 拿到字节数据后,用 MemoryStream 封装是最稳妥的方式,它不依赖文件系统,也避免了 FileStream 的权限和锁问题。
- 构造附件时必须传入
ContentType,否则 Outlook 等客户端可能无法识别类型:new Attachment(stream, "report.pdf", "application/pdf") - 若从文件读取,别用
File.OpenRead(path)直接传入 —— 它返回的FileStream在发送过程中容易被提前关闭 - 示例关键片段:
var bytes = File.ReadAllBytes("invoice.xlsx"); var stream = new MemoryStream(bytes); stream.Position = 0; var attachment = new Attachment(stream, "invoice.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); mailMessage.Attachments.Add(attachment);
发送后附件流被释放,还能再用吗?
不能。一旦 SmtpClient.Send() 返回,MailMessage 会内部调用所有附件的 Dispose(),此时原 MemoryStream 已不可读、不可重置 Position。
- 如果需要复用附件数据(比如重发、日志存档),必须在 Add() 前先
ToArray()或深拷贝一份字节 - 不要试图在 Send() 后再访问
attachment.ContentStream,它已是 null 或已 disposed - 调试时可用
attachment.ContentType.MediaType和attachment.Name验证是否注册成功,但别依赖ContentStream.Length—— 有些流不支持 Length(如加密流)
为什么用 Stream 而不是文件路径添加附件?
用 Attachment(string fileName) 看似简单,但它要求路径存在、有读权限、且文件在发送全程不能被移动或删除;而流附件绕过了这些限制,更适合 Web 应用、微服务或临时生成内容的场景。
- Web API 中生成报表后直接转
MemoryStream发送,避免磁盘 I/O 和清理逻辑 - 从数据库
varbinary(max)字段读出 byte[] 后封装为流,比先写临时文件再读更安全高效 - 注意:流附件不会自动设置
ContentDisposition.FileName编码,中文名需手动设attachment.NameEncoding = Encoding.UTF8,否则可能乱码
Position != 0 或用了被池化回收的流(如 ASP.NET Core 中的 HttpContext.Request.Body)。










