将MailMessage转换为原始文本

16

有没有简单的方法将 System.Net.Mail.MailMessage 对象转换为原始的邮件消息文本,就像在记事本中打开 eml 文件那样。

4个回答

19

这是同样的解决方案,但作为MailMessage的扩展方法。

在静态上下文中一次性获取ConstructorInfoMethodInfo成员可以最小化一些反射开销。

/// <summary>
/// Uses reflection to get the raw content out of a MailMessage.
/// </summary>
public static class MailMessageExtensions
{
    private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
    private static readonly Type MailWriter = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
    private static readonly ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(Flags, null, new[] { typeof(Stream) }, null);
    private static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", Flags);
    private static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", Flags);

    /// <summary>
    /// A little hack to determine the number of parameters that we
    /// need to pass to the SaveMethod.
    /// </summary>
    private static readonly bool IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;

    /// <summary>
    /// The raw contents of this MailMessage as a MemoryStream.
    /// </summary>
    /// <param name="self">The caller.</param>
    /// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
    public static MemoryStream RawMessage(this MailMessage self)
    {
        var result = new MemoryStream();
        var mailWriter = MailWriterConstructor.Invoke(new object[] { result });
        SendMethod.Invoke(self, Flags, null, IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true }, null);
        result = new MemoryStream(result.ToArray());
        CloseMethod.Invoke(mailWriter, Flags, null, new object[] { }, null);
        return result;
    }
}
为了获取底层的MemoryStream
var email = new MailMessage();
using (var m = email.RawMessage()) {
    // do something with the raw message
}

2
这个代码(在 Send 的附加参数下)在你的电脑上的 .NET 4.5 版本中能否正常工作?在调用 CloseMethod 之前,我不得不将 result 的内容复制到另一个 MemoryStream 中,因为当前的实现实际上会关闭底层流。 - hangy
1
是的,它应该像这样工作,但我还添加了一个检查 .NET 4.5 的功能。如果可以,请问您是否希望我更新答案并提供有效的代码? - hangy
1
我注意到 Send 方法实际上会剥离 BCC,因为它正在准备发送。如果你只是要将其传递给 Amazon SES 或其他类型的服务,那不是你想要的。最终我只是手动创建了 MIME 字符串。 - scojomodena
@allonhadaya,看起来答案没有返回非ASCII字符的MIME编码词语法 - Gan
@Gan,非常好奇...这只是利用了内部消息生成机制。您是否在MailMessage上设置了所有必要的编码属性?在此页面上搜索编码以查看它们:http://msdn.microsoft.com/en-us/library/system.net.mail.mailmessage(v=vs.110).aspx - allonhadaya
显示剩余3条评论

9
我已在 MimeKit 中实现了逻辑,使您可以将 System.Net.Mail.MailMessage 转换为 MimeKit.MimeMessage。一旦完成此操作,您就可以将消息简单地写入流中:
var message = (MimeMessage) CreateSystemNetMailMessage ();
using (var stream = File.Create ("C:\\message.eml"))
    message.WriteTo (stream);

这不需要反映到内部方法,这意味着它不依赖于运行时,使其比迄今为止给出的其他答案更加便携。

我注意到反射方法在通过Amazon SES发送时无法正确获取我的SubjectEncoding(UTF8)。Mimekit可以。谢谢! - Sebastian Brand
1
太棒了!很高兴MimeKit对您有所帮助! - jstedfast
看起来你是“手动映射”,对吧?不是说这很糟糕或者会导致问题,但如果我没有漏掉什么的话,理论上会有损失。(??)(现在正在尝试将我的代码适配到MonoMac,本来已经要放弃并自己手动映射了。) - ruffin
1
是的,这只是手动映射,但应该是完整的。显然,映射不是长期解决方案,因此我建议使用MimeKit构造消息作为长期解决方案,但如果你在短期内逐步移植你的S.N.Mail代码,映射方法是有用的... 如果这有意义的话。 - jstedfast
1
哦,如果你需要SMTP,我有一个叫做MailKit的库,它在MimeKit的基础上添加了SMTP、POP3和IMAP支持。 - jstedfast

2
我见过的代码实现这个功能依赖于反射。我改编了在网上找到的示例来创建这个方法:
    private static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
    {
        BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
        Assembly assembly = typeof(SmtpClient).Assembly;
        MemoryStream stream = new MemoryStream();
        Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
        ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(flags, null, new[] { typeof(Stream) }, null);
        object mailWriter = mailWriterContructor.Invoke(new object[] { stream });
        MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", flags);
        sendMethod.Invoke(message, flags, null, new[] { mailWriter, true }, null);
        MethodInfo closeMethod = mailWriter.GetType().GetMethod("Close", flags);                
        closeMethod.Invoke(mailWriter, flags, null, new object[] { }, null);
        return stream;
    }

你可以把 MemoryStream 转换成字符串或者其他需要的格式。
更新:在 .NET 4.5 中,一个方法的签名已经发生了变化,导致上述代码失效: 在 .NET 4.5 beta 中将 System.Net.Mail.MailMessage 转换为 MemoryStream

-1
byte[] allBytes = new byte[attachment.ContentStream.Length];
int bytesRead = attachment.ContentStream.Read(allBytes, 0, (int)attachment.ContentStream.Length);
Encoding encoding = Encoding.UTF8;
String contenidoCorreo = encoding.GetString(allBytes);

我不确定这个答案没有解释是否真的有所添加。 - Steve Owen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接