Jim's Blog Ramblings about novels, comics, programming, and other geek topics

11Jul/0823

How to render a ASP.NET User Control within a Web Service and return the generated HTML

Google AdSense

My needs:

  • Use ASP.NET AJAX PopUpExtender control to provide a mouseover popup with additional content relevant to the GridView row.
  • Use DynamicContextKey to pass in GridView record's unique identifier.

My wants:

  • Use a User Control to render the HTML response, so I don't need to build the HTML code using a StringBuilder or HtmlTextWriter.
First attempt

My initial code was something similar to the below code:

StringBuilder htmlResponse = new StringBuilder();
using (StringWriter sw = new StringWriter(htmlResponse))
{
   using (HtmlTextWriter textWriter = new HtmlTextWriter(sw))
   {
      UserControl myControl = new UserControl();
      myControl.LoadControl("myUserControl.ascx");
      myControl.Attributes["MyDataKey"] = myDataKey;
      myControl.RenderControl(textWriter);
   }
}
return htmlResponse.ToString();

However, my StringBuilder htmlResponse (htmlResponse) and StringWriter (sw) were always empty. So whatever was happening, the user control wasn't being rendered and my response was always empty.

Note, this is within a System.Web.Services.WebService class and not within a System.Web.UI.Page class. Doing this within a System.Web.UI.Page is very simple and you can just create a new instance of your User Control class and render it directly to the page or a string. Since this is within a WebService and not Page class, I discovered that I need to make a mock Page object in order to render the User control as you'll see below.

Second try

After running into a stumbling block of how to render a user control from within a web service, I turned to Google.

First, I found this nice article over at 4GuysFromRolla "Displaying Extended Details in a GridView Using an Ajax Pop-Up". This article was almost what I needed.

This wasn't too helpful, since it was just returning HTML built on the fly. It was exactly what I wanted to do, but without using the Web Control. However, this article did validate my ASP.NET AJAX technique and allowed me to confirm that my ASP.NET code on my web form and GridView was correct.

Finally,... ScottGu to the rescue

After a bunch of trying and searching, I finally found Scott Guthrie's (aka the ASP.NET Guru) blog entry that solved all of my problems. In his article titled "Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios", he explains exactly what I wanted to do. With a few minor modifications, I was off and running with a fully functional web service method.

In his ViewManager class (code download available on his web site via the blog link above), I updated the methods so that I can pass in a data object as a public property instead of a public field.  I also modified the method to pass in the property name instead of requiring a field named "Data".

Here's ScottGu's modified code:

public static string RenderUserControl(string path, 
                 string propertyName, object propertyValue)
{
   Page pageHolder = new Page();
   UserControl viewControl = 
      (UserControl)pageHolder.LoadControl(path);
   
   if (propertyValue != null)
   {
      Type viewControlType = viewControl.GetType();
      PropertyInfo property = 
         viewControlType.GetProperty(propertyName);

      if (property != null)
      {
         property.SetValue(viewControl, propertyValue, null);
      } 
      else
      {
         throw new Exception(string.Format(
            "UserControl: {0} does not have a public {1} property.",
            path, propertyName));
       }
    }

   pageHolder.Controls.Add(viewControl);
   StringWriter output = new StringWriter();
   HttpContext.Current.Server.Execute(pageHolder, output, false);
   return output.ToString();
}

And here is how I'm calling it from my web service:

[WebMethod()]
public string GetMyUserControlHtml(string contextKey)
{
    Guid myId = new Guid(contextKey);
    return htmlResponse = 
        UserControlRenderer.RenderUserControl(
        "myUserControl.ascx", "MyDataKey", myId);
}

Note: I changed ScottGu's class name and method names to UserControlRenderer and RenderUserControl.

James Welch

James Welch is a software engineer in Vermont working for a large information technology company and specializing in .NET. Additionally, he holds a Master’s Degree in Software Engineering and a Bachelor of Science Degree in Computer Science. Jim also enjoys local craft beer, comic books, and science-fiction and fantasy novels, games, and movies.

Twitter Google+ 

Comments (23) Trackbacks (0)
  1. Thank you! I’ve being going crazy because of an ascx that contains a datalist. Normal controls are rendered to text using common snippets of code (rendercontrol(..)) but it appears the trick is to use a page as a container when there is a datalist involved.
    Cheers

  2. In the UserControl I have a RadChart Telerik control, is possible to render it. Have someone face the same problem?

  3. It seems like you should be able to render it. Are you having a specific problem or getting a specific error message when you try to render it?

  4. Thanks Jim for this

    I also wanted to avoid building the html output line-by-line, and your solution saved me after a full-day search.

    Johann

  5. I am getting a “Error executing child request for handler ‘System.Web.UI.Page’” error when using the my usercontrol contains a datagrid within it.

    Any ideas on what am I doing wrong?

    Deepu

  6. You might not be able to use a datagrid in this manner.

  7. Has anyone else noticed that the controls rendered in this way don’t have any of their page lifecycle methods called? For instance OnInit, OnLoad etc will not get called. Thoughts?
    -Nate

  8. The single property value is limiting, and should probably be an array, list or dictionary. But that isn’t a big deal–I know this code was mostly “here’s how” and not a library. Either way, it was very useful for me, thanks!

  9. Musafa,

    That’s how I ended up coding my final soultion. I might update the above code to show how to use that technique next week.

  10. I did almost exactly the same but instead of the “if (propertyValue != null) {…}” , I created an HtmlForm instance, then added the userControl to the HtmlForm instance, then added the HtmlForm to the page (rather than the control to the page).

    After I had to strip out the ViewState, EventValidation and Form tags from the HTML because using the as-is generated html and injecting it into an existing page (with ajax) was causing an:

    Sys.WebForms.PageRequestManagerServerErrorException: The state information is invalid for this page and might be corrupted.

    Error was thrown when trying to do an partial page update.

    Does your technique not generate the ViewState, etc, tags?

    • The above technique doesn’t generate ASP.NET specific elements such as the hidden form element called ViewState. It only produces typical HTML element output.

  11. Thank you Jim
    Its really more helpfull to me.
    Thanks a lot.

  12. Is there any way to use Session variables in User control loaded with this Technique

  13. Hi,
    I am getting the foolowing error —-
    error executing child request for handler ‘system.web.ui.page’.

    I am rendering modified view mananger class.
    i have user control with asp.net CheckBox.

    How to solve this issue of rendering Asp.net server controls using ajax.

    “Why Datalist and Repeater controls can not throw error ,when they are the server side controls”

  14. Hey All,
    I’ve been successfully able to render the user controls HTML this way. Thanks all.
    Now comes another challenge : How do I access the values of these controls on client side and server side as well? Lets say I need to provide the sum of the values within the user controls added this way.

    Any resolution?

    Thanks again,
    Ishan

  15. Thx. Helped a lot.

  16. WOOT! I’ve been looking for a solution to this issue for YEARS! THANKS!

  17. Not sure why you cast the item as a UserControl and then used reflection to look up the property. Would’ve been a lot quicker to just cast it to the type you were looking for and set the property.

    • That might work. The article is a couple of years old, so I don’t have the technical specifics in front of me nor do I remember the code. However, if you were to just cast the object to the UserControl class, then you wouldn’t be able to specify dynamic attributes/properties of the object. You’d have to know both the type of the class and the exact names and data types of the attributes.

  18. Thank you soooooo much. I’ve been breaking my head for the past 2 days to get this solution.

  19. You can use session state, the webservice would have to be on the same box as the website, and you just need to specify:

    WebMethod(EnableSession:=True)

    for the web method.

  20. Consider the following code (delegation instead of reflection):

    public delegate void SetControlProperties(TControl control) where TControl : UserControl, new ();

    public class UserControlRenderer where TControl : UserControl, new()
    {
    private readonly string userControlPath;
    private SetControlProperties setControlPropertiesDelegate;

    ///
    ///
    ///
    /// Path to the ascx file of the control
    public UserControlRenderer(string userControlPath)
    {
    this.userControlPath = userControlPath;
    }

    ///
    /// Use to set the control’s properties before calling RenderHtml
    ///
    ///
    public void SetControlProperties(SetControlProperties setControlPropertiesCode)
    {
    setControlPropertiesDelegate = setControlPropertiesCode;
    }

    public string RenderHtml()
    {
    var pageHolder = new Page();
    var viewControl = (TControl)pageHolder.LoadControl(userControlPath);

    setControlPropertiesDelegate(viewControl);

    pageHolder.Controls.Add(viewControl);
    var output = new StringWriter();
    HttpContext.Current.Server.Execute(pageHolder, output, false);
    return output.ToString();
    }
    }


Leave a Reply

No trackbacks yet.