Table of contents
This is the third in a series of articles on a class library for ASP.NET applications that I have developed. It contains a set of common, reusable page classes that can be utilized in web applications as-is to provide a consistent look, feel, and set of features. New classes can also be derived from them to extend their capabilities. The features are all fairly modular and may be extracted and placed into your own classes too. For a complete list of articles in the series along with a demonstration application and the code for the classes, see Part 1 [^].
For the e-mail part of the demo, you will need the SMTP service on the web server or access to a separate SMTP server. The error page demos use an e-mail address stored in the Web.config file that is currently set to a dummy address. You should modify the address specified by the ErrorRptEMail
key in the appSettings
section to make it valid. The e-mail page class can also use an optional configuration option to control the name of the SMTP server (EMailPage_SmtpServer
).
This article describes the features of the BasePage
class that allow it to e-mail its rendered content. This article will identify and describe the added features present in this class. The class can be used as-is, or the functionality can be extracted and added to your own page classes.
One common approach to e-mailing the rendered content of a page is to use the WebRequest
and WebResponse
objects as follows:
WebRequest wreq = System.Net.HttpWebRequest.Create(
"http://www.mysite.com/mypage.html");
WebResponse wrsp = wreq.GetResponse()
StreamReader sr = new StreamReader(wrsp.GetResponseStream())
string strHTML = sr.ReadToEnd()
sr.Close()
MailMessage msg = new MailMessage();
msg.From = "Somebody@Domain.com"
msg.To = "Anyone@Domain.com"
msg.Subject = "HTML page"
msg.Body = strHTML;
msg.BodyFormat = MailFormat.Html;
SmtpMail.Send(msg);
Another common approach is to override the page's Render
event and use code similar to the following:
StringBuilder sb = new StringBuilder();
HtmlTextWriter htw =
new HtmlTextWriter(new StringWriter(sb));
base.Render(htw);
string strHTML = sb.ToString();
MailMessage msg = new MailMessage();
msg.From = "Somebody@Domain.com"
msg.To = "Anyone@Domain.com"
msg.Subject = "HTML page"
msg.Body = strHTML;
msg.BodyFormat = MailFormat.Html;
SmtpMail.Send(msg);
writer.Write(strHTML);
While these methods work, they do have some limitations:
- The code must be added to each page that needs it, and they do not provide for easily extending or overriding the behavior of the e-mailing process.
- They may run into difficulties if the page being e-mailed takes parameters via some method other than the query string (i.e. via the page's
Context
object).
- Any relative URLs in the message body will most likely render as broken links, images, or missing style sheets when the recipient views the e-mail unless you take steps to fix them up.
- View state is still present in the message body unnecessarily increasing its size.
- Script blocks within the message body are still present and may cause security alerts in the recipient's e-mail client software unless you remove them.
The BasePage
class provides the following benefits:
- The e-mailing behavior is built into the page class so that you do not have to write all of the code to do it.
- The e-mailing behavior can be turned on or off using the
EMailRenderedPage
property. When disabled (the default), the page renders itself to the browser in the normal fashion. When enabled, the page attempts to e-mail a copy of itself in addition to rendering itself to the browser.
- The page raises an event that lets you customize or totally replace the HTML rendered to the client browser as well as the mail message parameters and the HTML content sent as the mail message's body text. This allows you to easily render content for the mail message and then display something totally different to the browser such as a confirmation that the message has been sent.
- The page raises an event if the e-mail cannot be sent thus letting you adjust the HTML rendered to the browser to indicate the problem or to take alternate action. Support is also built in to attempt a retry of the send after making adjustments to the e-mail such as changing the SMTP server used.
- All relative URLs within the mail message body are translated to absolute URLs to prevent broken links when viewed by the recipient.
- The HTML in the mail message body will have the view state removed to reduce its size.
- Script blocks are removed from the mail message body so as not to cause unnecessary warnings from the recipient's e-mail client software.
- Through the use of a custom comment tag, you can have the page remove unwanted sections of the page from the mail message body. This approach can be extended in derived classes to further alter the message body and the HTML rendered to the browser.
The class consists of a property called EMailRenderedPage
that controls whether or not the page will attempt to e-mail itself, an IsRenderingForEMail
property that can be checked to see if the page is currently in the process of rendering itself in preparation for e-mailing, an EMailThisPage
event that lets you customize all aspects of the e-mail, an EMailError
event that lets you handle situations in which an error occurs and the e-mail could not be sent, an overridden Render
method, and a RenderForEMail
method that makes it all happen.
This property is set to false
by default so that the page renders itself to the browser in the normal fashion. You can set it to true
in the constructor, the Page_Load
event, or an event handler for a derived page, to have it attempt to e-mail its rendered content.
The overridden Render
method controls the e-mailing process. In all likelihood, you will never override this method. Instead, you can control the e-mail details and rendered content by adding handlers for the EMailThisPage
and the EMailError
events. Note that the BasePage
class' version of this method also calls the ConvertValMsgsToLinks
method that converts validation messages to links. This is described in part four. The IsRenderingForEmail
property is checked to see if we are already rendering for e-mailing. If so, it simply renders the page as normal. If not, it checks the EMailRenderedPage
property and, if necessary, calls the RenderForEMail
method to handle all of the work:
protected override void Render(HtmlTextWriter writer)
{
if(!this.IsRenderingForEMail)
{
this.ConvertValMsgsToLinks();
if(this.EMailRenderedPage)
{
this.RenderForEMail(writer);
return;
}
}
base.Render(writer);
}
This method handles all of the details of rendering the page, e-mailing it, and handling any errors that might occur. Note that the code shown below is from the .NET 1.1 version of the class. The .NET 2.0 version is almost identical except for a few minor changes with regard to how the e-mail is sent. In .NET 2.0, it makes use of the new System.Net.Mail
classes to send the e-mail:
protected void RenderForEMail(HtmlTextWriter writer)
{
StringBuilder sb = new StringBuilder();
HtmlTextWriter hw = new HtmlTextWriter(
new StringWriter(sb, CultureInfo.InvariantCulture));
try
{
isRenderingForEMail = true;
this.Render(hw);
}
finally
{
isRenderingForEMail = false;
}
MailMessage msg = new MailMessage();
msg.BodyFormat = MailFormat.Html;
msg.Body = sb.ToString();
EMailPageEventArgs args =
new EMailPageEventArgs(msg, this);
args.SmtpServer = ConfigurationSettings.AppSettings[
"EMailPage_SmtpServer"];
this.OnEMailThisPage(args);
The first thing that occurs is to render the page to a StringBuilder
. This is accomplished by setting the IsRenderingForEMail
property to true
and calling the Render
method again with an HTML text writer that we created locally. Next, a new MailMessage
object is created, the format is set to HTML, and the rendered content is set as the message body. An EMailPageEventArgs
object is then created and is sent as the parameter for the EmailThisPage
event. This will be covered shortly.
if(args.Cancel == false)
{
do
{
try
{
args.RetryOnFailure = false;
if(msg.From == null || msg.From.Length == 0)
throw new ArgumentNullException("From",
"A sender must be specified for " +
"the e-mail message");
if(msg.To == null || msg.To.Length == 0)
throw new ArgumentNullException("To",
"A recipient must be specified for " +
"the e-mail message");
if(args.SmtpServer != null &&
args.SmtpServer.Length != 0)
SmtpMail.SmtpServer = args.SmtpServer;
SmtpMail.Send(msg);
}
catch(Exception excp)
{
EMailErrorEventArgs err = new EMailErrorEventArgs(
args, excp);
this.OnEMailError(err);
}
} while(args.RetryOnFailure == true);
}
writer.Write(args.RenderedContent);
The EMailThisPage
event can be cancelled. Whether or not the event is cancelled, it renders the content to the browser but does so from the event argument class' RenderedContent
property rather than the StringBuilder
. This allows the event handler in the derived class to modify the HTML sent to the browser. For example, you may want to insert a message above the page content that states that the e-mail was sent or why it was not sent.
If e-mail is to be sent, the method checks to see if an SMTP server has been specified in the event arguments by an event handler. If it was, it will use this server name when sending the e-mail. This is useful in situations where your IIS server is not set up to be an SMTP mail server. It also checks to see if sender and recipient e-mail addresses were specified. If not, the e-mail cannot be sent and an exception is thrown stating that fact.
If the message is sent successfully, it renders the content to the browser and exits as noted above. If there was an error sending the e-mail, the method raises the EMailError
event to give the derived page a chance to handle the error and take alternate action. This will be covered shortly. The error event handler also lets you modify the rendered content to add additional information as to the cause of the error, or make changes and attempt to resend the message.
This event is raised whenever the page wants to e-mail its content. You should always add a handler for this event so that your page can, at the very least, specify the sender and recipient of the e-mail. Without them, the e-mail cannot be sent. The event passes a custom event arguments class called EMailPageEventArgs
that allows the handler to modify the e-mailed content, the rendered content, set the SMTP server, or cancel the event altogether. In its simplest form, the handler will look something like the following. The demo contains some more complex examples:
void Page_EMailThisPage(Object sender,
EWSoftware.Web.EMailPageEventArgs args)
{
if(Page.Request.ServerVariables["HTTP_HOST"] == "localhost")
args.SmtpServer = "REALSMTPSERVER"
args.EMail.From = "Somebody@Domain.com"
args.EMail.To = "Anyone@Domain.com"
args.EMail.Subject = this.PageTitle
}
The event argument class' properties and methods are described below.
public bool Cancel
This Boolean property can be set to true
to cancel the event and prevent the content from being e-mailed. If cancelled, the HTML in the RenderedContent
property will still be sent to the client's browser. You can replace it with an alternate response if necessary when canceling the e-mail (i.e., to redirect the user to another location, etc.).
public string SmtpServer
This property lets you set the SMTP server that should be used when sending the e-mail. It is usually only necessary to set this property if you are running the application on localhost or if your IIS server is not configured with the SMTP service.
public MailMessage EMail
This property gives you access to the e-mail message object. Use it to set the sender, recipient, and subject and also to modify the message body. The Body
property of the returned MailMessage
will contain the HTML that will be sent in the e-mail. You can modify it as needed.
public string RenderedContent
This property lets you modify the page content that will be rendered to the client after the e-mail is sent. Use it to alter the content rendered to the client's browser. For example, you may want to remove sections that are not relevant in the displayed content, insert a message telling the user that the message was sent and to whom, or replace the content with something entirely new.
public bool RetryOnFailure
This property lets you specify whether the page should retry sending the e-mail if it fails. It is set to false
by default. It is also set to false
before each send attempt in order to help prevent an endless loop in case you forget to turn it off. Set this to true
in the error event handler to have the page retry sending the e-mail based on changes you make to the message properties or the SMTP server property.
public int RetryCount
This property can be used to track how many times an attempt to send the e-mail has failed. You can increment it in the error event handler and use its value to determine when to stop trying.
The constructors
You will not create an instance of this class yourself. Instead, the page's RenderForEMail
method does it for you and fills in the necessary parameters. When created by the page, the instance will take the following steps to alter the e-mail message body and the rendered content:
- The e-mail body is processed to remove any HTML between
<!---->
comment tags. This can be used to automatically note sections of the page that should not be included in the e-mail but should be rendered to the browser. The demo contains examples of this.
- Since it serves no purpose, the view state tag is removed from the e-mail body to reduce its size.
- It is a fairly safe assumption that script within an e-mail is frowned upon and most, if not all, e-mail clients will block the script from running to prevent viruses. As such, all script tag blocks are removed from the e-mail body.
- An attempt is made to translate relative URLs to absolute URLs on all occurrences of
src
and href
attributes so that links, images, and stylesheets within the page will function as expected when viewed in the e-mail by the recipient. I do not guarantee 100% coverage, but it should catch and translate the vast majority of them.
Handling this event is optional. A handler for it can be added if you would like to alter the rendered page, take alternate action if the e-mail fails to get sent, or make changes and attempt to retry sending the e-mail. The event passes a custom event arguments class called EMailErrorEventArgs
that allows the handler to examine the cause of the exception. It is derived from EventArgs
and contains two properties.
The first is EMailEventArguments
which is a copy of the event argument's object sent to the EMailThisPage
event. Any of the properties described above can be examined or modified. To retry sending the e-mail, set the RetryOnFailure
property to true
. You can also increment the RetryCount
property to keep track of how many times you have attempted to resend the message.
The second property in this class is EMailException
, which returns an Exception
object containing the details about the problem that was encountered. The demo contains some examples of its use.
The .NET 1.1 version contains a delegate for both events. The .NET 2.0 version of the class makes use of the generic event handler type. As such, hooking up the events has a slightly different syntax in the .NET 2.0 version:
this.EMailThisPage += new EMailThisPageEventHandler(
this.Page_EMailThisPage);
this.EMailError += new EMailErrorEventHandler(
this.Page_EMailError);
this.EMailThisPage += new EventHandler<EMailPageEventArgs>(
this.Page_EMailThisPage);
this.EMailError += new EventHandler<EMailErrorEventArgs>(
this.Page_EMailError);
E-mailing the contents of a rendered page is useful in many situations. The demo application contains some common usage examples such as for the custom error page, user feedback, generating and e-mailing a report, etc. By using this class, you can easily add this ability to any Web Form in your application. Hopefully, you will find this class and the others in the library, or parts of them, as useful as I have.
- 04/02/2006
Breaking changes:
- The
EMailPage
class has been removed. The e-mailing functionality has been merged with the BasePage
class. This was necessary in order to move the rendering code into its own derived class (see part 1 of this series).
- Property and method names have been modified to conform to the .NET naming conventions with regard to casing (
EMailPageEventArgs.SmtpServer
).
- In the .NET 2.0 version, the
EMailThisPageEventHandler
and the EMailErrorEventHandler
delegates have been removed. To add event handlers for these two events use the new .NET 2.0 EventHandler<> generic type instead.
- 11/26/2004
Changes in this release:
- 12/01/2003