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.
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!
Other Posts you might also like