It sounded simple enough. I wanted to programmatically take the contents of a form, including TextBoxes, drop down lists, checkboxes, etc and email it to my client as a sort of “snapshot” of what the user had filled out on the form. I had done this countless times in ASP.NET 1.1, but 2.0 refused to budge. I kept getting error after error. Here is the basic code I was using. Since I seem to have a mental blocks on streams, textwriters and any other part of the framework that uses the Decorator pattern I had to look it up:
StringWriter swriter = new StringWriter();
HtmlTextWriter twrite = new HtmlTextWriter(swriter);
this.MainPanel.RenderControl(twrite);
string text = swriter.ToString();
This gave me the following exception: “Control 'TextBox1' of type 'TextBox' must be placed inside a form tag with runat=server.” I remembered this from my .NET 1.1, and my solution had been to create a server side form on the fly, in the code behind it, place my controls that I wanted to render into that, then render the form. The code would then look like this:
StringWriter swriter = new StringWriter();
HtmlTextWriter twrite = new HtmlTextWriter(swriter);
HtmlForm form = new HtmlForm();
form.Controls.Add(this.MainPanel);
form.RenderControl(twrite);
string text = swriter.ToString();
Unfortunately, this code didn’t work either. I got another exception: “HtmlForm cannot render without a reference to the Page instance. Make sure your form has been added to the control tree.” This left me puzzled. I did NOT remember this from asp.net 1.1. What does the framework care if I create an Html form without binding it to the page? What’s changed from asp.net 1.1 to 2.0?
Still I tried to give it what it wanted with this line of code:
form.Page = this.Page;
Still, ASP.NET was not satisfied. I now got another error: “RegisterForEventValidation can only be called during Render();” By now I was cursing Microsoft and spouting obscenities to myself. Fortunately, only one other developer named Tina was around and she’s used to me by now.
I searched over and over in google for a way of successfully rendering a control as an html string which showed me a bunch of code that worked in 1.1. Finally, after all my research, I found the answer. In ASP.NET 2.0, you must do two things. First override the VerifyRenderingInServerForm() method. You don’t have to actually do anything in this method, just override it. This ensures that you don’t have to create the HtmlForm, but you do have to override this method in your page. Next, you have to set a page directive: EnableEventValidation="false". This turns off event validation which basically means ASP.NET no longer checks to see if content in your controls contains malicious code. There is a vague MSDN article here.
I don’t know why so much changed since ASP.NET 1.1 that the old code didn’t work. But my real question to Microsoft is this: Why doesn’t EVERY control have a RenderAsString() method that will automatically do this for me? Why do I have to jump through all these hoops to get a string from my controls? This seems like very common functionality. In any case, these two things got it to work, so you can take a control and render it as a string and email it, save it to disk or whatever. All the research has been done for you, and here is my final example code. This example takes the resulting string and stores it to a file on disk, but you could email it, save it to a database or whatever you wanted.
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" EnableEventValidation="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Panel runat="server" ID="MainPanel">
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="SendButton" runat="server" OnClick="SendButton_Click" Text="Send" /></asp:Panel>
</div>
</form>
</body>
</html>
Default.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
using System.IO;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void SendButton_Click(object sender, EventArgs e)
{
StringWriter swriter = new StringWriter();
HtmlTextWriter twrite = new HtmlTextWriter(swriter);
this.MainPanel.RenderControl(twrite);
string text = swriter.ToString();
File.WriteAllText(Server.MapPath("~/output.htm"), text);
}
public override void VerifyRenderingInServerForm(Control control)
{
}
}