Emailing the Rendered Output of an ASP.NET Web Control in ASP.NET 2.0
By Scott Mitchell
Introduction
In a previous 4Guys article, Emailing the Rendered Output of an
ASP.NET Web Control, I showed how to programmatically render the HTML of an ASP.NET Web control (such as a GridView)
and then send that HTML through email. This technique is useful in scenarios where you want to let the user email themselves
the web page, or portions of the page. For example, in a typical eCommerce website, after completing an order the user is taken
to an order summary page that would likely include a Web control that lists the items purchased, the applicable tax, and
the total due. You could include a button on this page that, when clicked, emails the user this information.
While the code presented in Emailing the Rendered Output of an ASP.NET Web Control and its follow-up article,
Enhancing the 'Email the Rendered Output of an ASP.NET Web Control' Code,
works great in ASP.NET version 1.x, the same code results in an exception
in ASP.NET 2.0. In this article we will look at why this exception occurs
and how to circumvent it, exploring examples in both VB and C#. Read on to learn more!
A Quick Review of Rendering the Contents of a Web Control
The previous Emailing the Rendered Output of an
ASP.NET Web Control articles explore the ins and outs of programmatically rendering a Web control's HTML, but let's
take a moment to quickly review the basics. All Web controls have a
RenderControl(HtmlTextWriter)
method that, when invoked, renders the control, emitting its resulting markup to the passed-in
HtmlTextWriter instance.
This programmatic rendering is performed using the following code:
// C#
StringBuilder SB = new StringBuilder();
StringWriter SW = new StringWriter(SB);
HtmlTextWriter htmlTW = new HtmlTextWriter(SW);
WebControlToRenderID.RenderControl(htmlTW);
'VB
Dim SB As New StringBuilder()
Dim SW As New StringWriter(SB)
Dim htmlTW As New HtmlTextWriter(SW)
WebControlToRenderID.RenderControl(htmlTW)
After the above code is executed, the control's rendered HTML can be accessed from the StringBuilder object's
ToString() method (i.e., SB.ToString()).
In ASP.NET 1.x, this code works wonderfully unless the Web control being rendered is a control that must reside within
a WebForm or contains a child control that must reside in a WebForm. For example, Button Web controls must reside within
a WebForm. Consequently, if you attempt to use the above code to render a Button Web control or a DataGrid or GridView that
contains a Button (or LinkButton or ImageButton) Web control, an exception will be raised complaining that the control is not
in a WebForm. This check is performed by the Page class's VerifyRenderingInServerForm() method.
This problem, and three workarounds, are discussed in detail in the
Enhancing the 'Email the Rendered Output of an ASP.NET Web Control' Code
article. The simplest workaround is to create a custom base Page class that overrides the Page class's
VerifyRenderingInServerForm() method so that it ignores checking whether or not the control is rendering in
a WebForm.
Problems in ASP.NET 2.0
If you use the code presented in the Emailing the Rendered Output of an
ASP.NET Web Control articles to attempt to render the email output of a control in ASP.NET 2.0, you may wind up with
the following exception message (depending on what, exactly, you are attempting to render programmatically):
RegisterForEventValidation can only be called during Render();
This is due to ASP.NET 2.0's new event validation feature. In a nutshell, event validation is a technique
used by ASP.NET 2.0 to ensure that the data being sent back to the server on postback is an expected value and is designed
to help prevent injection attacks. As K. Scott Allen notes in
his blog entry ASP.NET Event Validation and "Invalid Callback Or Postback Argument":
one example of event validation in action is with a DropDownList Web control. Imagine that the DropDownList has three ListItems
specified with Values 1, 2, and 3:
If, on postback, the page sends back the value 4 for the DropDownList MyDropDownList, the ASP.NET runtime will
throw an exception because it expects a value of 1, 2, or 3. This is the ideal behavior if you really only wanted to allow
the values 1, 2, or 3, because then a value of 4 indicates that something fishy is going on. However, the DropDownList's items
might have been altered on the client-side intentionally, adding a list item with a value of 4. In that case, the exception
raised by event validation is a nuisance.
During the page's lifecycle, those controls that participate in event validation "register" themselves through the
ClientScriptManager class's
RegisterForEventValidation method.
The RegisterForEventValidation method throws the "RegisterForEventValidation can only be called during Render()" if
it is invoked before the Render stage. The code to programmatically render and email a Web control's
markup usually is placed in the Page_Load event handler or in the event handler for a Button's Click
event. But these event handlers fire before the Render stage. Therefore, when the control being rendered
registers itself for event validation, the RegisterForEventValidation method raises this exception because the
Render stage has not yet been reached.
Of course, this exception only occurs when programmatically rendering a control that participates in event validation (or when
the control being rendered has a child control that participates in event validation), and not all controls participate in
event validation. Only those that are decorated with the SupportsEventValidation
attribute go through the event validation workflow. The following table from
K. Scott Allen's blog entry lists those
controls that participate in event validation:
HtmlAnchor
HtmlButton
HtmlInputButton
HtmlInputCheckBox
HtmlInputHidden
HtmlInputImage
HtmlInputText
HtmlInputPassword
HtmlInputRadioButton
HtmlInputReset
HtmlInputSubmit
HtmlSelect
HtmlTextArea
BulletedList
Button
Calendar
CheckBox
Table
ChildTable
WizardChildTable
DataControlButton
ImageButton
DataControlImageButton
LinkButton
DataControlLinkButton
DataControlPagerLinkButton
DataGridLinkButton
DetailsView
DropDownList
FormView
GridView
HiddenField
ImageMap
LayoutTable
ListBox
Menu
PagerTable
RadioButton
RadioButtonList
TextBox
TreeView
WizardDefaultInnerTable
CatalogZone
ConnectionsZone
EditorZone
WebPartZone
ZoneButton
ZoneLinkButton
Fixing the Event Validation Exception for Programmatically Rendered Controls
There are a couple of approches that can be taken to allow for a control to be programmatically rendered without raising
the event validation-related exception. The simplest approach is to simply disable event validation for the page. This
can be done by setting the EnableEventValidation attribute in the <% @Page %> directive to
False:
<% @Page EnableEventValidation="False" ... %>
If you are using the base Page class technique to override the Page class's
VerifyRenderingInServerForm() method, you can always override the EnableEventValidation property there,
instead. The download at the end of this article provides VB and C# examples of how to override this property to have it return False
through the base Page class.
The other option is to leave the event validation feature enabled, but just wait to programmatically render the control
until the Render stage. By doing this, when the control registers for event validation it will be in the Render stage and
so the "RegisterForEventValidation can only be called during Render()" exception won't be raised. To accomplish this,
you'll need to set some flag in the Button's Click event handler (or wherever you decide to programmatically
render the control). Then, override the Page's Render(HtmlTextWriter) method and run the
code to generate and email the selected control's HTML output. See the code download for examples in VB and C#.