Title: ASP.NET Common Web Page Class Library - Part 3
Author: Eric Woodruff
Email: Eric@EWoodruff.us
Environment: Visual Studio .NET, IIS, ASP.NET, C#, VB.NET
Keywords: page, template, e-mail, render
Level: Intermediate
Description: An ASP.NET page class that has the ability to e-mail its rendered content
Section ASP.NET
SubSection General
Table of Contents
Introduction
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).
The BasePage Class E-Mail Features
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.
Two Common Approaches
One common approach to e-mailing the rendered content of a page is to
use the WebRequest and WebResponse objects as
follows:
// Create a request for the page that you want
WebRequest wreq = System.Net.HttpWebRequest.Create(
"http://www.mysite.com/mypage.html");
// Get the response
WebResponse wrsp = wreq.GetResponse()
// Get the HTML for the page
StreamReader sr = new StreamReader(wrsp.GetResponseStream())
string strHTML = sr.ReadToEnd()
sr.Close()
// Send it by e-mail
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:
// Create a string builder to contain the HTML
StringBuilder sb = new StringBuilder();
HtmlTextWriter htw =
new HtmlTextWriter(new StringWriter(sb));
// Render the page to it
base.Render(htw);
string strHTML = sb.ToString();
// Send it by e-mail
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);
// And finally, write the HTML to original writer
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 stylesheets 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 Benefits
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.
How It Works
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.
The EMailRenderedPage Property
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 Render Method
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's 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 already rendering for e-mail, just render the page
if(!this.IsRenderingForEMail)
{
this.ConvertValMsgsToLinks();
// Rendering normally or going to e-mail?
if(this.EMailRenderedPage)
{
this.RenderForEMail(writer);
return;
}
}
base.Render(writer);
}
The RenderForEMail Method
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();
// The string builder contains all of the HTML
msg.BodyFormat = MailFormat.Html;
msg.Body = sb.ToString();
EMailPageEventArgs args =
new EMailPageEventArgs(msg, this);
// Get the SMTP server from the Web.config file
// if specified
args.SmtpServer = ConfigurationSettings.AppSettings[
"EMailPage_SmtpServer"];
// Give the derived page a chance to modify or cancel
// the e-mail.
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)
{
// Try sending until it succeeds or told to stop
do
{
try
{
// Turn off retry so we don't get stuck here.
// The error event handler must turn it on if
// a retry is wanted.
args.RetryOnFailure = false;
// It has to come from somebody
if(msg.From == null || msg.From.Length == 0)
throw new ArgumentNullException("From",
"A sender must be specified for " +
"the e-mail message");
// It has to go to somebody
if(msg.To == null || msg.To.Length == 0)
throw new ArgumentNullException("To",
"A recipient must be specified for " +
"the e-mail message");
// Set the server?
if(args.SmtpServer != null &&
args.SmtpServer.Length != 0)
SmtpMail.SmtpServer = args.SmtpServer;
SmtpMail.Send(msg);
}
catch(Exception excp)
{
// Raise an EMailError event if it fails so that
// the derived page can take any action it wants
// including a retry.
EMailErrorEventArgs err = new EMailErrorEventArgs(
args, excp);
this.OnEMailError(err);
}
} while(args.RetryOnFailure == true);
}
// And finally, write the HTML to the original writer.
// It's rendered from the event args in case the handler
// modified it.
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.
E-Mail Event Handlers
The EMailThisPage Event
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.
// This event fires when the page is ready to be e-mailed
void Page_EMailThisPage(Object sender,
EWSoftware.Web.EMailPageEventArgs args)
{
// Set the SMTP server if running locally for testing
if(Page.Request.ServerVariables["HTTP_HOST"] == "localhost")
args.SmtpServer = "REALSMTPSERVER"
// Set sender, recipient, and subject
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
<!-- NOEMAIL --> 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.
The EMailError Event
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 arguments 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.
Connecting The Event Handlers
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:
// .NET 1.1
this.EMailThisPage += new EMailThisPageEventHandler(
this.Page_EMailThisPage);
this.EMailError += new EMailErrorEventHandler(
this.Page_EMailError);
// .NET 2.0
this.EMailThisPage += new EventHandler<EMailPageEventArgs>(
this.Page_EMailThisPage);
this.EMailError += new EventHandler<EMailErrorEventArgs>(
this.Page_EMailError);
Conclusion
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.
Revision History
| 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:
- Based on a suggestion from shtwang, I have added code to the
EMailPage class to retrieve the SMTP server name from
the application key EMailPage_SmtpServer in
Web.config so that you do not have to set it manually in the
event handler unless you want to override it. If not defined, it
stays set to null unless changed in the event handler as
before.
- Based on a suggestion from Lynn Evans, I have reworked the
EMailPage class to support a retry operation if the
initial send fails. This can be controlled by the new
RetryOnFailure and RetryCount properties
on the EMailPageEventArgs class.
NOTE: This introduces breaking changes to the
EMailErrorEventArgs class. It is now derived from
EventArgs and contains an
EMailEventArguments property that lets you access and
modify the e-mail event arguments (i.e. to increment the retry
count and specify that it should retry on return, etc).
|
| |
| 12/01/2003 |
|
Initial release |