|
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);
Very useful thanks.
Posted by: Tom Robinson at February 19, 2004 06:35 AMJust two words: Absolutly great! Thanks!
Posted by: Christian Cigler at April 19, 2004 02:16 AMOh 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
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
asef
Posted by: hardless at April 19, 2004 09:32 AMThis 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.
it is a very interesting material. Thanks
Posted by: Mounir Mhamdi at April 19, 2004 12:34 PMThis 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 PMResponse 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)
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 PMThe 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 PMYes, vb code doesnt work. It should be as following with explicitly converting:
Dim emailTemplate As ForgotPasswordEmail = CType(LoadControl("~/ForgotPasswordEmail.vb.ascx"),ForgotPasswordEmail )
very good!
I am looking forword so good topic every day.
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 AMdd
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 AMI get the error: - Any suggestions?
Type 'ForgotPasswordEmail' is not defined.
Source Error:
Dim emailTemplate As ForgotPasswordEmail = CType(LoadControl
"~/Controls/EmailTemplates/ForgotPasswordEmail.ascx"),ForgotPasswordEmail)
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
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?
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 PMdsadsa
Posted by: x at April 20, 2004 09:41 PMHi 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 AMHi 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 AMthank you !!
very good.
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 PMWow, great idea. Very insightful. 8)
Posted by: James White at April 22, 2004 05:13 PMthanks 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?
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
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
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 AMGreat 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 PMsweet
Posted by: Chayz at June 22, 2004 02:57 PMWhat 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


