Tip/Trick: Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios
I've been having some fun playing around with the ASP.NET AJAX Beta release this weekend.
Usually when I integrate AJAX functionality into my code I just end up using the built-in server controls that ASP.NET AJAX provides (UpdatePanel, UpdateProgress, etc) and the cool controls in the ASP.NET AJAX Control Toolkit. Scott Hanselman had jokingly called using these AJAX controls "cheating" when he interviewed me two weeks ago for his latest podcast - since they don't require that you write any client-JavaScript for most common scenarios.
This weekend I decided to focus my coding on some of the client JavaScript pieces in the ASP.NET AJAX framework that don't use UpdatePanels at all, and to experiment with alternative ways to use the server to easily generate HTML UI that can be dynamically injected into a page using AJAX. In the process I created what I think is a pretty useful library that can be used with both ASP.NET AJAX and other AJAX libraries to provide a nice template UI mechanism with ASP.NET, and which doesn't use or require concepts like postbacks or viewstate - while still providing the benefits of control encapsulation and easy re-use.
First Some Quick Background on the JavaScript Networking Stack in ASP.NET AJAX
To first provide some background knowledge about the client JavaScript library in ASP.NET AJAX before getting to the template approach I mentioned above, lets first walkthrough building a simple AJAX "hello world" application that allows a user to enter their name, click a button, and then make an AJAX callback to the server using JavaScript on the client to output a message:
ASP.NET AJAX includes a very flexible JavaScript network library stack with rich serialization support for .NET data-types. You can define methods on the server to call from JavaScript on the client using either static methods on your ASP.NET Page class, or by adding a web-service into your ASP.NET application that is decorated with the [Microsoft.Web.Script.Services.ScriptService] meta-data attribute and which exposes standard [WebMethod] methods.
For example, below is a SimpleService.asmx web-service with a "GetMessage" method that takes a string as an argument:
using System.Web.Services;
[Microsoft.Web.Script.Services.ScriptService]
public class SimpleService : WebService {
[WebMethod]
public string GetMessage(string name) {
return "Hello <strong>" + name + "</strong>, the time here is: " + DateTime.Now.ToShortTimeString();
}
}
ASP.NET AJAX can then automatically create a JavaScript proxy class to use on the client to invoke this method and pass appropriate parameters to/from it. The easiest way to add this JavaScript proxy class is to add an <asp:ScriptManager> control on the page and point at the web-service end-point (this control also does the work to ensure that each library only ever gets added once to a page).
I can then call and invoke the method (passing in a value from a textbox), and setup a callback event handler to fire when the server responds using client-side JavaScript code like below. Note: I could get fancier with the JavaScript code to eliminate some of the lines - but I'm deliberately trying to keep it clear and simple for now and avoid adding too much magic:
<head id="Head1" runat="server">
<title>Hello World Service</title>
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript">
function callServer() {
SimpleService.GetMessage( $get("Name").value, displayMessageCallback );
}
function displayMessageCallback(result) {
$get("message").innerHTML = result;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" >
<Services>
<asp:ServiceReference Path="~/SimpleService.asmx" />
</Services>
</asp:ScriptManager>
<h1>Hello World Example</h1>
<div>
Enter Name: <input id="Name" type="text" />
<a href="javascript:callServer()">Call Server</a>
<div id="message"></div>
</div>
</form>
</body>
</html>
Now when I run the page and enter a name "Scott", the page will use AJAX to call back and dynamically update the HTML of the page without requiring any postbacks or page refreshes.
A cleaner approach to generate HTML using templates
As you can see from the example above, I can easily return HTML markup from the server and inject it into the page on the client. The downside with the approach I am taking above, though, is that I am embedding the HTML generation logic directly within my server web method. This is bad because: 1) it intermixes UI and logic, and 2) it becomes hard to maintain and write as the UI gets richer.
What I wanted was an easy way to perform my logic within my web service method, retrieve some data, and then pass the data off to some template/view class to generate the returned HTML UI result. For example, consider generating a Customer/Order Manager application which uses AJAX to generate a UI list of customers like this:
I want to write server code like below from within my WebService to lookup the customers by country and return the appropriate html list UI. Note below how the ViewManager.RenderView method allows me to pass in a data object to bind the UI against. All UI generation is encapsulated within my View and out of my controller webmethod:
public string GetCustomersByCountry(string country)
{
CustomerCollection customers = DataContext.GetCustomersByCountry(country);
if (customers.Count > 0)
return ViewManager.RenderView("customers.ascx", customers);
else
return ViewManager.RenderView("nocustomersfound.ascx");
}
It turns out this wasn't too hard to enable and only required ~20 lines of code to implement the ViewManager class and RenderView methods used above. You can download my simple implementation of it here.
My implementation allows you to define a template to render using the standard ASP.NET User Control (.ascx file) model - which means you get full VS designer support, intellisense, and compilation checking of it. It does not require that you host the usercontrol template on a page - instead my RenderView implementation dynamically cooks up a dummy Page object to host the UserControl while it renders, and captures and returns the output as a string.
For example, here is the Customer.ascx template I could write to generate the customer list output like the screen-shot above which generates a list of customer names that have links to drill into their order history:
<div class="customers">
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<div>
<a href="javascript:CustomerService.GetOrdersByCustomer('<%# Eval("CustomerID") %>', displayOrders)">
<%# Eval("CompanyName") %>
</a>
</div>
</ItemTemplate>
</asp:Repeater>
</div>
And its associated code-behind file then looks like this (note: I could add view-specific formatting methods into this if I wanted to):
public partial class Customers : System.Web.UI.UserControl
{
public object Data;
void Page_Load(object sender, EventArgs e)
{
Repeater1.DataSource = Data;
Repeater1.DataBind();
}
}
For passing in the data to the template (for example: the customers collection above), I initially required that the UserControl implement an IViewTemplate interface that I used to associate the data with. After playing with it for awhile, though, I instead decided to go with a simpler user model and just have the UserControl expose a public "Data" property on itself (like above). The ViewManager.RenderView method then does the magic of associating the data object passed in to the RenderView method with the UserControl instance via Reflection, at which point the UserControl just acts and renders like normal.
The end result is a pretty powerful and easy way to generate any type of HTML response you want, and cleanly encapsulate it using .ascx templates.
Finishing it Up
You can download the complete sample I ended up building here. Just for fun, I added to the above customer list example by adding support for users to click on any of the customer names (after they search by country) to pop-up a listing of their orders (along with the dates they placed the order). This is also done fully with AJAX using the approach I outlined above:
The entire application is about 8 lines of JavaScript code on the client and a total of about 15 lines of code on the server (that includes all data access). All HTML UI generation is then encapsulated within 4 nicely encapsulated .ascx template files that I can load and databind my data to from my webmethods on demand:
Click here to download the ViewManager.RenderView implementation if you want to check it out and try it yourself.
Hope this helps,
Scott