In Part 1 we saw one technique for rendering the contents of a DataGrid that
contains in its control hierarchy controls that require they be in a server-side form. In this final part of this
article we'll look at another related approach to solve the same problem.
Creating a New Page Instance for Rendering
Rather than rendering the server-side form defined in the ASP.NET Web page's HTML portion, we can programmatically
create a new Page instance with a new server-side form instance. We can then add the DataGrid (or whatever
Web control we want to email the rendered contents of) to the server-side form, and then render the Page instance
by calling the RenderControl() method. (This technique was thought up by Andy
Smith, whose knowledge of ASP.NET control development is best shown by his numerous free ASP.NET server controls
available at MetaBuilders.com.)
The code for this approach is shown below. Note that we don't need to programmatically set the Visible
property of any controls. Rather, we just create a new Page instance and a new server-side form, build
up the control hierarchy, add the DataGrid, and call the programmatically-created Page's RenderControl()
method. We don't need any Panel controls either.
<% @Import Namespace="System.Web.Mail" %>
<% @Import Namespace="System.IO" %>
<% @Import Namespace="System.Data" %>
<% @Import Namespace="System.Data.SqlClient" %>
<script language="vb" runat="server">
... Page_Load & BindData() methods omitted for brevity ...
... Refer to Emailing the Rendered Output of an ASP.NET Web Control for their code ...
Sub EmailDataGrid(sender as Object, e as EventArgs)
'Save the position of the DataGrid in the myForm Controls collection
Dim dgControlPosition as Integer = myForm.Controls.IndexOf(dgPopularFAQs)
'Create a new Page instance and a server-side form
Dim p as New Page()
Dim hf as New HtmlForm()
'Build up the control hierarchy
p.Controls.Add(hf)
hf.Controls.Add(dgPopularFAQs)
Dim SB as New StringBuilder()
Dim SW as New StringWriter(SB)
Dim htmlTW as New HtmlTextWriter(SW)
'Call the page's RenderControl() method
p.DesignerInitialize()
p.RenderControl(htmlTW)
p = Nothing
'Add the DataGrid back to myForm
myForm.Controls.AddAt(dgControlPosition, dgPopularFAQs)
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 = "mitchell@4guysfromrolla.com"
'Send the email in text 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)
End Sub
</script>
<form runat="server" id="myForm">
<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:EditCommandColumn EditText="Edit" ... />
<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>
A couple things to notice here - before adding the dgPopularFAQs DataGrid to the programmatically-created
server-side form, we needed to first remember where in the Controls collection of myForm it
belonged. This is because after we render the contents of the programmatically-created Page, we'll need
to place the DataGrid back in myFormsControls collection, and we want to place it back in
the right place.
Second, note that we call the Page class's DesignerInitialize() method prior to calling the
RenderControl() method. This causes the Page class's protected InitRecursive()
method to be called, which preps a number of properties needed for rendering. (Omitting this call to DesignerInitialize()
will result in an exception being thrown since it won't properly mark that the server-side form has been rendered when the DataGrid's
LinkButton is rendered...)
With this approach, we don't need to worry ourselves with hiding the myForm server-side form since it is
only rendered once the entire page life-cycle.
Creating a Base Page Class That Overrides the VerifyRenderingInServerForm() Method
The crux of the problem the two workarounds examined earlier in this article attempt to overcome is that the Page
class's VerifyRenderingInServerForm() method throws an exception if the control being rendered is not located
within a Web Form. Rather than trying to "fake out" the existing Page class's VerifyRenderingInServerForm()
method (which is what the two previous workarounds do), we could just create our own base Page class and override
the VerifyRenderingInServerForm() method.
A base Page class is a class that extends the built-in System.Web.UI.Page, but adds to or customizes
its functionality. Once this base Page class has been created, we can have our ASP.NET pages derive from it instead
of System.Web.UI.Page. For more on this technique, be sure to read Using a Custom Base Class
for your ASP.NET Page's Code-Behind Classes.
Start by creating a class in your project named EmailReadyPage and use the following code:
Public Class EmailReadyPage Inherits System.Web.UI.Page
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As System.Web.UI.Control)
'DO NOTHING
End Sub
End Class
This class derives from System.Web.UI.Page and overrides its VerifyRenderingInServerForm() so
that it does nothing. That is, with the code above, when VerifyRenderingInServerForm() no exception is thrown,
regardless of whether the control appears within a Web Form.
Finally, configure your ASP.NET page(s) that send emails of rendered control markup to use the EmailReadyPage
class as its base class by replacing the Inherits System.Web.UI.Page in the ASP.NET code-behind class with
Inherits EmailReadyPage.
Conclusion
In this article we saw how to improve the code from Emailing the Rendered Output of an ASP.NET Web Control
to allow for DataGrid's with LinkButtons or Buttons in their control hierarchy to be emailed as well. This feat can be
accomplished in a number of ways: either by rendering the server-side form as opposed to the DataGrid, or by creating
a new Page and server-side form, and rendering those. For a much more in-depth look at ASP.NET server control
building, be sure to consider picking up a copy of Developing
Microsoft ASP.NET Server Controls and Components.
If you try out the code discussed in this article in an ASP.NET 2.0
application, you may wind up with an exception that reads: "RegisterForEventValidation can only be called during Render();"
If so, check out Emailing the Rendered Output of an ASP.NET Web Control in ASP.NET 2.0
for a discussion on why this error arises along with a couple of workarounds.