By Dino EspositoFor a few moments when I first approached ASP.NETMVC I really thought it was the negation of AJAX. With a programming modelheavily based on REST principles, I thought all that one could do is invoke aURL. And when you get, or post to, a URL, you inevitably involve the browserand get a full-page refresh. As I ve explored it more, and as the facts havelargely shown, my first impression was dead wrong. AJAX is definitely possiblein ASP.NET MVC.
The role of JavaScript is clearly relevant andfundamental. The ASP.NET MVC Framework simply provides its own set ofJavaScript files and utilities to make AJAX calls occur in much the same waythey occur in WebForms applications. In this article I ll go through thevarious aspects of the AJAX API you can leverage in ASP.NET MVC applications.
AJAX in ASP.NET MVC? Why Not!
Compared to WebForms, ASP.NET MVC provides asmarter layer of code on top of the same ASP.NET runtime.
In the WebForms model, the request that comes in isdispatched to an HTTP handler that maps the URL to a server file. It reads thecontent of the server file, parses that to a C# class, compiles the class intoan assembly, and invokes a well-known method on the class. The well-knownmethod is ProcessRequest, one of the members of the IHttpHandler interface thatrepresents the public contract for any ASP.NET page. The C# class that definesthe expected behavior for the page derives from a system class (theSystem.Web.UI.Page class). This class is nothing more than a built-in HTTPhandler albeit one of the most complex HTTP handlers ever written.
The ASP.NET MVC Framework abstracts some of thesteps in the procedure just described. For example, the URL is not necessarilybound to a server file. The URL is simply the representation of the requestedresource. This means that a module intercepts the request, parses the URL, andforwards the request to a controller component. The role that the page classplays in WebForms is split between controller and view in ASP.NET MVC. I daresay that ASP.NET MVC smartly refactors ASP.NET WebForms, but works on top ofthe same runtime environment and, therefore, according to the same set ofglobal rules. If AJAX is possible in WebForms, it has to be possible in ASP.NETMVC, as well.
JavaScript Makes AJAX Run
At its core, AJAX is about making an out-of-bandrequest to the web server via XMLHttpRequest. A piece of JavaScript codeprepares the call, runs it asynchronously, then processes the response in acallback function. The callback function is responsible for updating the userinterface with downloaded data using the DOM services. To make developers lives a bit easier, Microsoft provided in ASP.NET AJAX such facilities as thepartial rendering API and JavaScript proxy for Web services.
To be picky, I could say that Microsoft didn tprovide a very simple API to make AJAX calls that hide the nitty-gritty detailsof the XMLHttpRequest object. JavaScript proxies are great, but they require aWeb or WCF service at the other end. The Microsoft AJAX client library doesoffer a WebRequest object, but it takes a few lines of code to prepare it andmake it work. Other libraries, primarily the jQuery library now included inASP.NET AJAX, make up for this with their own API.
The package that contains the ASP.NET MVC installalso includes the jQuery library, so you can use this library (or any othersimilar libraries) to prepare your direct AJAX calls directed at URLs ofchoice. This is the most natural way of having AJAX in ASP.NET MVCapplications. However, Microsoft provides facilities to make AJAX happen in away that is similar to partial rendering in WebForms.
That said, I feel the need to clarify a key pointonce and for all. There s only one way of placing AJAX calls, and it s allabout using the XMLHttpRequest object. On top of this, some frameworks andJavaScript libraries have built their own infrastructure, with the sole purposeof making programming easier and faster.
Direct Scripting
I like to use the expression direct scripting torefer to the scenario where you have some JavaScript code that places calls toan HTTP endpoint and receives a semantic response, such as JSON data orprimitive data. Direct scripting is an approach to AJAX that works in any case,as it is at the lowest possible level of abstraction. All you need in the pageis a button or in general some event handler, no matter how defined. Figure 1shows some JavaScript code that uses jQuery to attach an onclick handler to abutton.
<input type="button"id="Button1" value=" Get customers"></input>
<selectid="ddCustomerList" name="ddCustomerList"size="8">
</select>
<script type="text/javascript"src="/Scripts/jquery-1.2.6.min.js"></script>
<scripttype="text/javascript">
$(document).ready(function() {
$('#Button1').click(function() {
$.getJSON("/Home/GetCustomers",
null,
function(data) {addCustomers(data); });
});
});
function addCustomers(data)
{
// Get the DOM reference to thedrop-down list. Note that
// $("#id") returns a wrappedset; to get the DOM reference
// you need to specify an index.
var list =$("#ddCustomerList")[0];
// Loop over the returned collection
for (var i = 0; i < data.length;i++) {
var cust = data[i];
var option = newOption(cust.CompanyName, cust.CustomerID);
list.add(option);
};
};
</script>
Figure 1: Anexample of direct scripting using jQuery
Among other things, the sample page contains abutton and a dropdown list. The button is given its onclick handler as thedocument is fully loaded. The handler uses the $.getJSON function from thejQuery library to download a response. Once downloaded, the response isprocessed by the callback associated with the $.getJSON call (the addCustomersfunction in Figure 1). This function assumes to receive a collection of dataand simply loops through it. For each encountered object, it creates an optionalDOM object and adds it to the dropdown list.
How is this approach specific to ASP.NET MVC?Actually, it isn t. Looking at the example, all you need is an HTTP endpointthat returns JSON data. In ASP.NET MVC, though, creating a controller actionthat returns JSON couldn t be easier. In Figure 1, the $.getJSON functionpoints to the /Home/GetCustomers URL. Such a URL is mapped to the GetCustomersmethod on the Home controller, as shown here:
publicJsonResult GetCustomers()
{
NWindDataContext context = newNWindDataContext();
context.DeferredLoadingEnabled = false;
List<Customer> list = (from c incontext.Customers
selectc).ToList<Customer>();
return this.Json(list);
}
A controller action that you want to invoke viaAJAX doesn t need to be connected to a view object. More simply, it can returna plain JSON string. In this case, you define the method to return a JsonResultobject and create it via the Json helper method on the controller base class.
The Json method takes any .NET object andserializes it to JSON using a given content type and encoding. The Json methodhas a few overloads that end up calling the one shown here:
protectedvirtual JsonResult Json(object data,
string contentType, Encoding contentEncoding)
Internally, the Json method invokes theJavaScriptSerializer class from the .NET Framework to serialize a .NET objectto JSON.
It is worth noting that the object you attempt toserialize to JSON must be serializable and should not include circularreferences. This might be a common source of trouble if you use a LINQ to SQLobject model to represent your data. In the preceding code snippet, a list ofCustomer objects should be returned to the client and the list of Customerobjects is obtained via a LINQ to SQL query. I ve placed Customer and Orderentities from the Northwind database in the sample model. As you may recall,there s a relationship set between the entities so that the Customer class hasan entity set named Orders and the Order class points back to a Customerobject. This sets up a circular reference and causes the Json method to fail.
There are two possible workarounds. One is using adata transfer object (DTO) instead of the real entity class. The other isdisabling the lazy loading feature so that no relationships are furtherexpanded and no circular reference is generated. The latter option is easier tocode; the former is more general. Using a DTO simply means copying the contentin Customer to another custom class that doesn t have dependencies on Orders.In this case, you also can optimize the amount of data being transferred.Unless you need on the client all the properties of a customer object, a DTOapproach allows you to transfer only what s strictly needed.
The ActionLink AJAX Helper
If you don t like writing that much JavaScript codeyourself, you can opt for the ActionLink AJAX helper method. The Ajax.ActionLinkhelper method generates a hyperlink in the page that points to a controlleraction and runs a callback function when data has been received.
No jQuery is required, as all the machinery isavailable in the MicrosoftMvcAjax.js file that must be included in the pagealong with the MicrosoftAjax.js file from the Microsoft AJAX client library:
<scriptsrc="<%= Url.Content("~/Scripts/
MicrosoftAjax.js") %>"
type="text/javascript"></script>
<scriptsrc="<%= Url.Content("~/Scripts/
MicrosoftMvcAjax.js") %>"
type="text/javascript"></script>
You use the Ajax.ActionLink method within codeblocks, as shown here:
<%=Ajax.ActionLink("Get customers",
"/GetCustomers", new AjaxOptions {
OnSuccess="addCustomersEx" })%>
The code generates a hyperlink that points to thesame GetCustomers method we considered earlier in the direct scripting example.The first argument you pass to the action link is the text of the hyperlink.The second argument is the controller action to invoke. Finally, the thirdargument is a collection of optional settings to use in the call. At the veryminimum, you must specify the JavaScript callback that runs upon a successfulcompletion of the call. The ActionLink method has several more overloads; theone shown here is the most common and simple of all.
The controller action can return anything,including JSON, JavaScript, or plain HTML markup. The callback, if specified,will handle the response and update the user interface accordingly. Here s thesource code of the addCustomersEx callback function:
functionaddCustomersEx(context)
{
// Grab the method s response
var response = eval(context.get_data());
// Invoke the addCustomers function ofFigure 1
// to populate the dropdown list
addCustomers(response);
};
The success callback, as well as any othercallbacks you can specify in the AjaxOptions object, receives only one argumentof type AjaxContext. Figure 2 shows the members of the object. To get theresponse as plain data, call the get_data method and pass it through theJavaScript s eval function to transform a JSON string into a usable JavaScriptobject.
Member | Description |
get_data | Gets any data returned from the controller action. |
get_insertionMode | Indicates how to treat the response (only if markup), whether to replace, prepend, or append it to the markup of the specified DOM element. The default is replace. |
get_loadingElement | Indicates the DOM element to be displayed to indicate that an AJAX call is going on. |
get_request | Gets the Sys.Net.WebRequest object that represents the current request. |
get_response | Gets the Sys.Net.WebRequestExecutor object for the current request. |
get_updateTarget | Indicates the DOM element to be automatically updated with the returned markup, if any. |
Figure 2: The AjaxContext object
The link emitted in the page takes the followingform:
<ahref="/Home/GetCustomers"
onclick="Sys.Mvc.AsyncHyperlink.handleClick(
this, new Sys.UI.DomEvent(event), {insertionMode:
Sys.Mvc.InsertionMode.replace, onSuccess:
Function.createDelegate(this, addCustomersEx)}
);">
Getcustomers
</a>
As you can see, AJAX in ASP.NET MVC is definitelypossible, but all of it happens through JavaScript.
Partial Rendering in ASP.NET MVC
The AJAX ActionLink method also can be used toimplement a sort of partial rendering. If the controller action returns HTMLmarkup, then this content can automatically be inserted in to the inner spaceof the specified DOM element. To get this, simply specify the element to updatein the AjaxOptions settings and, optionally, the desired insertion mode:
<%=Ajax.ActionLink("Details",
"/GetCustomerDetails", newAjaxOptions {
LoadingElementId="lblWait",
UpdateTargetId="pnlDetails" })%>
The lblWait DOM element is displayed for the timeit takes to download the response, then is hidden. The pnlDetails DOM element,instead, is updated with the content downloaded. Of course, this approach workswell if the downloaded content is an HTML string. Here s a sample controllermethod:
publicstring GetCustomerDetails(string id)
{
// Return HTML
:
}
I recommend that any element you use as the loadingelement be initially hidden from view using CSS.
Conclusion
In this article I ve demonstrated the simplest wayto get AJAX in an ASP.NET MVC solution. In particular, I focused on directscripting and the AJAX ActionLink helper method. The ActionLink approach,though, also lends itself very well to implement a sort of partial rendering inASP.NET MVC. Because of space constraints I could only scratch the surface ofthis topic. Next month I ll provide full coverage of partial rendering inASP.NET MVC, including posting an entire form to a controller. Stay tuned!
Dino Esposito (dino@idesign.net) is an architect at IDesign, specializing in ASP.NET, AJAX, and RIA solutions. Dino is the author of Microsoft .NET: Architecting Applications for the Enterprise and Microsoft ASP.NET and AJAX: Architecting Web Applications.