Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Common Web Page Class Library - Part 3

0.00/5 (No votes)
6 Apr 2006 1  
An ASP.NET page class that has the ability to e-mail its rendered content.

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 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 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' 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 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.

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here