9

I'm currently looking at serializing a MailMessage object in C# and although there are a couple of variations of an example on the net, they serialize to binary which kind of misses the point IMO.

My approach is that I'd like to serialize a MailMessage to an RFC2822 eml string and the code below is what I've come up with.

    public string SerializeEmail(MailMessageArgs e)
    {
        string rfc822eml = "";
        Guid g = Guid.NewGuid();
        lock (g.ToString())
        {
            System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(@"C:\tmpspool");
            di.CreateSubdirectory(g.ToString());
            string spoolDir = @"C:\tmpspool\" + g.ToString();
            SmtpClient Client = new SmtpClient("localhost");
            Client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
            Client.PickupDirectoryLocation = spoolDir;
            Client.Send(e.mailObj);
            var files = from file in Directory.EnumerateFiles(spoolDir)
                       select file;
            string serializedEml = files.First();
            rfc822eml = File.ReadAllText(serializedEml);
            File.Delete(serializedEml);
            Directory.Delete(spoolDir);
        }

        return rfc822eml;
    }

It's pretty nasty but it does work. Ideally though, I'd create a new SMTPClient and add in a Serialize function which would return the rfc822 string automatically without it ever hitting the file system.

As I don't seem to be able to trace into the SMTPClient.Send function with Visual Studio, this "ideal" way of doing things is a tad tricky.

I've already sorted out the deserialization of the RFC message by adding in some small changes to Peter Huber's POP3MimeClient and POP3MailClient classes so I can deserialize a file on the HDD or a string back into a MailMessage.

The thing that's nagging at me is that I really should be able to serialize the MailMessaage using a stream and writing the stream to a string -or- file of my choice instead of going around the houses to create GUID based folders as the code above does and reading the content of the file back into a string...

Any pointers as to how I could make this more elegant will be much appreciated.

Thanks.

PaulV
  • 195
  • 1
  • 9
  • hi @PaulV - do you mind sharing the deserialization bit please ? Much appreciated. – YS. Apr 28 '15 at 00:58
  • 1
    Hi, it's not easy to share. I was working on a contract at the time and I no longer have access to the code. If I recall correctly, it was pretty much loading the string from the DB into a stream, then from there populating the message object, just as if you'd downloaded it from a POP server on a stream. – PaulV May 11 '15 at 16:03

2 Answers2

21

This will be a hack. Don't forget, MS can change it in the next versions of .Net.

(With the help of ILSpy) you can write an extension method like below

public static class MailExtensions
{
    public static string ToEml(this MailMessage mail)
    {
        var stream = new MemoryStream();
        var mailWriterType = mail.GetType().Assembly.GetType("System.Net.Mail.MailWriter");
        var mailWriter = Activator.CreateInstance(
                            type: mailWriterType,
                            bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic,
                            binder: null,
                            args: new object[] { stream },
                            culture: null,
                            activationAttributes: null);

        mail.GetType().InvokeMember(
                            name: "Send",
                            invokeAttr: BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
                            binder: null,
                            target: mail,
                            args: new object[] { mailWriter, true, true });


        return Encoding.UTF8.GetString(stream.ToArray());
    }
}

and use as

string eml = mailMessage.ToEml();
I4V
  • 34,891
  • 6
  • 67
  • 79
  • 1
    Thanks for that. I've tweaked it slightly to cope with our custom MailMessage object which has some extra features and the results are impressive. It works perfectly and is fast and elegant just as I thought should be possible. Very cool :D – PaulV Sep 12 '13 at 08:21
  • 3
    Why they no make public APIs! – John Leidegren Apr 24 '14 at 19:13
  • Dumbness on my part, removing the comment now. – muttley91 Jul 16 '14 at 00:04
  • 3
    This is a really nice extension. Has anyone implemented the reciprocal namely EML back to MailMessage? – TheEdge Sep 10 '14 at 05:48
  • 1
    Use a POP client and override a function or two to take a stream and load it into the MailMessage. The RFC2822 format is the same format that is served over POP and IMAP clients so you should be able to deserialize it quite easily. – PaulV May 11 '15 at 16:05
  • A using() can be added to dispose of the MemoryStream explicitly and a Close can be called on the MailWriter: http://stackoverflow.com/questions/25897922/how-invoke-mailwriter-through-reflection-could-corrupt-file – ofthelit May 18 '15 at 13:48
  • Can this work for an empty "From" field as well on the MailMessage? – Richard Mar 22 '16 at 17:38
  • Hi. Without diving in yet, will the above work with VS2005 and .net2? – Fandango68 Nov 16 '16 at 03:51
0

I finally managed to deserialize the MailMessage after a lot of work:

To get it right, I created a dll which will be used in both the client application and the webservice. This needs to be the same for the deserialization to work :)

I picked the dll serialization classes here: https://bitbucket.org/burningice/compositec1contrib.email/src/0bba07df532c8134717cfb40757f4cb22f002b1d/Email/Serialization/?at=default

Many thanks for sharing!

Once compiled and added my new dll to the projects, the send and receive worked.

So I convert the MailMessage this way : string serializedMessage = SerializeAsBase64(mail); and in the Webservice, I reconstruct it this way : MailMessage Mm = DeserializeFromBase64(serializedMessage);

I hope this helps...

Kemboi
  • 1
  • 1
  • 2