When broken down into digestible chunks, the technique is easy to implement and lends your application a level of polish that your users are sure to appreciate. The four steps required to accomplish this will be: building the user control, statelessly rendering the control as HTML, providing progress indication, and using ASP.NET AJAX to request and inject that HTML.
Building the user control
First, we need some slow-loading, auxiliary content to encapsulate in a user control. For this example, that’s going to be a minimal RSS feed reader widget that displays the most recent posts from this site.The key activity here is retrieving my RSS feed and querying it for some basic information. To expedite this, I’m going to use one of ASP.NET 3.5′s great new features: LINQ to XML. LINQ really makes short work of this normally tedious task. It still blows me away every time I use it.
protected void Page_Load(object sender, EventArgs e) { XDocument feedXML = XDocument.Load("http://feeds.encosia.com/Encosia"); var feeds = from feed in feedXML.Descendants("item") select new { Title = feed.Element("title").Value, Link = feed.Element("link").Value, Description = feed.Element("description").Value }; PostList.DataSource = feeds; PostList.DataBind(); }
<asp:ListView runat="server" ID="PostList"> <LayoutTemplate> <ul> <asp:PlaceHolder runat="server" ID="itemPlaceholder" /> </ul> </LayoutTemplate> <ItemTemplate> <li><a href='<%# Eval("Link") %>'><%# Eval("Title") %></a><br /> <%# Eval("Description") %> </li> </ItemTemplate> </asp:ListView>With a little bit of CSS (included in the source download later), this results in something resembling the screenshot to the right. The ListView comes in especially handy for our purposes here, because it gives you such effortless control over the rendered HTML. When injecting generated HTML into a page, you really appreciate knowing exactly what markup to expect.
Rendering the user control as HTML
The next step is to create a web service that statelessly renders our user control as an HTML string. By statelessly, I mean that we need to render this user control outside the context of an active ASP.NET Page instance, where user controls are normally intended to be used.To solve that problem, you can create a temporary instance of the ASP.NET Page class, dynamically add the user control to it, and then execute it within the web service’s context. Doing this turns out to be easier than it is to accurately describe:
[WebMethod] public string GetRSSReader() { // Create a new Page and add the control to it. Page page = new Page(); UserControl ctl = (UserControl)page.LoadControl("~/RSSReaderControl.ascx"); page.Controls.Add(ctl); // Render the page and capture the resulting HTML. StringWriter writer = new StringWriter(); HttpContext.Current.Server.Execute(page, writer, false); // Return that HTML, as a string. return writer.ToString(); }
However, since our Page instance is created outside of the normal ASP.NET HTTP pipeline and only contains one control, the overhead is negligible. The Page itself is fairly performant compared to all of the other work involved in a typical HTTP round-trip.
Setting up the demonstration page
To demonstrate, we’ll need a page with some fast-loading content to provide contrast. Inside that, we can embed an empty DIV which will be used to accurately inject the user control’s rendered HTML.<asp:ScriptManager runat="server"> <Services> <asp:ServiceReference Path="~/RSSReader.asmx" /> </Services> <Scripts> <asp:ScriptReference Path="~/Default.js" /> </Scripts> </asp:ScriptManager> <div id="Container"> <div id="RSSBlock" class="loading"></div> <div id="Content"> <p>Lorem ipsum dolor sit amet, consectetuer adipiscing...</p> </div> </div>
.loading { background: url('progress-indicator.gif') no-repeat center; }
Calling the web service from JavaScript
Now that we’ve got a place to inject it, the final step is to retrieve the HTML rendering of our user control and insert it into the page. ASP.NET AJAX takes all of the hard work out of this step:Sys.Application.add_init(AppInit); function AppInit() { RSSReader.GetRSSReader(OnSuccess, OnFailure); } function OnSuccess(result) { // Remove the .loading CSS from the div, to remove the // progress indicator background. Sys.UI.DomElement.removeCssClass($get('RSSBlock'), 'loading'); // Fill the div with the HTML generated from the user control. $get('RSSBlock').innerHTML = result; } function OnFailure() { // Do something if our callback fails. Retry it, perhaps. }
With the animated background removed, we are now free to insert the rendered HTML into the DIV’s innerHTML. The end result is exactly the same as if we had placed the user control inside that DIV, without unnecessarily delaying the entire page load.
Conclusion
I think you’ll find that this technique is very powerful. It allows you to leverage your existing knowledge of ASP.NET and its server controls as a robust templating solution for lightweight AJAX. At the same time, it exudes the kind of professional usability that typically requires more tedious and less maintainable client side coding.Note that there is not an UpdatePanel anywhere on this page. Using this technique does not require relying on partial postbacks. Not only does that improve performance, but also allows UpdatePanels elsewhere on the page to operate normally, while the deferred content loads.