Introduction
In this article, I am going to consider the process of managing and sending emails inside a Web application. Almost every Web site has a feedback form or a registration form. After validating user's entries and doing some actions, it is necessary sometimes to send a notification email to the user or, for instance, to a moderator of the site. To achieve this goal, you can use the standard .NET classes (read more here and here if you are using .NET 2.0). But how can you manage permanent content of emails inside your Web application?
The simplest way is to hardcode email content and insert dynamic values using concatenating functions inside page's code. But what if you need to change design of email or add some functionality to the page source? Email template can be very big and contain a pile of dynamic variables with concatenation correspondingly. Looking into such source is horrible. Everybody knows it and in spite of this, even professional programmers often choose the way to place content email in the source code.
Summarizing all problems, you can be faced with:
- Source of page that sends email is hardly readable because there are a lot of long string literals and a lot of concatenate operations
- You cannot preview email template until you actually run the code
- It's necessary to recompile the application after even a small content change in email template
Solution
The solution of the problem is simple – using email templates and parser. For example, we need to send email to a user after registration is completed. This email must contain such information like first and last name to say hello and in addition we need to send login information too. Example template can be similar to this one:
Hello ##FirstName## ##LastName##,
You have been registered with the following information:
Login: ##Login##
Password: ##Password##.
To login use this link: www.server.com
##FirstName##
in the body of the message does mean a marker of place in the template where parser inputs a value of FirstName
variable. So we have four places where we need to place personal information for each user. Other contents are permanent. Also please notice that we can use HTML as content for template instead of plain text in the previous example:
<html>
<head>
<style>
p {
font-family: Verdana;
}
</style>
</head>
<body>
<p>Hello ##FirstName## ##LastName##,</p>
<p>You have been registered with the following information:</p><table>
<tr>
<td>Login: </td>
<td>##Login##</td>
</tr>
<tr>
<td>Password: </td>
<td>##Password##.</td>
</tr>
</table><p>To login use this link:
<a href="http://www.server.com/">www.server.com</a></p>
</body>
To use this template in your code, we need to save it in a separated file. I called it as Registration.htm and placed it in MailTemplates folder where I can manage all application mail templates. Now we can use the registration template in code:
Dim templateVars As New Hashtable()
templateVars.Add("FirstName", "Alexander")
templateVars.Add("LastName", "Kleshchevnikov")
templateVars.Add("Login", "seigo")
templateVars.Add("Password", "123pass")
Dim parser As New Parser_
(Server.MapPath("~/MailTemplates/Registration.htm"), templateVars)
Try
Dim message As New MailMessage()
message.From = New MailAddress("<a href="mailto:noreply@server.com">noreply@server.com</a>")
message.To.Add(New MailAddress("<a href="mailto:seigo.ua@via.com.ua">seigo.ua@via.com.ua</a>"))
message.Subject = "Registration"
message.BodyEncoding = System.Text.Encoding.UTF8
message.Body = parser.Parse
message.IsBodyHtml = True
Dim client As New SmtpClient()
client.Send(message)
Label1.Text = "Email has been sent."
Catch ex As Exception
Label1.Text = "Could not send email: " + ex.Message
End Try
The first key element of sending code is creating templateVars
object as instance of Hashtable
class from System.Collections
and pushing all mentioned variables in the template with key equal variable name. After that, we can create a parser object as an instance of Parser
class passing template path and hashtable with template variables as parameters to the constructor. To get a template with replaced values, you can simply invoke the Parse
method of the Parser
object.
To configure SMTP server settings, add <system.net>configuration section in web.config file:
<configuration>
...
<system.net>
<mailSettings>
<smtp deliveryMethod="Network">
<network defaultCredentials="true" host="localhost" port="25" />
</smtp>
</mailSettings>
</system.net>
...
</configuration>
For more information, read this MSDN article.
Modificators
Now let's imagine you need to replace all unsafe HTML special chars with HTML-encoded equivalents to prevent a user from using tags in first name or somewhere and destroy view of HTML email message. Of course, you can use Server.HTMLEncode()
method and rewrite adding variables in the hashtable:
templateVars.Add("FirstName", Server.HtmlEncode("Alexander"))
templateVars.Add("LastName", Server.HtmlEncode("Kleshchevnikov"))
templateVars.Add("Login", Server.HtmlEncode("seigo"))
templateVars.Add("Password", Server.HtmlEncode("123pass"))
But after that, we need to recompile the application. And it will hang up application performance if we need to make it on the server directly. Another approach is using value modificators – simple functions which have variable value as one of the parameters. For example:
...
<p>Hello ##FirstName:HTMLENCODE## ##LastName:HTMLENCODE##,</p>
...
Now if the user inputs "<b>Alex</b>
" as first name, the value actually will be inserted in the template as "<b>Alex</b>
". And we do not need to change and recompile application sources. In the table below, you can find all built-in value modificators with a short functional description:
NL2BR | Replace all new line chars with <br /> tag. |
HTMLENCODE | Change all unsafe HTML special chars with HTML-encoded equivalents. |
UPPER | Change string to uppercase. |
LOWER | Change string to lowercase. |
TRIM | Remove all spaces from right and left side. |
TRIMEND | Remove spaces from end only. |
TRIMSTART | Remove spaces from start only. |
If it is not enough for your claims, you can create custom value modificators. For instance, if you need to create a modificator to change only the first letter to upper (to represent names) you can create such a class:
Imports System
Imports System.Collections
Namespace TemplateParser.Modificators
Class NL2BR
Inherits Modificator
Public Overrides Sub Apply(ByRef Value As String, _
ByVal ParamArray Parameters() As String)
Value = Value.ToUpper()(0) + Value.ToLower().Substring(1)
End Sub
End Class
End Namespace
Compile it in assembly and place in the bin folder. Using Reflection Parser
object will determine that you declared a new custom FIRSTUPPER
modificator and will use it as soon as it finds a link to it from the template.
Conditional Statements
Next I am going to explain how to use conditions in email templates. There can be such a situation when depending on some variable value we need to use one or the other block of template. A simple example is show "Please sign in" message if user is not logged into a system or show greeting with full name in other case:
##If--IsRegisteredUser##
<p>Hello, ##FirstName## ##LastName##!</p>
##Else--IsRegisteredUser##
Please sign in.
##EndIf--IsRegisteredUser##
IsRegisteredUser
must contain a boolean variable which determines user login status. Please notice also that you can use variables FirstName
and LastName
in conditional blocks.
Blocks and Loops
Finally, I want to show how to display a list of data in template using Block declaration. You can be faced with a problem when you need to place some list of data in a template which rows you need to generate in code. For example - list of links like this one:
<ul>
<li><a href="http://www.codeproject.com">Codeproject</a></li>
<li><a href="http://www.msdn.com">MSDN</a></li>
<li><a href="http://www.google.com">Google</a></li>
</ul>
The problem is we do not know how many links we have but it's clear that we can declare iterative block and use it in a loop. In the following situation, such block is <li>
tag and everything that it includes. So we can put it in a Block
:
##BlockBegin--Link##
<li><a href="##Url##">##Title##</a></li>
##BlockEnd--Link##
I called Block
as "Link" and replaced two areas with ##Url##
and ##Title##
variables. Now I can iterate through Hashtable with links data and create template block with a list of all links:
Dim links As New Hashtable()
links.Add("Google", "http://www.google.com/")
links.Add("Codeproject", "http://www.codeproject.com/")
links.Add("MSDN", "http://www.msdn.com/")
Dim linksBlock As New StringBuilder()
Dim myEnumerator As IDictionaryEnumerator = links.GetEnumerator()
While myEnumerator.MoveNext()
Dim blockVars As New Hashtable()
blockVars.Add("Url", myEnumerator.Value)
blockVars.Add("Title", myEnumerator.Key)
linksBlock.Append(parser.ParseBlock("Link", blockVars))
End While
To get parsed content of each link, I use the ParseBlock()
method. The first parameter is the name of the Block
and the second is Hashtable
of variables which is needed to replace in the current Block
.
Summary
Using template parser, you can simply store and manage all application email templates in one place. This approach helps separate content from code sources and gives you the ability to edit them easily.
History
- 16/8/2006 - Some fixes - Added ability to declare and use Blocks and use them to create a list of data. Convert sources of .NET 2.0 version to VB.NET.
- 7/29/2007 - Added sources for .NET 1.1.