Click here to Skip to main content
Email Password   helpLost your password?

Introduction

This article is going to use some simple techniques to analyse an existing .NET framework class and to extend it in a way the original developers did not intend.

Background

I’ve been recently working on a project related to batch generating large numbers of emails for sending at a later stage. The MailMessage class appeared to provide the functionality I needed. It allowed me to add both plain text and HTML content, and using the DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory of SmtpClient, I could generate the emails to a file folder. However, when using this approach, I ran into a problem ...

One of the requirements of my project specified that I needed to control the filename used to output the email so that multiple jobs could be run concurrently and the filename could in some way relate the email to the job that run it. I also wanted to append a sequence number so that I could use this as a way for jobs to continue from where they left off if they failed.

The SmtpClient class has a Send(MailMessage Message) method which, when the PickupDirectoryLocation is specified and DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory, generates the email in that directory. However, the file name used by SmtpClient appeared to be a random Guid. No where does the standard SmtpClient or MailMessage expose a way for me to choose the filename used for output or even feedback the filename that was used.

Using Reflector to analyse existing classes

Not choosing to give up on using MailMessage and therefore opt for a third party component outside of the .NET framework, I embarked on finding a way of extending MailMessage and SmtpClient to provide the functionality I needed.

First, I used the excellent Reflector tool to analyse the MailMessage and SmtpClient classes to find out what they were really doing ‘under the hood’. You can download this free from RedGate.

Pointing Reflector at the SmtpClient.Send() method told me two things. When a pickup directory is specified, SmtpClient.Send() creates a MailWriter object called fileMailWriter using the GetFileMailWriter(string Path) method.

To actually generate the email, it then calls a Send() method on the MailMessage object, passing the newly created MailWriter object.

reflect1.jpg

To investigate further, I looked at what was happening in the SmtpClient.GetFileMailWriter() method.

reflect2.jpg

As expected, the disassembled code shows that this method creates a random filename with Guid.NewGuid() + “.eml”. It then creates a MailWriter object, passing it a standard FileStream.

From Reflector to Reflection

So, at this stage, I knew what was happening when I called SmtpClient.Send(message). But, how could I change this behaviour so that I could generate an email with my own specified filename?

I basically wanted to create a MailWriter object with my own FileStream and then pass it to MailMessage.Send(). However, Microsoft has not exposed this functionality to me. The MailWriter class, GetFileMailWriter(), and MailMessage.Send() methods are all marked as internal, so I couldn’t construct or invoke these methods in the standard way.

This is where Reflection stepped in. I could use Reflection to construct an internal MailWriter and then again to invoke the internal Send() method on MailMessage.

And that was it – the generated email was created to the filename specified when constructing my FileStream class. By using Reflection, I had reused internal classes and methods in the System.Net.Mail namespace to save a mail to the file system with my own filename – exactly what I needed.

Finishing touches - extension method

For a final nice touch, I could now wrap this up by using a new feature of C# 3.0 called extension methods. By using an extension method, you can add a Save(string FileName) method to the MailMessage class as if it was put there in the first place.

Shown below is the complete extension method:

public static class MailMessageExt
{
    public static void Save(this MailMessage Message, string FileName)
    {
        Assembly assembly = typeof(SmtpClient).Assembly;
        Type _mailWriterType = 
          assembly.GetType("System.Net.Mail.MailWriter");

        using (FileStream _fileStream = 
               new FileStream(FileName, FileMode.Create))
        {
            // Get reflection info for MailWriter contructor
            ConstructorInfo _mailWriterContructor =
                _mailWriterType.GetConstructor(
                    BindingFlags.Instance | BindingFlags.NonPublic,
                    null,
                    new Type[] { typeof(Stream) }, 
                    null);

            // Construct MailWriter object with our FileStream
            object _mailWriter = 
              _mailWriterContructor.Invoke(new object[] { _fileStream });

            // Get reflection info for Send() method on MailMessage
            MethodInfo _sendMethod =
                typeof(MailMessage).GetMethod(
                    "Send",
                    BindingFlags.Instance | BindingFlags.NonPublic);

            // Call method passing in MailWriter
            _sendMethod.Invoke(
                Message,
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new object[] { _mailWriter, true },
                null);

            // Finally get reflection info for Close() method on our MailWriter
            MethodInfo _closeMethod =
                _mailWriter.GetType().GetMethod(
                    "Close",
                    BindingFlags.Instance | BindingFlags.NonPublic);

            // Call close method
            _closeMethod.Invoke(
                _mailWriter,
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new object[] { },
                null);
        }
    }
}

Basically, we have created a static method in a static class. The thing that marks this method out to be an extension method is the first parameter, this MailMessage Message. The first parameter when marked with this <ObjectType> says that this method is to extend the <ObjectType> class, in this case the MailMessage class.

Now, we can call our extra functionality like so:

MailMessage _testMail = new MailMessage();
_testMail.Body = "This is a test email";
_testMail.To.Add(new MailAddress("email@domain.com"));
_testMail.From = new MailAddress("sender@domain.com");
_testMail.Subject = "Test email";
_testMail.Save(@"c:\testemail.eml");

Final note

What we have achieved here is:

Of course, as with most things, there is a downside to the approach I have taken.

One of the major things is that because we are using Reflection to get around the original restrictions put in place by Microsoft in this case, they are free to change their ‘internal’ implementation of System.Net.Mail in future framework releases, which could break our implementation. For example, what is to stop them changing the name of the MailWriter class to something else as it wasn’t exposed to us in the first place.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralIs that thread-safe?
microsoftcsharp
0:29 6 Mar '10  
Hi, thanks for this great article. The only thing I want to confirm is that thread-safe? thanks
GeneralCan we get stream before sending the mail?
yogesh.rain
0:26 14 Jan '10  
Hi Allan,

Nice stuff by you. But i have some different requirement.

I want to access the stream object before sending the mail.

Reason is I want to estimate approximate value of number of bytes suppose to be transmitted over the wire.

Please advice. Thanks in advance.
GeneralI never thought so
Sameers (theAngrycodeR )
22:14 17 Nov '09  
I never thought that it could be done in this way, but it is really nice thing. I wonder, is there any way to implement such thing in VB? Or the technique is C# specific?

thanks
Sameers

FREE MSN Auto Responder[^]

History Remember Vendors, NOT Developers

AnswerRe: I never thought so
Allan Eagle
11:01 19 Nov '09  
If you mean use 'reflection' and 'extension methods' then, yes, you can in VB. I don't ever code in VB but presume they are not too much different from doing the same in C#. Everything you need for reflection is in the namespace System.Reflection ... I think even Reflector will disassemble code into VB instead of C# ...
GeneralRe: I never thought so
Sameers (theAngrycodeR )
21:24 19 Nov '09  
Well, reflection is not an issue, I tried to convert your code into VB and stucked with the

void Save (this System.Net.Mail.MailMessage Message, string FileName)

there seems to be no way to specify "this" in VB in the method signature. I guess, that is the core of the technique.

Any Idea?

thanks,
Sameers

FREE MSN Auto Responder[^]

History Remember Vendors, NOT Developers

GeneralRe: I never thought so [modified]
Allan Eagle
1:58 25 Dec '09  
It looks like the syntax varies slightly in VB.NET. From what I have read elsewhere (see Implementing Extension Methods in VB.NET) it appears that you have to mark your extension method with a custom attribute. You should end up with something like this:-

<System.Runtime.CompilerServices.Extension()> _
Public Sub Save(ByVal Message As MailMessage, FileName As String)

...

End Sub

Hope this helps.

modified on Friday, December 25, 2009 7:20 AM

GeneralRe: I never thought so
Sameers (theAngrycodeR )
3:37 25 Dec '09  
Yes, I see that now. Thank you for referring such a nice thing. I would like to add that there are some exceptions here you must understand

1. You have to pass the type in the parameter as you wrote in your sample (MailMessage for example)
2. You must define that in a module, it wont work in Class
3. You should have framework 3.5 (I tried in 2.0 but didn't worked, maybe, it is available in 3.0 as well)

that's all. then its easy to implement.

Thanks again,
Sameers

FREE MSN Auto Responder[^]

History Remember Vendors, NOT Developers

QuestionFileStream to MailMessage
jp2code
9:48 10 Jul '09  
Very good article, and easy enough for someone without Reflector (me) to still understand.

I happened across this while searching for a way to read in a message saved on a computer. I do not use the System.Net.Mail namespace often, so I am likely overlooking some very basic tools to load a MailMessage.

Could you (or anyone on here) comment on how to get the code snippet below to generate the MailMessage that I am after?

Server.Start(); // public TcpListener 
using (TcpClient client = AcceptTcpClient()) { // waits until data is avaiable
string file = @"C:\Msg.eml";
byte[] tcpBuf = new byte[client.ReceiveBufferSize];
byte[] memBuf = null;
try {
using (MemoryStream ms = new MemoryStream(tcpBuf)) {
using (NetworkStream stream = client.GetStream()) {
int len;
do {
len = stream.Read(tcpBuf, 0, client.ReceiveBufferSize);
ms.Write(tcpBuf, 0, len);
} while (len == client.ReceiveBufferSize);
stream.Close();
}
memBuf = ms.GetBuffer();
ms.Close();
using (FileStream fs = new FileStream(file, FileMode.Create)) {
fs.Write(memBuf, 0, memBuf.Length);
fs.Close();
}
}
} finally {
client.Close();
}
try {
MailMessage email = (MailMessage)memBuf; /* <= THIS IS WRONG!
** "Cannot convert type 'byte[]' to 'System.Net.Mail.MailMessage'"
**
**/
SmtpClient client = new SmtpClient("172.16.8.200");
client.Send(email);
} catch (Exception er1) {
Console.WriteLine(er1.Message);
}
}



AnswerRe: FileStream to MailMessage
jp2code
13:02 10 Jul '09  
I've got something very close, but it does not quite work.

Could you, instead, take a look at this MailStream class?
public class MailStream : FileStream {

public MailStream(string path, FileMode mode, FileAccess access)
: base(path, mode, access) {
}

public void Load(MailMessage email) {
BindingFlags dwFlag = BindingFlags.Instance | BindingFlags.NonPublic;
Assembly assm = typeof(SmtpClient).Assembly;
using (MemoryStream ms = new MemoryStream()) {
try {
/************************************************************/ Type mailreaderType = assm.GetType("System.Net.Mail.MailReader");
/** ^ It looks like there is no such thing as MailReader ^ **/ ConstructorInfo mailreaderConstructor = mailreaderType.GetConstructor(dwFlag, null, new Type[] { typeof(Stream) }, null);
object mailReader = mailreaderConstructor.Invoke(new object[] { ms });
MethodInfo sendInfo = typeof(MailMessage).GetMethod("Send", dwFlag);
sendInfo.Invoke(email, dwFlag, null, new object[] { mailReader, true }, null);
MethodInfo closeInfo = mailReader.GetType().GetMethod("Close", dwFlag);
ms.WriteTo(this);
closeInfo.Invoke(mailReader, dwFlag, null, new object[] { }, null);
} catch (Exception err) {
throw err;
} finally {
ms.Close();
}
}
}

public void Save(MailMessage email) {
BindingFlags dwFlag = BindingFlags.Instance | BindingFlags.NonPublic;
Assembly assm = typeof(SmtpClient).Assembly;
using (MemoryStream ms = new MemoryStream()) {
try {
Type mailwriterType = assm.GetType("System.Net.Mail.MailWriter");
ConstructorInfo mailwriterConstructor = mailwriterType.GetConstructor(dwFlag, null, new Type[] { typeof(Stream) }, null);
object mailWriter = mailwriterConstructor.Invoke(new object[] { ms });
MethodInfo sendInfo = typeof(MailMessage).GetMethod("Send", dwFlag);
sendInfo.Invoke(email, dwFlag, null, new object[] { mailWriter, true }, null);
MethodInfo closeInfo = mailWriter.GetType().GetMethod("Close", dwFlag);
ms.WriteTo(this);
closeInfo.Invoke(mailWriter, dwFlag, null, new object[] { }, null);
} catch (Exception err) {
throw err;
} finally {
ms.Close();
}
}
}

}



GeneralGreat Article
daryl_au
12:46 2 Feb '09  
Thanks for sharing this code. It saved me a lot of time.
GeneralNice One !!!
ashu fouzdar
2:51 13 Jan '09  
This is really great work, congratulation for it.

Thanks for sharing Smile

dnpro
"Very bad programmer"

GeneralExcellent
RVW
22:43 12 Jan '09  
A very clever technique and nicely explained. You get my 5.
GeneralHow to read *.eml MailMessage file and send it out?
doggy_epaper.tw
6:12 12 Jan '09  
Hi Allan,

May I ask how to read *.eml file into MailMessage object and send it out?

Thanks.


GeneralRe: How to read *.eml MailMessage file and send it out?
Allan Eagle
7:30 12 Jan '09  
If you look up the SMTP protocol I am sure that you could send the contents of the .eml file to an SMTP server without too much hassle ... I am sure that is what SmtpClient does anyway ...
QuestionIt's free to change their ‘internal’ implementation of System.Net.Mail in future framework releases?
doggy_epaper.tw
6:05 12 Jan '09  
Hi Allan,

How do you know they are free to change their ‘internal’ implementation of System.Net.Mail in future framework releases?

Is this appear in .Net 4.0 ?


AnswerRe: It's free to change their ‘internal’ implementation of System.Net.Mail in future framework releases?
Allan Eagle
7:25 12 Jan '09  
Well, any developer that writes a class library containing classes marked as internal are marking them internal for the reason that they are not exposing this functionality outside their class library. Therefore as these are not public and intended ways of using the class library, the original developer is free to change anything marked as internal without affecting users of that library ...

I have no idea whether Microsoft will change anything here in their next releases but the point is it is possible ...
GeneralGreat article!
rht341
3:44 12 Jan '09  
Great information and excellent, detailed explanation. I may find some use for this, thanks!
GeneralNice!
roguewarrior
17:39 11 Jan '09  
I wish I had seen this about 4 years ago when I was using this object Smile

Nice job on tearing it apart!


Last Updated 11 Jan 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010