Useful for you?
In this post I will show you how I did this zip and email library that helps me to email a file after zip it. There are methods for zipping files and for attaching files to an email and send it through smtp, but, do we really need to save the zipped file before sending it? In this post I will show you how I used the memoryStream to temporarily persist the zipped file and then use this memory to attach the file in an email.The Projects
First, we will create the required projects and their references in our working area, we will create our vb.Net solution with 2 projects:- mailSender: Is a windows forms application that will help us to configure and provide the details for the email that will be sent. This will be the startup project for our solution.
- Mailer: Is a class library project that will provide the methods to zip and email our attachments. I've set the default package of this class library to dtorres.Mailer
mailSender Windows Forms
In the mailSender project, we will create a form that might look like this one:In the configuration tab we will include the email configuration fields, and will default their values to our email configuration:
Finally, a control not seen in this screenshots is a OpenFileDialog that will assist us to browse the file sytem contents in order to create our attachment when clicking the browse button. I've named this form FormMailSender.
We can start programming our browse button to explore and set the attachment path in our text box assigned for that purpose, I've included the following behavior in the browse button:
Private Sub ButtonBrowse_Click(sender As System.Object, e As System.EventArgs) Handles ButtonBrowse.Click
OpenFileDialogAttachment.FileName = ""
Dim dr As DialogResult = OpenFileDialogAttachment.ShowDialog()
If (dr = Windows.Forms.DialogResult.OK) Then
TextBoxAttachmentPath.Text = OpenFileDialogAttachment.FileName
End If
End Sub
We will also provide some validation for the data that we are sending to our api for attachment zipping and email:
A validation for email patterns:
Private Function Is_Email(ByVal value As String) As Boolean
Dim pattern As String = "^[a-zA-Z][\w.-][a-zA-Z0-9]@[a-zA-Z0-9][\w.-][a-zA-Z0-9].[a-zA-Z][a-zA-Z.]*[a-zA-Z]$"
Dim patternMatch As Match = Regex.Match(value, pattern)
Is_Email = patternMatch.Success
End Function
A validation for the provided configuration:
Private Function Validate_Configuration() As Boolean
Validate_Configuration = True
If Not Is_Email(TextBoxEmail.Text) Then
Return False
End If
If TextBoxUserName.Text = "" Then
Return False
End If
If TextBoxPassword.Text = "" Then
Return False
End If
If TextBoxHost.Text = "" Then
Return False
End If
If TextBoxPort.Text = "" Or Not IsNumeric(TextBoxPort.Text) Then
Return False
End If
End Function
A validation for the email content:
Private Function Validate_Email() As Boolean
Validate_Email = True
If Not Is_Email(TextBoxTo.Text) Or TextBoxSubject.Text = "" Then
Validate_Email = False
End If
If TextBoxCc.Text <> "" And Not Is_Email(TextBoxCc.Text) Then
Validate_Email = False
End If
End Function
Mailer
Mailer is the class library project that we will use to build the email and its zipped attachment. In order to have the ability to zip attachments, we will need the reference to WindowsBase library. Is a .NET library that you can find also in your equivalent path to my library path, depending on your installed version and OS: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\WindowsBase.dll. First thing: add the WindowsBase library to your Mailer project and add the Mailer project reference to the mailSender windows forms project.Next, we will create some utility elements that will be used to communicate between the mailSender and the Mailer:
An exception type (MailerException).- This exception type will be thrown each time we want to communicate an exception to the client, in this case, the mailSender project.
Public Class MailerException
Inherits Exception
Sub New(ByVal ErrMsg As String)
MyBase.New(ErrMsg)
End Sub
Sub New(ByVal ErrMsg As String, ByVal innerException As Exception)
MyBase.New(ErrMsg, innerException)
End Sub
End Class
A configuration type (MailerConfig).- This class will state the contract that should be filled in order to configure the smtp server properties that will help us to send the email. The configuration for now, includes the following properties:
- From_Email
- User_name
- Password
- Host
- Port
- SSL
Public Class MailerConfig
Dim _from_email As String
Dim _user_name As String
Dim _password As String
Dim _host As String
Dim _port As Long
Dim _ssl As Boolean = False
Public Property From_Email As String
Get
Return _from_email
End Get
Set(value As String)
_from_email = value
End Set
End Property
Public Property User_Name As String
Get
Return _user_name
End Get
Set(value As String)
_user_name = value
End Set
End Property
Public Property Password As String
Get
Return _password
End Get
Set(value As String)
_password = value
End Set
End Property
Public Property Host As String
Get
Return _host
End Get
Set(value As String)
_host = value
End Set
End Property
Public Property Port As Long
Get
Return _port
End Get
Set(value As Long)
_port = value
End Set
End Property
Public Property SSL As Boolean
Get
Return _ssl
End Get
Set(value As Boolean)
_ssl = value
End Set
End Property
End Class
An email details type (EmailDetails).- This class will state the details of the email that we will construct and send. The details that we will specify, include the following properties:
- Email_to
- Email_cc
- Email_subject
- Email_body
- Attachment_path
Public Class EmailDetails
Dim _to As String
Dim _cc As String
Dim _subject As String
Dim _body As String
Dim _attachment_path As String
Public Property Email_to As String
Get
Return _to
End Get
Set(value As String)
_to = value
End Set
End Property
Public Property Email_cc As String
Get
Return _cc
End Get
Set(value As String)
_cc = value
End Set
End Property
Public Property Email_subject As String
Get
Return _subject
End Get
Set(value As String)
_subject = value
End Set
End Property
Public Property Email_body As String
Get
Return _body
End Get
Set(value As String)
_body = value
End Set
End Property
Public Property Attachment_path As String
Get
Return _attachment_path
End Get
Set(value As String)
_attachment_path = value
End Set
End Property
End Class
Now we will define the methods to zip and email, those methods will be defined in a class that I will call CMailer. The CMailer class will be our library that will publish a method to handle the email sending and will contain the methods required to read the attachment file, zip it and email it. The method that we will define to send the email will be: Public Sub Send_Email(ByVal config As MailerConfig, ByVal details As EmailDetails). You can add this class with this sole method in the library to begin the integration in this way:
First we will add the class to our Mailer project:
Public Class CMailer
Public Sub Send_Email(ByVal config As MailerConfig, _
ByVal details As EmailDetails)
Throw New MailerException("Not implemented yet!")
End Sub
End Class
Now we will define the action for the send button at the mailSender project, first we will need a few more private helper methods, one to define the configuration object and one to define the email object. In the FormMailSender code add the following private methods:
Get_Mailer_Config(): Defines the configuration object using the provided values in the config tab controls of the form:
Private Function Get_Mailer_Config() As MailerConfig
Dim config As New MailerConfig()
config.From_Email = TextBoxEmail.Text
config.Host = TextBoxHost.Text
config.Password = TextBoxPassword.Text
config.Port = Long.Parse(TextBoxPort.Text)
config.SSL = CheckBoxSSL.Checked
config.User_Name = TextBoxUserName.Text
Return config
End Function
Get_Email_Details(): Defines the email content details using the provided values in the email tab controls of the form:
Private Function Get_Email_Details() As EmailDetails
Dim details As New EmailDetails()
details.Attachment_path = TextBoxAttachmentPath.Text
details.Email_body = TextBoxBody.Text
details.Email_cc = TextBoxCc.Text
details.Email_subject = TextBoxSubject.Text
details.Email_to = TextBoxTo.Text
Return details
End Function
Clean_Details(): Resets the values of the email content:
Private Sub Clean_Details()
TextBoxAttachmentPath.Text = ""
TextBoxTo.Text = ""
TextBoxCc.Text = ""
TextBoxSubject.Text = ""
TextBoxBody.Text = ""
End Sub
Now, we will integrate our code in the CMailer class by adding the behavior of the send button click:
Private Sub ButtonSend_Click(sender As System.Object, e As System.EventArgs) Handles ButtonSend.Click
If Not Validate_Configuration() Then
MessageBox.Show("Email configuration is not well formed", "Email Configuration Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
If Not Validate_Email() Then
MessageBox.Show("Email details are not well formed", "Email Details Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
Cursor = Cursors.WaitCursor
Try
Dim mailer As New CMailer()
mailer.Send_Email(Get_Mailer_Config(), Get_Email_Details())
MessageBox.Show("Email successfully sent", "Fine", MessageBoxButtons.OK, MessageBoxIcon.Information)
Clean_Details()
Catch ex As Exception
MessageBox.Show("Unable to send the email due to the following exception:" & _
vbCr & ex.Message, "Error Sending Email", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
Cursor = Cursors.Default
End Try
End Sub
For now, this program will always throw for you the error message that the Send_Email method hasn't be implemented yet. Next we will implement the zip and email methods.
Zip And Email
Lets get back to our CMailer class and provide some private methods that will help us with the zip and email activities. The first method that we will create is the Read_File(ByVal fileName As String) As Byte(). The Read_File(ByVal fileName As String) As Byte() method will read a file from the system storage and keep its bytes in an array:
Private Function Read_File(ByVal fileName As String) As Byte()
If Not File.Exists(fileName) Then
Throw New MailerException("File Not Found: [" & fileName & "]")
End If
Try
Using fs As FileStream = New FileStream(fileName, FileMode.Open, FileAccess.Read)
Read_File = New Byte((fs.Length) - 1) {}
Dim bytesToRead As Integer = CType(fs.Length, Integer)
Dim bytesRead As Integer = 0
While (bytesToRead > 0)
Dim n As Integer = fs.Read(Read_File, bytesRead, bytesToRead)
' Check EOF To break the look
If n = 0 Then
Exit While
End If
bytesRead = bytesRead + n
bytesToRead = bytesToRead - n
End While
End Using
Catch ex As Exception
Throw New MailerException("Exception while reading the file [" & _
fileName & "]: [" & ex.Message & "]", ex)
End Try
End Function
The Sub zipFile(ByRef zip As Package, ByVal fBytes As Byte(), ByVal fileName As String) method will zip a provided byte array into a zip file:
Private Sub zipFile(ByRef zip As Package, ByVal fBytes As Byte(), ByVal fileName As String)
Dim zipUri As String = String.Concat("/", fileName)
Dim partUri As New Uri(zipUri, UriKind.Relative)
Dim contentType As String = Net.Mime.MediaTypeNames.Application.Zip
Dim pkgPart As PackagePart = zip.CreatePart(partUri, contentType, CompressionOption.Normal)
pkgPart.GetStream().Write(fBytes, 0, fBytes.Length)
End Sub
Please note the usage of the fileName, you will need to provide a uri that specifies a relative path inside the zip where the file will be placed.
Now we will provide the behavior to the Public Sub Send_Email(ByVal config As MailerConfig, ByVal details As EmailDetails) previously defined in our CMailer class:
Public Sub Send_Email(ByVal config As MailerConfig, _
ByVal details As EmailDetails)
' Read File
Dim b As Byte() = Read_File(details.Attachment_path)
Dim fileName As String = Path.GetFileName(details.Attachment_path).Replace(" ", "_")
' Zip File
Dim ms As New MemoryStream()
Dim zip As Package = ZipPackage.Open(ms, IO.FileMode.Create)
zipFile(zip, b, fileName)
zip.Close()
' Create Email
Dim msg As MailMessage = Create_Email(details)
msg.From = New MailAddress(config.From_Email)
' Create Attachment with zip memory stream
Dim ct As New ContentType()
ct.MediaType = MediaTypeNames.Application.Zip
ct.Name = Path.GetFileNameWithoutExtension(fileName) & ".zip"
ms.Seek(0, SeekOrigin.Begin)
Dim att As New Attachment(ms, ct)
msg.Attachments.Add(att)
' Connect and send through smtp
Dim SmtpServer As New SmtpClient()
SmtpServer.Credentials = New Net.NetworkCredential(config.User_Name, config.Password)
SmtpServer.Host = config.Host
SmtpServer.Port = config.Port
SmtpServer.EnableSsl = config.SSL
SmtpServer.Send(msg)
ms.Close()
ms.Dispose()
End Sub
Note how I'm using the MemoryStream object (ms) to create the zip package in the line: Dim zip As Package = ZipPackage.Open(ms, IO.FileMode.Create), this memory stream is fully written when closing the zip object. Then we use the same object to create the attachment in the line: Dim att As New Attachment(ms, ct). Please note that we have to position the cursor at the begin of the stream before using it to provide the attachment.
In the comments I will provide the link to the source code once I upload it to a public repository.
Cheers.
Check the source code in gitHub: https://github.com/diego-torres/mailSender
ResponderBorrar