在.NET 4.5 beta中如何将System.Net.Mail.MailMessage作为MemoryStream获取

23

所以,下面这段代码在 .NET 4 中可以用来获取一个作为 MemoryStream 的 System.Net.Mail.MailMessage 对象,但是在 .NET 4.5 beta 版本中会导致运行时异常。

Assembly assembly = typeof(SmtpClient).Assembly;
Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
using (MemoryStream stream = new MemoryStream())
{
    ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
    object mailWriter = mailWriterContructor.Invoke(new object[] { stream });
    MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
    sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true }, null);

    .....
}

在 sendMethod.Invoke() 上发生运行时异常。


什么是异常(堆栈跟踪可能会有帮助)? - M.Babcock
异常信息如下: System.Reflection.TargetParameterCountException:参数数量不匹配。 - dimoss
你有比较过.NET 4和.NET 4.5的代码,看看他们是否删除了Send的某个重载吗?使用dynamic可以更简单地处理这个问题。 - M.Babcock
请看下面我的回答,他们在Send方法中添加了一个额外的布尔参数(allowUnicode)。 - dimoss
6个回答

34

我设法在.NET 4.5 beta中找出如何再次启用此功能。MailMessage中的私有API Send()方法已更改为:internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)

请查看下面的更新代码。

Assembly assembly = typeof(SmtpClient).Assembly;
Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
using (MemoryStream stream = new MemoryStream())
{
    ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
    object mailWriter = mailWriterContructor.Invoke(new object[] { stream });
    MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
    sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true, true }, null);

    .....
}

这就是你访问非公共方法的下场 :) - D Stanley
9
据我所调查,获取MailMessage的MemoryStream没有其他方法。即使之前的帖子也使用了私有API方法,甚至第三方供应商的示例也使用了这个私有API方法。如果有人知道如何使用公共API来完成这个操作,分享一下将会非常有帮助 :) - dimoss
嗨,我遇到了同样的问题,但是对于closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null)这一行代码有什么线索吗?谢谢。 - VAAA
注意,在.NET 5中,上述代码中的mailWriterContructor将为null。新的构造函数是(Stream,bool)。 - Brad

13

如果你不想使用不受支持的技巧,并且不介意额外的性能损失,那么这可能是可用的。

public static class MailMessageExtensions
    {
    public static string  RawMessage(this MailMessage m)
        {
        var smtpClient = new SmtpClient { DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory };

        using (var tempDir = new TemporaryDirectory())
            {
            smtpClient.PickupDirectoryLocation = tempDir.DirectoryPath;
            smtpClient.Send( m );
            var emlFile = Directory.GetFiles( smtpClient.PickupDirectoryLocation ).FirstOrDefault();
            if ( emlFile != null )
                {
                return File.ReadAllText( emlFile );
                }
            else
                return null;
            }
        return null;
        }

    }

class TemporaryDirectory : IDisposable
    {
    public TemporaryDirectory()
        {
        DirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
        Directory.CreateDirectory( DirectoryPath );
        }

    public string DirectoryPath { get; private set; }

    public void Dispose()
        {
        if ( Directory.Exists( DirectoryPath ) )
            Directory.Delete( DirectoryPath, true );
        }
    }

不错的解决方案,很可能在未来的 API 版本中继续有效。谢谢! - antlersoft
我发现使用这种技术的唯一问题是Bcc目标从标题中删除了。https://technet.microsoft.com/en-us/library/bb124230.aspx#Anchor_3 - Spidermain50
不错。但是有一件事:如果 app.config 包含 SMTP 设置,则必须进行覆盖。请确保将 Host 设置为“localhost”,Credentials 设置为“null”,并将 EnableSsl 设置为“false”。 - Berend
这将是我的首选解决方案。唯一的问题是,由于我们在Windows\TEMP中创建随机文件夹,长期以来Windows是否会“允许”这样做?我的意思是,如果同时发送许多邮件,程序是否最终会因此而崩溃? - Soeren

9

检查是否使用额外的布尔值:

 If _sendMethod.GetParameters.Length = 2 Then
    _sendMethod.Invoke(Message, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Object() {_mailWriter, True}, Nothing)
 Else
    _sendMethod.Invoke(Message, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Object() {_mailWriter, True, True}, Nothing)
 End If

0

使用额外的TRUE的提议解决方案非常好。

在VS2012中运行我的项目时,即使我在所有库中都没有使用.net 4.5而是4.0,我开始出现错误。

错误仅发生在安装了VS2012的计算机上,看起来在调试时VS2012会引用.net 4.5。当您在运行.net 4.0的客户端上部署和运行应用程序时,一切正常。

因此:如果您运行4.0-不要添加额外的TRUE,如果您运行4.5,请添加它。


是的,我也遇到了这个问题。不得不加入编译器条件语句,以便我们可以调试代码并使其在生产环境中正常工作。 - efbenson

0
对于那些在.NET 5中遇到nullmailWriterContructor或面临参数计数不匹配异常的人,请仔细查看我的解决方案,适用于任何流。链接此处

0

我们花了很长时间来解决邮件消息转换的问题。最终的解决方案是使用MimeKit

var memoryStream = new MemoryStream();
var mimeMessage = MimeMessage.CreateFromMailMessage(message);
mimeMessage.WriteTo(memoryStream);

如果您使用上述方法,您将非常接近并且它将在大多数文化中工作,但最终主题编码将击败您。

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