Article of the day at ASP.NET (january 13th, 2010)
This is a natural evolution of a Previous Post about using UserControls as mail templates.
Here I will show a way to code a reusable helper class to:
- create a MailMessage
- set its body by eventually using a paremeterized template control
- automatically embed images as LinkedResources
- send the MailMessage using the desired SMTP server
Full source control is available at the bottom of this article.
Let's give a look to the main parts of the code.
In the previous post, I already showed how to use a UserControl as a mail template.
The Mailer class uses the same technique, but I've added something new about template's parameters:
the previous article showed how to use an interface to set parameters on the template control. Here we will use reflection instead.
The Mailer class has, among others, two Properties (and corresponding private fields) to hold a reference to the template control and a generic dictionary of key-value pairs for template's parameters:
/// <summary>
/// Gets or sets a reference to a Control to be used as template
/// </summary>
public Control TemplateControl
{
get { return _templateControl; }
set { _templateControl = value; }
}
/// <summary>
/// Gets or sets a Dictionary to hold parameters for the template control
/// </summary>
public Dictionary<string, object> MailTemplateParameters
{
get { return _mailTemplateParameters; }
set { _mailTemplateParameters = value; }
}
Here is the Mailer's only method, which does all the magic:
/// <summary>
/// Sends the e-mail
/// </summary>
public void Send()
{
// checks if a template control is set
if (_templateControl != null)
{
// gets the control's Type
Type templateType = _templateControl.GetType();
// sets properties from the dictionary using reflection
foreach (KeyValuePair<string, object> parameter in MailTemplateParameters)
templateType.GetProperty(parameter.Key).SetValue(_templateControl, parameter.Value, null);
// renders the template control as a string
StringBuilder stringBuilder = new StringBuilder();
TextWriter textWriter = new StringWriter(stringBuilder);
HtmlTextWriter htmlWriter = new HtmlTextWriter(textWriter);
_templateControl.RenderControl(htmlWriter);
_mailMessage.Body = stringBuilder.ToString();
// release resources
htmlWriter.Close();
htmlWriter.Dispose();
textWriter.Dispose();
}
// embeds images for html messages
if (_mailMessage.IsBodyHtml)
{
ImagesEmbedder embedder = new ImagesEmbedder();
embedder.EmbedImages(_mailMessage);
}
// sends the message
SmtpClient client = new SmtpClient(_mailServer);
client.Send(_mailMessage);
// release resources
_mailMessage.Dispose();
_mailTemplateParameters.Clear();
}
It starts checking whether a template control was set. If so, it enumerates all the key-value pairs within the dictionary and sets corresponding values to the control's properies.
This means, we could use the following sample UserControl as template:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Mail.ascx.cs" Inherits="Mail" %>
<html>
<head>
<title></title>
</head>
<body>
<img src="Images/MailBanner.jpg" alt="" /><br />
Hello <%= Username %>, this is a templated e-mail for you!
</body>
</html>
With its corresponding codebehind:
using System.Web;
using System.Web.UI;
public partial class Mail : System.Web.UI.UserControl
{
#region Public properties
public string Username { get; set; }
#endregion
}
This template uses a single property: "Username". We would use this template with the Mailer class this way:
// load the template control
Control templateControl = LoadControl("Mail.ascx");
// create mailer object
Mailer mailer = new Mailer();
// set the template control
mailer.TemplateControl = templateControl;
// add parameter to the dictionary
mailer.MailTemplateParameters.Add("Username", "Code Golem");
// set smtp server
mailer.MailServer = "mysmtpserver";
// specify it's an html message
mailer.MailMessage.IsBodyHtml = true;
// set from, to, subject
mailer.MailMessage.Subject = "Test template message";
mailer.MailMessage.From = "codegolem@codegolem.com";
mailer.MailMessage.To.Add(new MailAddress("user@hisdomain.com"));
// send the message
mailer.Send();
This is one step forward to using specific interfaces for each template!
Now, the other interesting part: image embedding.
This is done through another helper class: the ImagesEmbedder.
It has a single private field to store the messages linked resources:
/// <summary>
/// Holds a list of linked resources
/// </summary>
List<LinkedResource> _linkedResources;
And a single public method to perform embedding:
/// <summary>
/// Embeds images in the MailMessage
/// </summary>
/// <param name="mailMessage">MailMessage instance</param>
public void EmbedImages(MailMessage mailMessage)
{
// creates a new list of linked resources
_linkedResources = new List<LinkedResource>();
// regular expression to find <img> tags within the message's body
Regex regex = new Regex("src=(?:\"|\')?(?<imgSrc>[^>]*[^/].(?:jpeg|jpg|bmp|gif|png|tif))(?:\"|\')?");
// replaces urls in <img> tag with corresponding unique ids
string body = regex.Replace(mailMessage.Body, imageHandler);
// creates an alternate view with linked resources
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(body, null, "text/html");
foreach (LinkedResource linkedResource in _linkedResources)
htmlView.LinkedResources.Add(linkedResource);
mailMessage.AlternateViews.Add(htmlView);
// release resources
_linkedResources.Clear();
_linkedResources = null;
}
This goes through the message's body looking for <img> tags using regular expressions. Each match is handled by the following delegate:
/// <summary>
/// Updates imgs' src and adds linkedResources to the list
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
private string imageHandler(Match match)
{
// gets the imgs src attribute from the Regular Expression's match
string src = match.Groups[1].Value;
Uri uri = null;
// try to create a valid Uri by the img's src
try
{
uri = new Uri(src);
}
catch
{
}
LinkedResource linkedResource = null;
// if it's a valid, absolute Uri, download the image from remote source using a WebClient request
if (uri != null && uri.IsAbsoluteUri)
{
WebClient webClient = new WebClient();
try
{
byte[] buf = webClient.DownloadData(uri);
MemoryStream memoryStream = new MemoryStream(buf);
linkedResource = new LinkedResource(memoryStream);
}
catch
{
}
finally
{
webClient.Dispose();
}
}
else
// otherwise, get the image from local server
linkedResource = new LinkedResource(HttpContext.Current.Server.MapPath(src));
if (linkedResource != null)
{
// add the linked resource to the list
linkedResource.ContentId = Guid.NewGuid().ToString();
linkedResource.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
_linkedResources.Add(linkedResource);
// update the img's src attribute with the linked resource's unique id
return string.Format("src='cid:{0}'", linkedResource.ContentId);
}
else
// leave src unchanged if we can't get the image
return match.Value;
}
This delegate tries to understand if the img src attribute point to an absolute Uri (I didn't find any better way than trying to create an Uri object and catching exceptions when it's not a valid Uri string).
If it's a remote image, it's downloaded using a WebClient request.
The downloaded (or local) image is then used to create a LinkedResource, which is added to the list.
The ImageEmbedder finally creates an alternate view and stores all LinkedResources from the list.
The embedder is invoked from the Mailer class before sending the e-mail.
So we can reuse the Mailer class to send templated e-mails with few lines of code in the calling application.
Here is the full source code for the Mailer and ImagesEmbedder classes.
CodeGolem_Mailer.zip (4.19 kb)
Happy image-embedding-template-mailing! 