views:

6917

answers:

4

I asked how to render a UserControl's HTML and got the code working for a dynamically generated UserControl.

Now I'm trying to use LoadControl to load a previously generated Control and spit out its HTML, but it's giving me this:

Control of type 'TextBox' must be placed inside a form tag with runat=server.

I'm not actually adding the control to the page, I'm simply trying to grab its HTML. Any ideas?

Here's some code I'm playing with:

TextWriter myTextWriter = new StringWriter();
HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter);

UserControl myControl = (UserControl)LoadControl("newUserControl.ascx");
myControl.RenderControl(myWriter);

return myTextWriter.ToString();
A: 

You can either add a form to your user control, or use a regular html input box

 <input type="text" />

Edit: If you are trying to do something AJAXy, maybe you want something like this http://aspadvice.com/blogs/ssmith/archive/2007/10/19/Render-User-Control-as-String-Template.aspx

    public static string RenderView<D>(string path, D dataToBind)
    {
        Page pageHolder = new Page();
        UserControl viewControl = (UserControl) pageHolder.LoadControl(path);
        if(viewControl is IRenderable<D>)
        {
            if (dataToBind != null)
            {
                ((IRenderable<D>) viewControl).PopulateData(dataToBind);
            }
        }
        pageHolder.Controls.Add(viewControl);
        StringWriter output = new StringWriter();
        HttpContext.Current.Server.Execute(pageHolder, output, false);

        return output.ToString();
    }

You can remove the data binding part if not needed.

Bob
I thought about adding a second UserControl with the same view minus the runat="server" on each control, but that definitely isn't DRY, and it puts a lot of constraints on things like drop-downs.
Jon Smock
The problem is without the form, your drop downs won't be able to communicate back with the server, your best bet is to wrap the usercontrol's contents with <form runat="server">
Bob
I have a <form runat="server"> around my entire page, but this control doesn't see that I guess. Would you still recommend this?
Jon Smock
I'm actually adding this control to the page just fine, but it's giving me problems when I try to RenderControl() the Control. Is there some problem with the way I'm grabbing the text?
Jon Smock
Wrapping the usercontrol with a form tag produces an error. (A page can have only one server-side Form tag.)
Jon Smock
A: 

You can add the control into page, render html and then remove the control from page.

Or try this:

Page tmpPage = new TempPage(); // temporary page
Control tmpCtl = tmpPage.LoadControl( "~/UDynamicLogin.ascx" );
tmpPage.Form.Controls.Add( tmpCtl );

StringBuilder html = new StringBuilder();
using ( System.IO.StringWriter swr = new System.IO.StringWriter( html ) ) {
    using ( HtmlTextWriter writer = new HtmlTextWriter( swr ) ) {
        tmpForm.RenderControl( writer );
    }
}
TcKs
I actually tried adding it to the page, but let me try what you're doing there.
Jon Smock
It's saying I don't have access to tmpForm in that context. (Also TempPage should be Page, right?)
Jon Smock
Oops, it should be tmpPage there.
Jon Smock
+1  A: 

This is a dirty solution I used for the moment (get it working then get it right, right?).

I had already created a new class that inherits the UserControl class and from which all other "UserControls" I created were derived. I called it formPartial (nod to Rails), and this is going inside the public string renderMyHTML() method:

TextWriter myTextWriter = new StringWriter();
HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter);

UserControl myDuplicate = new UserControl();
TextBox blankTextBox;

foreach (Control tmpControl in this.Controls)
{
    switch (tmpControl.GetType().ToString())
    {
        case "System.Web.UI.LiteralControl":
            blankLiteral = new LiteralControl();
            blankLiteral.Text = ((LiteralControl)tmpControl).Text;
            myDuplicate.Controls.Add(blankLiteral);
            break;
        case "System.Web.UI.WebControls.TextBox":
            blankTextBox = new TextBox();
            blankTextBox.ID = ((TextBox)tmpControl).ID;
            blankTextBox.Text = ((TextBox)tmpControl).Text;
            myDuplicate.Controls.Add(blankTextBox);
            break;

            // ...other types of controls (ddls, checkboxes, etc.)

    }
}

myDuplicate.RenderControl(myWriter);
return myTextWriter.ToString();

Drawbacks off the top of my head:

  1. You need a case statement with every possible control (or controls you expect).
  2. You need to transfer all the important attributes from the existing control (textbox, etc) to the new blank control.
  3. Doesn't take full advantage of Controls' RenderControl method.

It'd be easy to mess up 1 or 2. Hopefully, though, this helps someone else come up with a more elegant solution.

Jon Smock
+4  A: 

Alternatively you could disable the ServerForm/Event-validation on the page that is rendering the control to a string.

The following example illustrates how to do this.

public partial class _Default : System.Web.UI.Page
{
 protected void Page_Load(object sender, EventArgs e)
 {
  string rawHtml = RenderUserControlToString();
 }

 private string RenderUserControlToString()
 {
  UserControl myControl = (UserControl)LoadControl("WebUserControl1.ascx");

  using (TextWriter myTextWriter = new StringWriter())
  {
   using (HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter))
   {
    myControl.RenderControl(myWriter);

    return myTextWriter.ToString();
   }
  }
 }

 public override void VerifyRenderingInServerForm(Control control)
 { /* Do nothing */ }

 public override bool EnableEventValidation
 {
  get { return false; }
  set { /* Do nothing */}
 }
}
Tom Jelen
It's unfortunate you have to override it for the whole page, but it worked for me. Nice.
Jon Smock
Yeah its not pretty, but easy.I guess you could introduce a boolean class variable, and use it to toggle the "disabling" on and off. That way you can just switch it off temporarily while rendering the control, and then turn in on again for the rest of the page. Its worth a try if you need it on.
Tom Jelen