Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Rendering individual controls for AJAX Callbacks


:P
On this page:

This isn't exactly a new trick, but when using AJAX without ASP.NET AJAX you can still take advantage of Web Forms and partial page rendering and  I find myself using individual control  rendering called from client script more and more frequently. I can use server side code to render one or more specific controls and use an AJAX callback (without UpdatePanel) to feed the data back to the client to inject the HTML into the page. Rendering individual controls or user controls to use as an HTML result from a callback can be a great way to feed server generated HTML back to the client.

ASP.NET Web Forms make this job pretty easy. To render an individual  control contained on a page (or dynamically created on the fly) only takes a few lines of code. The following generic code renders any non-Postback control or any container that doesn't contain Postback controls:

/// <summary>
/// Renders a control to a string - useful for AJAX partials

/// Works only with non-postback controls
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public static string RenderControl(Control control)
{
    StringWriter tw = new StringWriter();
 
    // *** Simple rendering - just write the control to the text writer
    // *** works well for single controls without containers
    Html32TextWriter writer = new Html32TextWriter(tw);
    control.RenderControl(writer);
    writer.Close();            
 
    return tw.ToString();
}

In the form show below I use this exact mechanism to update the Listview control in the 'popup' list.Basically the page renders with all the controls on the page and when the user clicks the Show Entries button - a relatively rare operation - the Entry list pops up/becomes visible and with the Listview control data loaded from an AJAX callback.

PopoverList

The code that's responsible for doing the callback is not all that interesting here - it uses my West Wind Ajax Toolkit and the wwMethodCallback control to do a Page level callback, but it could be implemented with any AJAX client toolset. The key is that you need to be able to route a request to a particular method on the server that handles the rendering. I use the wwMethodCallback control which auto routes calls to a Page method with a [CallbackMethod] attribute but you can just as easily pass a query string parameter and route the call from any of the page events.

In my case I'm routing to this server side Page method that's just a few lines of code:

[CallbackMethod]
public object ShowEntries()
{
    // *** Load data and bind list control
    string filter = this.lstFilter.SelectedValue;
    this.LoadChildEntries(filter);
 
    // *** Render the list view
    string html = wwWebUtils.RenderControl(this.lstEntries);

    string statusHtml = this.divListStatus.InnerText;
 
    return new { listHtml=html,  statusHtml=statusHtml };
}

This code basically binds the server side list view control and then renders it using RenderControl(). LoadChildEntries() handles binding the ListView. It uses a business object to return a LINQ to SQL query, filters the data based on the selection dropdown, and then simply binds the ListView to this data. The only UI code in the method is setting the Listview DataSource and calling DataBind().

The key feature though is that the ListView in this case is not rendered into the page, but rendered individually with RenderControl() and sent to a string. This string - actually it and the status string - are then returned to the client as a JSON result in an anonymous type which is passed back to the client which receives the result and assigns it to the appropriate controls. Here's the extend of the JavaScript code:

function ShowEntries()
{
    var cb = Proxy_GetCallback();
    cb.serverUrl = "ShowProject.aspx?id=" + serverVars.projectPk;
    cb.callMethod('ShowEntries',[],ShowEntries_Callback,OnPageError);
}
function ShowEntries_Callback(result)
{                   
    $("#lstContainer").html(result.listHtml); 
    $("#divStatusLabel").html(result.statusHtml);
 
    EntryList_Dialog.show();   
}

It's a poor man's way of doing partial rendering when you're not using ASP.NET AJAX. But the partial rendering makes this a super easy approach to update the ListView content with any AJAX client implementation.

I've come to appreciate client side coding so I tend to prefer pulling data from the server as data rather than as HTML. Partial rendering for EVERYTHING as UpdatePanel implies is abhorrent to me. However, there are definitely good canidates for partial rendering especially when dealing with visually complex controls like a Listview that has several components for each individual item that would be quite messy to update via code. So server side rendering of the control and HTML embedding actually feels cleaner than other approaches. As it happens it's also very easy to do as shown above. Very little code goes into managing the ListView display beyond the layout.

Caveats of RenderControl

Now before you get too excited about this simple way of control rendering to HTML there are some serious limitations of what you can render through RenderControl() as shown above. The biggest issue is that it doesn't work with Postback controls or if you're trying to render a container control that contains Postback controls. So no textboxes, dropdowns or list or even gridview controls - they all implement IPostbackHandler and so can't be used.

The reason for this is that control renders individually and so renders outside of the current form context. Since there's no form wrapping the control if it's a postback control the rendering fails with an error like:

 ctl00_lstFilter must be placed  inside of form tag with runat=server.

That's a pretty serious limitation. You can't render any input controls or a datagrid/gridview for example (not even in 'read-only' mode), but the ListView control and Repeater work great as does any amount of label or literal text - any read-only content. In reality though, if you are building a client side UI it's probably a good idea NOT to render large areas of the page - input fields, lists etc. - all at once, but rather do modular changes only to the pieces you need to update. Updating things like textbox or lists is easy enough by using AJAX and data callbacks especially if you're using a tool like jQuery.  The things that are difficult to manipulate on the client are complex list displays that go in Repeaters and ListViews and also - unfortunately - grids (although I've already found myself completely ditching Grids for ListView at this point).

In the example above I'm only rendering the ListView control - the rest of the dialog interface stays the same. So the dropdown selection causes the Listview to update, but it doesn't require a change to the dropdown because it's managed on the client. Only the ListView, which is the only thing that I wouldn't want to update through pure client script code, should be rendered from the server.

So RenderControl() is useful in some scenarios. It's also fairly efficient because it renders a single control only. But it's definitely limited when considering the postback issue.

More Control with User Controls

But there's another way that you can accomplish a similar task using UserControls/ASCX files dynamically with a similar approach. So if you have reusable content you want to feed to the client you can implement this content inside of a user control. So rather than putting my pop up dialog right into the same page I would put the whole panel - window, toolbar, statusbar and listview into an ASCX control. The control would take care of its own rendering.

Some time ago Scott Guthrie mentioned this same concept using User Controls as Ajax views. The idea is similar to RenderControl() but a little bit more work is required to make it work with composite controls like a user control. Scott's sample - it turns out - also doesn't work with Postback variables.  But with a little bit of experimenting I managed to get just about anything to render using the following generic implementation of RenderUserControl:

/// <summary>
/// Renders a user control into a string
/// </summary>
/// <param name="page">Instance of the page that is hosting the control</param>
/// <param name="userControlVirtualPath"></param>
/// <param name="includePostbackControls">If false renders using RenderControl, otherwise uses Server.Execute() constructing a new form.</param>
/// <param name="data">Optional Data parameter that can be passed to the User Control IF the user control has a Data property.</param>
/// <returns></returns>
public static string RenderUserControl(string userControlVirtualPath,
                                       bool includePostbackControls, 
                                       object data)
{
    const string STR_NoUserControlDataProperty = "Passed a Data parameter to RenderUserControl, but the user control has no public Data property.";
    const string STR_BeginRenderControlBlock = "<!-- BEGIN RENDERCONTROL BLOCK -->";
    const string STR_EndRenderControlBlock = "<!-- End RENDERCONTROL BLOCK -->";
 
    StringWriter tw = new StringWriter();
    Control control = null;
 
    if (!includePostbackControls)
    {
        // *** Simple rendering works if no post back controls are used
        Page curPage = (Page)HttpContext.Current.Handler;
        control = curPage.LoadControl(userControlVirtualPath);
        if (data != null)
        {
            try
            {
                wwUtils.SetProperty(control, "Data", data);
            }
            catch 
            { 
                throw new ArgumentException(STR_NoUserControlDataProperty); 
            }
        }
        return RenderControl(control);
    }
 
    // *** Create a page and form so that postback controls work          
    Page page = new Page();
    page.EnableViewState = false;            
 
    // *** IMPORTANT: Control must be loaded of this NEW page context or call will fail
    control = page.LoadControl(userControlVirtualPath);
 
    if (data != null)
    {
        try
        {
            wwUtils.SetProperty(control, "Data", data);
        }
        catch { throw new ArgumentException(STR_NoUserControlDataProperty); }           
    }
 
    HtmlForm form = new HtmlForm();
    form.ID = "__t";
    page.Controls.Add(form);
 
    form.Controls.Add(new LiteralControl(STR_BeginRenderControlBlock));           
    form.Controls.Add(control);
    form.Controls.Add(new LiteralControl( STR_EndRenderControlBlock));
 
    HttpContext.Current.Server.Execute(page, tw, true);
 
    string Html = tw.ToString();
 
    // *** Strip out form and ViewState, Event Validation etc.
    Html = wwUtils.ExtractString(Html, STR_BeginRenderControlBlock, STR_EndRenderControlBlock);
 
    return Html;
}
 
/// <summary>
/// Renders a user control into a string into a string.
/// </summary>
/// <param name="userControlVirtualPath">virtual path for the user control</param>
/// <param name="includePostbackControls">If false renders using RenderControl, otherwise uses Server.Execute() constructing a new form.</param>
/// <param name="data">Optional Data parameter that can be passed to the User Control IF the user control has a Data property.</param>
/// <returns></returns>
public static string RenderUserControl(string userControlVirtualPath,
                                   bool includePostbackControls)
{
    return RenderUserControl(userControlVirtualPath, includePostbackControls, null);
}

There's quite a bit more code here for a few reasons. First off there are two render modes - one is lightweight and uses RenderControl() to simply renders the ASCX control using the same approach shown above. This works fine as long as no Postback controls are involved.

For more complete control  - and also more expensive rendering - the code creates a new page, adds a form and then adds the user control to the new page. Adding a <form> control allows the control to render Postback controls.

In addition a couple of literal placeholders are added to strip out just the rendered content from the generated HTML - the actual output from the rendered control contains the <form> tag as well as viewstate and event validation all of which is not needed for the rendered content that gets embedded into the page. That code uses Server.Execute() to actually execute the new page.

Because User Controls are fully self contained and they are more flexible in how they are loaded. RenderControl() requires a control instance to be passed which suggests that the control already is loaded on a page. RenderUserControl() has no such limitations and can be called as part of a Web Service or other Http Handler completely independent of the page.

This is essentially the same approach that Scott's post use with the addition of the <form> control to allow for postback controls to work.

Caveats again

Again there are some caveats here. This approach works well if you purely use the user controls from dynamic loading for AJAX callbacks. I tried some fairly complex user controls and they rendered without any issues, but that doesn't necessarily mean it will work in all cases.

The biggest issues here are control naming. User Controls add a naming container so when you create a control in this ASCX control it'll automatically have a naming container name added to its name. In addition there are also potential issues if you re-render a control that is already embedded into the page. When using the above method a new Page and Form are created and so the naming container naming is based on this new form. There's only one naming container always so the name will always be Ctl00_ctrlNames which may interfere with other controls on a page.

This exercise makes you appreciate what the ASP.NET AJAX UpdatePanel actually has to do to keep the controls on the page valid through data updated from the server.

So this approach - both for single control rendering or user control rendering is best suited for pure AJAX scenarios where you are always dynamically loading through AJAX rather than updating existing controls on the page.

Still even though there are some caveats, I find both of these approaches extremely useful especially for list rendering. Of all the things that I do client side these days updating complex list data is still something that can't be done easily using script code at least not without moving some of the output generation into client script code which is unacceptable. For this sort of thing partial rendering of small chunks of UI works very well and if I can get a ListView to re-render easily I'm a half way there!

Posted in ASP.NET  AJAX  

The Voices of Reason


 

Josh Stodola
April 01, 2008

# re: Rendering individual controls for AJAX Callbacks

This is not necessarily on topic, but I just wanted to say that I admire your intuition with web-based GUIs. Your stuff looks incredibly easy to use.

Rick Strahl
April 01, 2008

# re: Rendering individual controls for AJAX Callbacks

@Josh - well, things often look easy in a blog post but there's often a bit of work to figure out the best way to do things. But yes, I am a firm believer in making the things you do day in and day out easy. Ajax callbacks in particular are something that shouldn't take more than a few lines of code to initiate and consume and making rendering easy when possible is a natural progression I suppose.

Anatoly
April 01, 2008

# re: Rendering individual controls for AJAX Callbacks


Jeff Gombala
July 26, 2008

# re: Rendering individual controls for AJAX Callbacks

This all works well and good, including the what is covered at Encosia, if you are in the ASMX or PageMethod models. However if you try to move this model to WCF you'll encounter that the HttpContext doesn't exist (as to be expected). I have yet to find a way to render a user control in WCF and execute Page_Loads and such.

Let me know if anyone has found a similar solution in WCF.

Rick Strahl
July 27, 2008

# re: Rendering individual controls for AJAX Callbacks

@Jeff - why would you use a service to render page level code? If you want page level code you can use PageMethods or stick with ASMX. The same approach can be used with PageMethods or easier yet, calling back to standard ASPX pages and routing based on query strings. It's not rocket science...

And in any case WCF DOES support an ASP.NET compatible mode where HttpContext and all the rest of the intrinsic objects are available. It's just that if you go that route you really are not making any distinction between ASMX and WCF style services by depending on the ASP.NET semantics.

DotNetShoutout
November 19, 2008

# Rendering individual controls for AJAX Callbacks

Your Story is Submitted - Trackback from DotNetShoutout

RK
December 17, 2008

# re: Rendering individual controls for AJAX Callbacks

You rock Rick (Well... you always do)

Darin
January 13, 2009

# re: Rendering individual controls for AJAX Callbacks

This is pretty cool....however i tried to implement this and can't solve the error:
Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack. thrown by the server.Execute method. I am sure it is something with me control but any ideas what...i have googled this for hours and the simple suggestions do not solve the issue. Any help would really be appreciated.

Jonathan
March 18, 2009

# re: Rendering individual controls for AJAX Callbacks

Hi Rick,
Fisrt of all, thank you and congratulation for all these good quality posts. I have recently found out your website and fell in love of it ;-)
I have tried to implement your code (but in VB.NET) to render in ajax user control with postback.
Unfortunatly I encounter an issue with: HttpContext.Current.Server.Execute(page, tw, true).
tw.ToString value is "", unlike my user control is properly executed (go to page load event in Debug). Using HttpContext.Current.Server.Execute(page, tw, false) instead returns User contol's Html but causes to not sort out the postback problem.
Do you have any idea why? That's so frustrating to find out this awesome solution and to not get it working...
Thanks in advance and please excuse my english, I'm french...;-)

William
April 02, 2009

# re: Rendering individual controls for AJAX Callbacks

I don't thin Jonathan read the no postback part of your article ;)

Also Jon, forget using AJAX controls inside user controls to be rendered this way. The ajax controls create <script> tags which will NOT be evaluated when imported into the DOM.
Also IE strips out script contents that are inserted via innerHTML.

However, you can add your own scripts inside your control like so..

<pre id='getAndEvalMe'>
//<script> Yea! this comment let's us have some intellisense and highlighting

hello = function(){alert("hello");}

hello();

//</script>
</pre>

then of course use the nonrendered code to execute it on callback..

onCallBack = function(response){
$("#someHiddenTag").html(response.html);
eval( $("#getAndEvalMe") );
}

blahblahblah

Maxi
November 30, 2009

# re: Rendering individual controls for AJAX Callbacks

Dear Rick,

where can I find the "wwUtils" class?
I tried Scott approach and it gives me an "unknown error" on binding.
With only that information I can't find what goes wrong.

I want to try your sample but I can't find wwUtils class.

Thank you very much, your articles are great!

Maxi
November 30, 2009

# re: Rendering individual controls for AJAX Callbacks

Dear Rick,

I found it, the wild west web toolkit. thankx.
But the sample code snippet gives me "unknow error" too.
at this line :
Page curPage = (Page)HttpContext.Current.Handler;

I think the problem maybe how I called that .ashx
I am using :
var wRequest = new Sys.Net.WebRequest();
wRequest.set_url(URL);
wRequest.add_completed(Callback);
wRequest.invoke();

Is that the reason? am I lacking the HttpContext?

siva
December 02, 2009

# re: Rendering individual controls for AJAX Callbacks

Would it not be easier to override VerifyRenderingInServerForm() method ? Am I overlooking something ?

Andreas
May 04, 2010

# re: Rendering individual controls for AJAX Callbacks

Hi Rick,

Your implementation of the static method RenderControl in the beginning of your article didn't work for me. The reason is that my UserControl contains a ListView that's bound to an ObjectDataSource. In order to render my UserControl dynamically I had to implement the RenderControl method as follows:

string htmlCtrl;
var ctrl = (MyUserControl)LoadControl("~/Controls/MyUserControl.ascx");
 
using (var sw = new StringWriter())
{
  using (var htw = new HtmlTextWriter(sw))
  {
    ctrl.DesignerInitialize();
    ctrl.DataBind();
    ctrl.RenderControl(htw);
    htmlCtrl = sw.ToString();
  }
}
// use htmlCtrl


Explanation:
DesignerInitialize() initializes the object of MyUserControl (at this point you could override OnInit in order to perform additional stuff).
DataBind() takes care of binding the ListView to its ObjectDataSource. In my case I decided to override MyControl.OnDataBinding in order to initialize the control.

Additionally, I'm using this control in 2 different ways:
* directly inside some other ascx file
* with the RenderControl method in order to pass the html structure back to my page's ajax callback.

In order to prevent the control from executing my initialization code twice I implemented the ascx.cs file as follows:
protected override void OnDataBinding(EventArgs e)
{
  base.OnDataBinding(e);
 
  // executed when initialized via ajax call (LoadControl and RenderControl)
  this.InitializeControl();
}
 
protected void Page_Load(object sender, EventArgs e)
{
  // executed when initialized normally (directly integrated inside ascx file)
  this.InitializeControl();
}
 
private void InitializeControl()
{
  // ...
}


Kind regards,
Andreas

igzkap
July 12, 2010

# re: Rendering individual controls for AJAX Callbacks

public class MP : Page
{
public override void VerifyRenderingInServerForm(Control control)
{

}
}

should do the trick

Tim C
September 28, 2010

# re: Rendering individual controls for AJAX Callbacks

I was not able to get your example to work with skinning and localization. However I was able to get this to work:


         StringBuilder sb = new StringBuilder();
        string fileText;
            using (StringWriter swriter = new StringWriter(sb)) {
                using (HtmlTextWriter htw = new HtmlTextWriter(swriter)) {
                    Control ctl = this.Page.LoadControl(filePath);
                    Reflector.InvokeMethod(ctl, "InitRecursive", new object[] { ctl });
                    ctl.RenderControl(htw);
                    fileText = sb.ToString();
                    swriter.Close();
                }
            }


Optimally I would also like post back controls to be included as well for true templating. Reflector is a custom class of mine that exposes pretty standard reflection methods.

DaniB
February 06, 2012

# re: Rendering individual controls for AJAX Callbacks

The postback works alright but is there any way to wire up a button click in the code behind of the control? Those event handler are no longer getting fired after injecting a control with this method. Does anyone know a workaround for this?

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024