Emailing the Rendered Output of an ASP.NET Web Control
By Scott Mitchell
A Follow-Up Article is Available!
If you try out the code discussed in this article you'll find that
while this technique and code works well for DataGrids that just contain BoundColumns or TemplateColumns without Buttons
or LinkButtons, it breaks when the DataGrid is configured for sorting or paging or contains ButtonColumn, EditCommandColumn,
or TemplateColumns with Buttons or LinkButtons. The follow-up article - Enhancing the 'Email the Rendered Output of an ASP.NET Web Control' Code -
discusses why this problem arises and examine a workaround.
Introduction
Last week I received an email on one of the many lists I was on from Dave Wanta, developer of
aspNet Email and Web master of
123ASPX.com. In his email, Dave shared a URL that demonstrated
how to use his product (aspNet Email) to mail a rendered DataGrid Web control in an HTML-formatted email.
That is, a user, who was viewing a DataGrid control on a Web site's ASP.NET Web page, could
provide her email address and click a button to have the DataGrid's output emailed to her.
This concept really piqued my interest, and although Dave has provided
the source code for his approach, I wanted to figure it out on my own. With some poking around
in the .NET Framework documentation I was able to figure it all out in a relatively short amount of
time (15 minutes or so). I think this is much more of a testament to the power and ease of use of
the .NET Framework than it is my abilities.
Rendering a Control
It is important to understand that when you add a control to a Web page when the page is requested
the control is "rendered" into HTML. For example, say that in your ASP.NET Web page you add the following
ASP.NET Web control:
This rendered HTML is then sent to the client. The important information to understand here is that
all ASP.NET Web controls are rendered down into some sort of HTML markup.
You may be wondering how this rendering happens, and how all controls can provide this functionality.
First, realize that all ASP.NET Web controls are derived from the Controls class (a class
in the .NET Framework). This class provides some base properties and methods that all of these
controls contain. One of the more important methods is the Render method, which accepts
as a single input parameter an object of type HtmlTextWriter. Each Web
control overrides this method; inside this method, the control is responsible for generating its
proper HTML markup and writing this markup to the HtmlTextWriter parameter.
Retrieving a Control's Rendered HTML in an ASP.NET Page
In order to email someone the HTML rendering of an ASP.NET Web control we'll need to first be able
to retrieve a control's HTML and store it in a string variable. Since each control has a
Render method, it would seem to make sense that we could just pass in an HtmlTextWriter,
let the control render itself to the HtmlTextWriter, and then read the HTML content stored in
this HtmlTextWriter.
Things aren't quite that simple, though. First off, the Render method is a protected method,
meaning that only methods within the class itself or derived from the class may call it. Fortunately,
however, the Control class provides a public RenderControl method. This method,
like the protected Render method, accepts an HtmlTextWriter as its sole input
parameter. So, we can use RenderControl in place of Render. Problem #1 solved.
The second difficulty that arises is that the HtmlTextWriter, as its name implies, is for
writing information to. That is, it has methods like Write and WriteLine, but
no corresponding Read methods. Essentially the HtmlTextWriter class serves
as a means to only write data to some backing store. When the ASP.NET page is rendering the controls,
preparing to send this HTML to the client, I would assume the backing store is some kind of network stream.
However, we want our backing store to be a simple string.
It took a bit of pecking through the documentation, but I found that the HtmlTextWriter class's
constructor will accept a TextWritter instance. The TextWritter class, though,
is an abstract one, meaning you cannot use the class as-is; rather, you have to use one of the classes
that inherits the TextWritter class. One such class that is derived from the TextWritter class
is the StringWriter class. The StringWriter class can use as a backing store
a StringBuilder, which can be used to return a string. Phew!
So, in order to retrieve the rendered HTML of a Web control we have to do the following:
Create an instance of the StringBuilder class.
Create an instance of the StringWriter class, using the StringBuilder class
from (1) as its backing store.
Create an instance of the HtmlTextWriter class, using the StringWriter class
from (2) as its backing store.
Call the RenderControl method of the control whose rendered HTML we are interested in,
passing in the HtmlTextWriter instance from (3).
At this point we can retrieve the control's rendered HTML as a string by accessing the StringBuilder
instance's ToString() method.
Retrieving a Control's Rendered HTML - in Action
Ok, by this point you're likely thoroughly confused. Understandably, a discussion on these topics
can be tricky and quite wordy. By looking at some code, though, hopefully things will start to clear up.
The following code demonstrates how to read the rendered HTML from a data bound DataGrid Web control.
As you can see, retrieving the rendered HTML is only a matter of a few lines of code.
<% @Import Namespace="System.IO" %>
<% @Import Namespace="System.Data" %>
<% @Import Namespace="System.Data.SqlClient" %>
<script language="vb" runat="server">
Sub Page_Load(sender as Object, e as EventArgs)
'1. Create a connection
Dim myConnection as New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
'2. Create the command object, passing in the SQL string
Const strSQL as String = "sp_Popularity"
Dim myCommand as New SqlCommand(strSQL, myConnection)
'Set the datagrid's datasource to the datareader and databind
myConnection.Open()
dgPopularFAQs.DataSource = myCommand.ExecuteReader(CommandBehavior.CloseConnection)
dgPopularFAQs.DataBind()
'Get the rendered HTML
Dim SB as New StringBuilder()
Dim SW as New StringWriter(SB)
Dim htmlTW as New HtmlTextWriter(SW)
dgPopularFAQs.RenderControl(htmlTW)
Dim dataGridHTML as String = SB.ToString()
ltlHTMLOutput.Text = Server.HtmlEncode(dataGridHTML)
End Sub
</script>
<asp:datagrid id="dgPopularFAQs" runat="server"
AutoGenerateColumns="False"
Font-Name="Verdana" Width="85%"
HorizontalAlign="Center"
ItemStyle-Font-Size="9pt"
AlternatingItemStyle-BackColor="#dddddd">
<HeaderStyle BackColor="Navy" ForeColor="White" Font-Bold="True"
Font-Size="13pt" HorizontalAlign="Center" />
<Columns>
<asp:BoundColumn HeaderText="FAQ ID" DataField="FAQID"
ItemStyle-HorizontalAlign="Center" />
<asp:BoundColumn HeaderText="Question" DataField="Description" />
<asp:BoundColumn HeaderText="Total Views" DataField="ViewCount"
DataFormatString="{0:#,###}" ItemStyle-HorizontalAlign="right" />
</Columns>
</asp:DataGrid>
<p>Below you will see the rendered HTML for the DataGrid Web control</p>
<pre>
<asp:literal id="ltlHTMLOutput" runat="server" />
</pre>
All of the action's happening in the bolded text in the Page_Load event handler.
There a StringBuilder instance is created, which is used as the backing store for
a StringWriter object. Next, the HtmlTextWriter object is created, using
the StringWriter object as its backing store. Finally, the DataGrid
dgPopularFAQs has its RenderControl method called. After this has finished
we can retrieve the rendered HTML by calling the StringBuilder object's ToString()
method.
As the live demo shows, this example simply outputs the
rendered HTML of the DataGrid onto the ASP.NET Web page. An ASP.NET literal control (ltlHTMLOutput)
is used to hold the rendered HTML. At the end of the Page_Load event handler the literal control's
Text property is set to the DataGrid's rendered HTML. Of course, we use Server.HtmlEncode
first so that < are converted into < and so forth, so that we see the actual HTML rather than
the HTML as it is normally displayed in the browser.
Emailing a Control's Rendered HTML
One of the neat things you can do, once you have the rendered HTML of a Web control, is email it using
HTML-formatted email! For example, if you run an eCommerce store and you use a Web control to, say, display
the user's bill, you could have the user automatically receive a copy of this bill via email. You wouldn't have
to write a line of code to do any special formatting for the email version of the bill - just send an HTML-formatted
email whose body is set to the rendered HTML of the bill you are wanting to send.
Below you will find a demo for emailing a Web control to a user via HTML-formatted email. I am not
including a live demo for this example because I don't want anyone to abuse the demo to
start sending floods of email to folks they might not like. Instead, I'll show you a simple example in
code and let you run it on your own servers.
<% @Import Namespace="System.Web.Mail" %>
<% @Import Namespace="System.IO" %>
<% @Import Namespace="System.Data" %>
<% @Import Namespace="System.Data.SqlClient" %>
<script language="vb" runat="server">
Sub Page_Load(sender as Object, e as EventArgs)
If Not Page.IsPostBack then
BindData()
End If
End Sub
Sub BindData()
'1. Create a connection
Dim myConnection as New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
'2. Create the command object, passing in the SQL string
Const strSQL as String = "sp_Popularity"
Dim myCommand as New SqlCommand(strSQL, myConnection)
'Set the datagrid's datasource to the datareader and databind
myConnection.Open()
dgPopularFAQs.DataSource = myCommand.ExecuteReader(CommandBehavior.CloseConnection)
dgPopularFAQs.DataBind()
End Sub
Sub EmailDataGrid(sender as Object, e as EventArgs)
Dim SB as New StringBuilder()
Dim SW as New StringWriter(SB)
Dim htmlTW as New HtmlTextWriter(SW)
dgPopularFAQs.RenderControl(htmlTW)
Dim dataGridHTML as String = SB.ToString()
'Now, send the email
'Create an instance of the MailMessage class
Dim objMM as New MailMessage()
'Set the properties
objMM.To = txtEmailAddy.Text
objMM.From = "YourEmailAddress"
'Send the email in HTML format
objMM.BodyFormat = MailFormat.Html
'Set the subject
objMM.Subject = "DataGrid Emailing"
'Set the body - use VbCrLf to insert a carriage return
objMM.Body = "<font face=""Verdana"">This is an example of emailing a " & _
"DataGrid Web control via an ASP.NET Web page." & dataGridHTML
SmtpMail.Send(objMM)
Response.Write("<i>Email sent to " & txtEmailAddy.Text & "!</i>")
End Sub
</script>
<form runat="server">
<asp:datagrid id="dgPopularFAQs" runat="server"
AutoGenerateColumns="False"
Font-Name="Verdana" Width="85%"
HorizontalAlign="Center"
ItemStyle-Font-Size="9pt"
AlternatingItemStyle-BackColor="#dddddd">
<HeaderStyle BackColor="Navy" ForeColor="White" Font-Bold="True"
Font-Size="13pt" HorizontalAlign="Center" />
<Columns>
<asp:BoundColumn HeaderText="FAQ ID" DataField="FAQID"
ItemStyle-HorizontalAlign="Center" />
<asp:BoundColumn HeaderText="Question" DataField="Description" />
<asp:BoundColumn HeaderText="Total Views" DataField="ViewCount"
DataFormatString="{0:#,###}" ItemStyle-HorizontalAlign="right" />
</Columns>
</asp:DataGrid>
<p>
<asp:TextBox id="txtEmailAddy" runat="server" />
<asp:Button Text="Email DataGrid" OnClick="EmailDataGrid" runat="server" />
</form>
For more information on sending email through an ASP.NET Web page be sure to check out:
Sending Email from an ASP.NET Web Page.
A screenshot of the emailed DataGrid control can be seen below. You can also view a high quality
image of the screenshot below (~100 KB).
A Follow-Up Article is Available!
If you try out the code discussed in this article you'll find that
while this technique and code works well for DataGrids that just contain BoundColumns or TemplateColumns without Buttons
or LinkButtons, it breaks when the DataGrid is configured for sorting or paging or contains ButtonColumn, EditCommandColumn,
or TemplateColumns with Buttons or LinkButtons. The follow-up article - Enhancing the 'Email the Rendered Output of an ASP.NET Web Control' Code -
discusses why this problem arises and examine a workaround.
Conclusion
In this article we examined how to programmatically retrieve the HTML that is rendered by an ASP.NET
Web control. While our examples focused on retrieving the rendered HTML of a DataGrid, the same code
will work for any ASP.NET Web control. One useful implementation of this knowledge is in sending
users an HTML-formatted email that contains the output of an ASP.NET Web control, as shown in the
last example in this article.