23

So, the below code used to work in .NET 4 to get a System.Net.Mail.MailMessage object as a MemoryStream, however with the release of .NET 4.5 beta a runtime exception occurs.

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);

    .....
}

Runtime exception occurs on sendMethod.Invoke().

dimoss
  • 1,506
  • 2
  • 16
  • 24
  • What's the exception (stack trace may help)? – M.Babcock Mar 07 '12 at 03:37
  • Exception was this: System.Reflection.TargetParameterCountException: Parameter count mismatch. – dimoss Mar 07 '12 at 03:59
  • Have you compared the .NET 4 code to .NET 4.5 to see if they dropped an overload of `Send`? This could most certainly be handled more simply with the use of `dynamic`. – M.Babcock Mar 07 '12 at 04:04
  • Please see my answer below, they added an extra boolean parameter (allowUnicode) to the Send method. – dimoss Mar 07 '12 at 04:06

6 Answers6

34

Managed to figure out how to get this working again in .NET 4.5 beta. The private API Send() method in MailMessage has changed to: internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)

Please find updated code below.

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);

    .....
}
dimoss
  • 1,506
  • 2
  • 16
  • 24
  • That's what you get for accessing non-public methods :) – D Stanley Mar 07 '12 at 03:43
  • 9
    As far as I could investigate, there is no other way to get the MemoryStream of a MailMessage. Even previous posts on here use the private API method, and even examples from 3rd party vendors use this private API method. If anyone knows of a way to do this using public API's it would be a big help to share it :) – dimoss Mar 07 '12 at 03:58
  • Hi, Im having the same issue but for closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null);... any clue? Thanks – VAAA Sep 12 '12 at 16:41
  • Note, in .NET 5 mailWriterContructor sill be null in the above code. The new constructor is (Stream, bool). – Brad Feb 21 '21 at 01:16
13

This might be usable if you don't want to go with unsupported hacks and don't mind extra performance hit.

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 );
        }
    }
mbergal
  • 635
  • 6
  • 13
  • Nice workaround, and likely to work through further api versions. Thanks! – antlersoft Dec 11 '13 at 14:04
  • The only problem I found with using this technique is the Bcc destination is removed from the header. https://technet.microsoft.com/en-us/library/bb124230.aspx#Anchor_3 – Spidermain50 Jun 19 '18 at 12:26
  • Nice. One thing though: If the app.config contains smtp settings, these must be overridden. Make sure to set Host to `localhost`, Credentials to `null` and EnableSsl to `false`. – Berend May 01 '19 at 07:47
  • This would be my preferred solution. Only question is; since we create random folders in Windows\TEMP, would Windows "allow" this in the long run? I mean, if many mails were sent simultaneously all the time, would the program eventually break down because of this? – Soeren Feb 21 '20 at 07:43
9

for checking if extra boolean i use :

 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
r2b2s
  • 203
  • 1
  • 3
  • 12
0

The proposed solution with the extra TRUE works beautifully.

I started to getting the error while running my project in VS2012 even though I am not using .net 4.5 but 4.0 in all my libraries.

The error only happens on the machine where you have installed VS2012, looks like VS2012 makes reference to .net 4.5 while you are debugging. When you deploy and run the application in clients running .net 4.0 everything works fine.

Thus : If you run 4.0 - do not add the extra TRUE, if you run 4.5 add it.

  • Yeah I ran into this as well. Had to put compiler conditionals so we can debug the code and have it work in production. – efbenson Oct 10 '13 at 11:54
0

We fought with the mail message conversion for a long time. Ultimately the solution was to use MimeKit.

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

If you use the methods above you will get really close and it will work in most cultures but eventually the subject encoding will defeat you.

Shaun Bowe
  • 9,840
  • 11
  • 50
  • 71
0

For those, who are struggling with mailWriterContructor being null in .NET 5 or facing Parameter count mismatch exception, take a closer look on my solution usable for any stream. Link here

B8ightY
  • 469
  • 5
  • 5