Who Are We?

Fluent Consulting is a software development and consulting firm that specializes in enterprise application integration, web applications, and software product development. We are a dedicated team focused on providing the highest level of quality and value for our clients.

Please feel free to visit our corporate site or get in touch!




Creating Templated Emails

Often we need to generate email responses from our ASP.NET applications. Users forget their passwords, need email confirmations, or we'd just like to send them a friendly thank you for signing up.

Now you could create that email message with a nice series of string concatenations, or some fancy xml/xslt transformation. But who needs that when there is an easier way using what's available from our aspx parser?

First thing to create is a typical user control. This will be your template.

ForgotPasswordEmail.ascx
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="ForgotPasswordEmail.ascx.cs" Inherits="EmailTemplateExample.ForgotPasswordEmail" %>
Here is your password reminder. 

-----------------------------------
YOUR ACCOUNT INFORMATION:

Login: <%= Username %>
Password: <%= Password %>
-----------------------------------

TO CHANGE YOUR ACCOUNT INFORMATION:
You can change your account information by logging in and selecting 'Account Information'.

This is an automated message; please do not reply to this email.
ForgotPasswordEmail.ascx.cs
namespace EmailTemplateExample {

    public abstract class ForgotPasswordEmail : System.Web.UI.UserControl {

        public string Username;
        public string Password;

    }
}
Once you have created the template, using it only takes a few lines of code.
using System;
using System.IO;
using System.Web.UI;
using System.Web.Mail;

...

//Load the user control
ForgotPasswordEmail emailTemplate = (ForgotPasswordEmail)LoadControl("~/ForgotPasswordEmail.ascx");
            
emailTemplate.Username = "jroberts";
emailTemplate.Password = "a7e945";

//Render the user control to a string
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
emailTemplate.RenderControl(htmlWriter);
string body = stringWriter.ToString();

//Send the email
SmtpMail.SmtpServer = "localhost";
SmtpMail.Send("system@localhost","jroberts@localhost",
    "Password Reminder", body);
Your template could have more complex behavior, having the full power of the ASP.NET user control model. Additionally, using html in your user control to create html emails only requires properly setting the format of the MailMessage.
//Prepare HTML email
MailMessage mailMessage = new MailMessage();
mailMessage.From = "system@localhost"; 
mailMessage.Subject = "Password Reminder";
mailMessage.BodyFormat = MailFormat.Html;
mailMessage.UrlContentBase = "http://localhost/";

//Send the email
SmtpMail.SmtpServer = "localhost";
SmtpMail.Send(mailMessage);

This entry was posted in the following categories: .NET
Posted by mlehman at January 28, 2004 12:52 AM | TrackBack
Comments

Very useful thanks.

Posted by: Tom Robinson at February 19, 2004 06:35 AM

Just two words: Absolutly great! Thanks!

Posted by: Christian Cigler at April 19, 2004 02:16 AM

Oh it's very good,but i need .vb code
who can help me?
i wanner make a webform which can send mail in using vb lang
my Email :snowouldance@hotmail.com

Posted by: tony at April 19, 2004 05:08 AM

Look very nice.

However, is it wise to use response.write () to display the dynamic info in a control?
As I understand it, this will break the encapsulation provided by the page framework. Better perhaps to use literal controls to display the data....?

Cheers,
Mark

Posted by: Mark at April 19, 2004 06:32 AM

asef

Posted by: hardless at April 19, 2004 09:32 AM

This is perfect solution i was looking for.
I have developed my own COM object to create templated emails in ASP. But since i can not easily pass .NET objects to COM object i was looking for something similar for .NET. Only when i read this i understood how easily it can be done.

Posted by: Navel at April 19, 2004 10:29 AM

it is a very interesting material. Thanks

Posted by: Mounir Mhamdi at April 19, 2004 12:34 PM

This is one of those simple solutions to a common problem that makes me wonder, "why in the hell didn't I think of that."

Posted by: Vance at April 19, 2004 01:19 PM

Response to Tony, the following is the codes in VB.NET. Enjoy .NET!
Note: The author assumes the readers know the basic of how to create web application and user controls.

ForgotPasswordEmail.ascx (HTML edit mode)

Here is your password reminder.
-----------------------------------
YOUR ACCOUNT INFORMATION:
Login:
Password:
-----------------------------------
TO CHANGE YOUR ACCOUNT INFORMATION:
You can change your account information by logging in and selecting 'Account Information'. This is an automated message; please do not reply to this email.

ForgotPasswordEmail.ascx.vb

Enter the following codes( Public Username As String, Public Password As String) right below Public Class ForgotPasswordEmail Inherits System.Web.UI.UserControl
Like:
Public Class ForgotPasswordEmail
Inherits System.Web.UI.UserControl
Public Username As String
Public Password As String

On your WebForm
Import the following assembly:
Imports System
Imports System.IO
Imports System.Web.UI
Imports System.Web.Mail

Enter these codes in Page_Load module for testing:
Dim emailTemplate As ForgotPasswordEmail = LoadControl("~/ForgotPasswordEmail.ascx")
emailTemplate.Username = "jroberts"
emailTemplate.Password = "a7e945"

Dim stringWriter As StringWriter = New StringWriter
Dim htmlWriter As HtmlTextWriter = New HtmlTextWriter(stringWriter)
emailTemplate.RenderControl(htmlWriter)
Dim body As String = stringWriter.ToString()
SmtpMail.SmtpServer = "localhost"
SmtpMail.Send("system@localhost", _
"jroberts@localhost", _
"Password Reminder", _
body)

Posted by: EmailTemplate at April 19, 2004 01:31 PM

Another great example of the power of the object!

I am embarassed at how simple it is to get the html string of a control and do whatever I want with it.

.NET Lives! God bless objects.

Posted by: T at April 19, 2004 06:38 PM

The vb code doesnt work. there is no such thing as
Dim emailTemplate As ForgotPasswordEmail = LoadControl("~/ForgotPasswordEmail.ascx")

this is not possible.

Posted by: dhaval at April 19, 2004 07:12 PM

Yes, vb code doesnt work. It should be as following with explicitly converting:
Dim emailTemplate As ForgotPasswordEmail = CType(LoadControl("~/ForgotPasswordEmail.vb.ascx"),ForgotPasswordEmail )

Posted by: vb at April 19, 2004 07:50 PM

very good!
I am looking forword so good topic every day.

Posted by: jaky224 at April 20, 2004 12:14 AM

Nice but I think there are others way, and easy to do the same work, you can provide HTML templates (which design by Front Page...) with a set of predefined tags, at runtime, load this file and replace the tags with their values. Using this way you can seperate the templates from application code, I mean that your can edit the templates for their intention.

Posted by: Tien at April 20, 2004 12:58 AM

dd

Posted by: sss at April 20, 2004 03:25 AM

>However, is it wise to use response.write () to
>display the dynamic info in a control?
>As I understand it, this will break the
>encapsulation provided by the page framework.
>Better perhaps to use literal controls to
>display the data....?
>
>Cheers,
>Mark

The UserControl Class defines Request and Response properties.

Steve

Posted by: Steve at April 20, 2004 04:36 AM

I get the error: - Any suggestions?
Type 'ForgotPasswordEmail' is not defined.

Source Error:
Dim emailTemplate As ForgotPasswordEmail = CType(LoadControl
"~/Controls/EmailTemplates/ForgotPasswordEmail.ascx"),ForgotPasswordEmail)

Posted by: Marno at April 20, 2004 06:18 AM

IMO, this is more elegent and can be used in winforms apps OR webform ones. it requires a text template file which has tokens in it

a VB eg of usage is below

dim x as new TemplateParser
x.TemplateFile = "c:\whatever.htm"
x.Tokens = "Token1|Token2|Token3" 'seperate each token by a |
x.Values = "value1|value2|value3" 'seperate each value that replaces the corresponding tokens by a |

emailbody = x.GetParsedFile()

'then email off emailbody to whoever.


the TemplateParser is below.

--- start copying here ---

Imports System.IO

Public Class TemplateParser

Private pTemplateFile As String

Public Property TemplateFile() As String
Get
Return pTemplateFile
End Get
Set(ByVal Value As String)
If Value.IndexOf("\") = -1 Then
Throw New System.Exception("TemplateFile must be a full path and filename")
Else
pTemplateFile = Value
End If
End Set
End Property

Private pTokens() As String

Public WriteOnly Property Tokens() As String

Set(ByVal Value As String)
If Value.Length > 0 Then
pTokens = Value.Split("|")
Else
Throw New System.Exception("There must be at least 1 element in the Tokens array")
End If
End Set
End Property

Private pValues() As String

Public WriteOnly Property Values() As String

Set(ByVal Value As String)
If Value.Length > 0 Then
pValues = Value.Split("|")
Else
Throw New System.Exception("There must be at least 1 element in the Values array")
End If
End Set
End Property

Public Function GetParsedFile() As String

CheckArguments()

Dim FileContents As New System.Text.StringBuilder(), counter As Integer

If StripHead Then
FileContents = FileContents.Append(GetHTMLPageBody(GetFileContents(TemplateFile)))
Else
FileContents = FileContents.Append(GetFileContents(TemplateFile))
End If

For counter = LBound(pTokens) To UBound(pTokens)
FileContents = FileContents.Replace(pTokens(counter), pValues(counter))
Next

Return FileContents.ToString

End Function

Private Sub CheckArguments()
If TemplateFile = vbNullString Then
Throw New System.Exception("You have not specified a file to parse")
End If

If pValues.Length = 0 Then
Throw New System.Exception("There must be at least 1 element in the Values array")
End If

If pTokens.Length = 0 Then
Throw New System.Exception("There must be at least 1 element in the Tokens array")
End If
End Sub

Private Function GetFileContents(ByVal FileName As String) As String
Dim sr As StreamReader
Dim FileText As String

sr = File.OpenText(FileName)
FileText = sr.ReadToEnd
sr.Close()

Return FileText
End Function

Private Function GetHTMLPageBody(ByVal HTMLPage As String) As String
'this function returns all the text between the and tags of HTMLPage
Dim pos As Integer, endpos As Integer
pos = HTMLPage.ToLower.IndexOf("", pos)
endpos = HTMLPage.ToLower.IndexOf("", pos)

If endpos = -1 Then Return HTMLPage

Return HTMLPage.Substring(pos + 1, endpos - pos - 1)

End Function

Public StripHead As Boolean

Public Sub New()
StripHead = False
End Sub
End Class

Posted by: Rimu at April 20, 2004 06:23 AM

Nice little article - I have been toying with the idea of writing some templates and was thinking about an XML/XSLT solution. For eg:
Your templates are xslt files that render the XML however they want (so that handles the layout and UI side of it. The templated parses an XML document and populates the fields it needs... any thoughts from anyone on this? Is it too complicated?

Posted by: Rodney Joyce at April 20, 2004 12:07 PM

What if one was trying to do this from a class file and not from the page itself. How would you load the control?

Dim emailTemplate As ForgotPasswordEmail = CType(LoadControl("~/ForgotPasswordEmail.vb.ascx"),ForgotPasswordEmail )

how can this be done from a class file instead of directly from a page. I have a function in a class files which sends out emails. I want to load the template and render to html within the file. Can anyone help? thanks.

Posted by: dhaval at April 20, 2004 12:36 PM

dsadsa

Posted by: x at April 20, 2004 09:41 PM

Hi Rodney,

for me personally the xml/xslt is more extensibel than the ascx solution, especially if you are using some hundreds of templates, but the xml/xslt solution is much more work-intensive ...

Greetings

Stefan

Posted by: Stefan Walther at April 21, 2004 11:36 AM

Hi Stefan,

Instead of XML/XSLT, you could use the NVelocity template engine to implement template-driven emails.

http://nvelocity.sourceforge.net/

Posted by: Harvey Kandola at April 22, 2004 05:54 AM

thank you !!
very good.

Posted by: jaky224 at April 22, 2004 12:40 PM

Just what I was looking for.
One question, would it be possible to have the template contain the subject, format and other parameters..?

I am thinking of using this, but not making the template class abstact.

This way I can populate some standard parameters on each template, such as subject, htmlformat, etc, so I could pass those values to the sendmail call.

any thoughts?

thanks

Posted by: rod at April 22, 2004 02:19 PM

Wow, great idea. Very insightful. 8)

Posted by: James White at April 22, 2004 05:13 PM

thanks reply my question in
http://blog.fluentconsulting.com/archives/000042.html
i have a another problem.

if my mail from snowouldance@hotmail.com to snowood@hotmail.com
i dim SmtpMail.SmtpServer = "localhost"
but i found that it's hard to receive mail in snowood's mailbox or it's will use a long time
that's why?

Posted by: tony at April 23, 2004 05:35 AM

I ran into an issue with this way.

How do I include the images in the template? As they are not strings, so won't be treated as Text.

Any suggestions on that guys?

Thanks in advance.
Pankaj

Posted by: Pankaj at April 23, 2004 01:39 PM

Pankaj,
To include images in your email, set the format of the email to html as shown in the article. Then to support the most email clients make sure your image urls are absolute paths and set the UrlContentBase to the same server. You also can find more useful articles on html emails here: http://www.templatekit.com/articles.php?cat_type=A&cat_id=6

Posted by: Matt at April 23, 2004 06:47 PM

Here is a slight varation to load the template with data from a class. The class has a load method to extract the data from a table.


Private Sub Print1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Print1.Click

Dim emailTemplate As PressBriefClient = CType(LoadControl("~/EmailTemplates/PressBriefClient.ascx"), PressBriefClient)
Dim pressBrief As New PressBrief
pressBrief.Load(_projID)

Dim ContactName As Label = emailTemplate.FindControl("lblContactName")
ContactName.Text = pressBrief._clientContact
Dim ContactTel As Label = emailTemplate.FindControl("lblContactTel")
ContactTel.Text = pressBrief._clientContactTel
Dim ContactFax As Label = emailTemplate.FindControl("lblContactFax")
ContactFax.Text = pressBrief._clientContactFax
Dim ContactEmail As Label = emailTemplate.FindControl("lblContactEmail")
ContactEmail.Text = pressBrief._clientContactEmail
Dim CopyAvailability As Label = emailTemplate.FindControl("lblCopyAvailability")
CopyAvailability.Text = pressBrief._dateofCopy
Dim CreativeName As Label = emailTemplate.FindControl("lblCreativeName")
CreativeName.Text = pressBrief._creativeContact
Dim CreativeTel As Label = emailTemplate.FindControl("lblCreativeTel")
CreativeTel.Text = pressBrief._creativeContactTel
Dim CreativeFax As Label = emailTemplate.FindControl("lblCreativeFax")
CreativeFax.Text = pressBrief._creativeContactFax
Dim CreativeEmail As Label = emailTemplate.FindControl("lblCreativeEmail")
CreativeEmail.Text = pressBrief._creativeContactEmail
Dim Requirements As Label = emailTemplate.FindControl("lblRequirements")
Requirements.Text = pressBrief._regionalRequirments
Dim Budget As Label = emailTemplate.FindControl("lblBudget")
Budget.Text = pressBrief._budget
Dim Phasing As Label = emailTemplate.FindControl("lblPhasing")
Phasing.Text = pressBrief._budgetPhasing
Dim StartDate As Label = emailTemplate.FindControl("lblStartDate")
StartDate.Text = pressBrief._campaignStart
Dim EndDate As Label = emailTemplate.FindControl("lblEndDate")
EndDate.Text = pressBrief._campaignEnd
Dim Commision As Label = emailTemplate.FindControl("lblCommision")
Commision.Text = pressBrief._clientCommision
Dim Comments As Label = emailTemplate.FindControl("lblComments")
Comments.Text = pressBrief._clientCommision


Dim stringWriter As StringWriter = New StringWriter
Dim htmlWriter As HtmlTextWriter = New HtmlTextWriter(stringWriter)
emailTemplate.RenderControl(htmlWriter)

Dim body As String = stringWriter.ToString()
Dim WFMail As Mail = New Mail
WFMail.EmailBrief(_user.UserID, body)

End Sub

Posted by: Edward Waldron at April 26, 2004 07:42 AM

Great article, immensely helpful. I, too wanted to implement this from a class, but I also wanted to create a generic "Send Templated EMail" method where I could specify a template and parameters, so here's what I did:

I created an interface:

public interface ITemplatedEMail{
string Subject{ get; }
void SetParameters(ListItemCollection templateValues)
}

Then in the .ascx file I inherit from ITemplatedEMail and implement the Subject property and the SetParameters() method. The SetParameters() for the ForgotPassword example would simply be:

public void SetParameters(ListItemCollection items){
UserName = items.FindByText("UserName").Value.ToString();
Password = items.FindByText("Password").Value.ToString();
}

And the subject property would just be:
public string Subject{ get { return "Password Reminder";}}

The method that send the email is SendTemplatedEMail(string templateName,ListItemCollection valueCol)

I get the template path from the config and build it and then:

UserControl ctrl = new userControl();
ITemplatedEMail emailTemp = (ITemplatedEMail)ctrl.LoadControl(templateFullPath);
ctrl = (UserControl)emailTemp;
eMailTemp.SetParameters(valueCol);

The rest of the code is the same as the above example, except:

mailMessage.Subject=emailTemp.Subject.

The web page which sends the e-mail just looks like:

(create mail service object)
ListItemCollection col = new ListItemCollection();
col.Add(New ListItem("UserName","JohnSmith");
col.Add(New ListItem("Password","abc123");

The only drawback is that you have to know the names of the ListItems that you are going to set, but otherwise it works great. Wouldn't have been able to do it without this article, really the most useful thing I've seen in a long time and I've started implementing it everywhere. My app send out a TON of e-mails and I've always been jealous of those nicely formatted ones the big guys send out.

p.s.: I might be looking for a job soon.

Posted by: Peter Joyce at May 5, 2004 10:13 PM

sweet

Posted by: Chayz at June 22, 2004 02:57 PM

What happen if the user control you are loading has it's own databinding events i.e. page.load events. These are not fired when you load a control this way! I have tried to override the render event but with no success.

Reason I ask is that the control I'm trying to load binds data to a datagrid!

Posted by: Paul at June 29, 2004 08:39 AM


Copyright © 2002-2003 Fluent Consulting. All rights reserved.