Wednesday, 15 August 2018

VB.NET Extending the MailMessage to have the ability to save the mail message

Problem:

The System.Net.Mail.MailMessage is badly missing the option of saving the Message as a file. Though there is a workaournd by using the SpecifiedPickupDirectory option of the SmtpClient to write the mail message ot the specific folder. This option however doesn't have flexibility in naming the files. In a multi-threaded parallel execution environment, it is difficult to identify and rename the specific files post writing to disk. There ought to be a better solution to handle this problem.

Solution:

You can extend the MailMessage class and add a Save method yourself. The Send method of the SmtpClient creates and uses MailWriter object to write out the mail message and it uses fileMailWriter when the SpecificPickupDirectory is specified as the DeliveryMethod. Create your own MailWriter object using FileStream and invoke the internal Send method, passing your MailWriter object. Check out this Code Project Article for more details. The code samples given there are for the C# language. Just in case you need, given below the same code for the Extension in VB.NET.


Imports System
Imports System.Net.Mail
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.IO
Imports System.Reflection
Imports System.Runtime.CompilerServices

Module MailMessageExtension
    <Extension()>
    Public Sub Save(ByVal msg As MailMessage, FileName As String)

        Dim asm As Assembly = New SmtpClient().GetType().Assembly
        Dim _mailWriterType As Type = asm.GetType("System.Net.Mail.MailWriter")


        Using _fileStream As FileStream = New FileStream(FileName, FileMode.Create)


            ' Get reflection info for MailWriter contructor
            Dim _mailWriterContructor As ConstructorInfo = _mailWriterType.GetConstructor(BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Type() {GetType(Stream)}, Nothing)

            ' Construct MailWriter object with our FileStream
            Dim _mailWriter As Object = _mailWriterContructor.Invoke(New Object() {_fileStream})

            ' Get reflection info for Send() method on MailMessage
            Dim _sendMethod As MethodInfo = New MailMessage().GetType().GetMethod("Send", BindingFlags.Instance Or BindingFlags.NonPublic)


            ' Call method passing in MailWriter

            _sendMethod.Invoke(msg, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Object() {_mailWriter, True, True}, Nothing)

            ' Finally get reflection info for Close() method on our MailWriter
            Dim _closeMethod As MethodInfo = _mailWriter.GetType().GetMethod("Close", BindingFlags.Instance Or BindingFlags.NonPublic)

            ' Call close method
            _closeMethod.Invoke(_mailWriter, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Object() {}, Nothing)
        End Using

    End Sub
End Module


You may observe that you need to create extensions as a module.