By Dino Esposito
For a few moments when I first approached ASP.NET
MVC I really thought it was the negation of AJAX. With a programming model
heavily based on REST principles, I thought all that one could do is invoke a
URL. And when you get, or post to, a URL, you inevitably involve the browser
and get a full-page refresh. As I ve explored it more, and as the facts have
largely shown, my first impression was dead wrong. AJAX is definitely possible
in ASP.NET MVC.
The role of JavaScript is clearly relevant and
fundamental. The ASP.NET MVC Framework simply provides its own set of
JavaScript files and utilities to make AJAX calls occur in much the same way
they occur in WebForms applications. In this article I ll go through the
various 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 a
smarter layer of code on top of the same ASP.NET runtime.
In the WebForms model, the request that comes in is
dispatched to an HTTP handler that maps the URL to a server file. It reads the
content of the server file, parses that to a C# class, compiles the class into
an assembly, and invokes a well-known method on the class. The well-known
method is ProcessRequest, one of the members of the IHttpHandler interface that
represents the public contract for any ASP.NET page. The C# class that defines
the expected behavior for the page derives from a system class (the
System.Web.UI.Page class). This class is nothing more than a built-in HTTP
handler albeit one of the most complex HTTP handlers ever written.
The ASP.NET MVC Framework abstracts some of the
steps in the procedure just described. For example, the URL is not necessarily
bound to a server file. The URL is simply the representation of the requested
resource. This means that a module intercepts the request, parses the URL, and
forwards the request to a controller component. The role that the page class
plays in WebForms is split between controller and view in ASP.NET MVC. I dare
say that ASP.NET MVC smartly refactors ASP.NET WebForms, but works on top of
the same runtime environment and, therefore, according to the same set of
global rules. If AJAX is possible in WebForms, it has to be possible in ASP.NET
MVC, as well.
JavaScript Makes AJAX Run
At its core, AJAX is about making an out-of-band
request to the web server via XMLHttpRequest. A piece of JavaScript code
prepares the call, runs it asynchronously, then processes the response in a
callback function. The callback function is responsible for updating the user
interface with downloaded data using the DOM services. To make developers
lives a bit easier, Microsoft provided in ASP.NET AJAX such facilities as the
partial rendering API and JavaScript proxy for Web services.
To be picky, I could say that Microsoft didn t
provide a very simple API to make AJAX calls that hide the nitty-gritty details
of the XMLHttpRequest object. JavaScript proxies are great, but they require a
Web or WCF service at the other end. The Microsoft AJAX client library does
offer a WebRequest object, but it takes a few lines of code to prepare it and
make it work. Other libraries, primarily the jQuery library now included in
ASP.NET AJAX, make up for this with their own API.
The package that contains the ASP.NET MVC install
also includes the jQuery library, so you can use this library (or any other
similar libraries) to prepare your direct AJAX calls directed at URLs of
choice. This is the most natural way of having AJAX in ASP.NET MVC
applications. However, Microsoft provides facilities to make AJAX happen in a
way that is similar to partial rendering in WebForms.
That said, I feel the need to clarify a key point
once and for all. There s only one way of placing AJAX calls, and it s all
about using the XMLHttpRequest object. On top of this, some frameworks and
JavaScript libraries have built their own infrastructure, with the sole purpose
of making programming easier and faster.
Direct Scripting
I like to use the expression direct scripting to
refer to the scenario where you have some JavaScript code that places calls to
an HTTP endpoint and receives a semantic response, such as JSON data or
primitive 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 page
is a button or in general some event handler, no matter how defined. Figure 1
shows some JavaScript code that uses jQuery to attach an onclick handler to a
button.
<input type="button"
id="Button1" value=" Get customers"></input>
<select
id="ddCustomerList" name="ddCustomerList"
size="8">
</select>
<script type="text/javascript"
src="/Scripts/jquery-1.2.6.min.js"></script>
<script
type="text/javascript">
$(document).ready(function() {
$('#Button1').click(function() {
$.getJSON("/Home/GetCustomers",
null,
function(data) {
addCustomers(data); });
});
});
function addCustomers(data)
{
// Get the DOM reference to the
drop-down list. Note that
// $("#id") returns a wrapped
set; 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 = new
Option(cust.CompanyName, cust.CustomerID);
list.add(option);
};
};
</script>
Figure 1: An
example of direct scripting using jQuery
Among other things, the sample page contains a
button and a dropdown list. The button is given its onclick handler as the
document is fully loaded. The handler uses the $.getJSON function from the
jQuery library to download a response. Once downloaded, the response is
processed by the callback associated with the $.getJSON call (the addCustomers
function in Figure 1). This function assumes to receive a collection of data
and simply loops through it. For each encountered object, it creates an optional
DOM 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 endpoint
that returns JSON data. In ASP.NET MVC, though, creating a controller action
that returns JSON couldn t be easier. In Figure 1, the $.getJSON function
points to the /Home/GetCustomers URL. Such a URL is mapped to the GetCustomers
method on the Home controller, as shown here:
public
JsonResult GetCustomers()
{
NWindDataContext context = new
NWindDataContext();
context.DeferredLoadingEnabled = false;
List<Customer> list = (from c in
context.Customers
select
c).ToList<Customer>();
return this.Json(list);
}
A controller action that you want to invoke via
AJAX doesn t need to be connected to a view object. More simply, it can return
a plain JSON string. In this case, you define the method to return a JsonResult
object and create it via the Json helper method on the controller base class.
The Json method takes any .NET object and
serializes it to JSON using a given content type and encoding. The Json method
has a few overloads that end up calling the one shown here:
protected
virtual JsonResult Json(object data,
string contentType, Encoding contentEncoding)
Internally, the Json method invokes the
JavaScriptSerializer class from the .NET Framework to serialize a .NET object
to JSON.
It is worth noting that the object you attempt to
serialize to JSON must be serializable and should not include circular
references. This might be a common source of trouble if you use a LINQ to SQL
object model to represent your data. In the preceding code snippet, a list of
Customer objects should be returned to the client and the list of Customer
objects is obtained via a LINQ to SQL query. I ve placed Customer and Order
entities 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 has
an entity set named Orders and the Order class points back to a Customer
object. This sets up a circular reference and causes the Json method to fail.
There are two possible workarounds. One is using a
data transfer object (DTO) instead of the real entity class. The other is
disabling the lazy loading feature so that no relationships are further
expanded and no circular reference is generated. The latter option is easier to
code; the former is more general. Using a DTO simply means copying the content
in 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 DTO
approach allows you to transfer only what s strictly needed.
The ActionLink AJAX Helper
If you don t like writing that much JavaScript code
yourself, you can opt for the ActionLink AJAX helper method. The Ajax.ActionLink
helper method generates a hyperlink in the page that points to a controller
action and runs a callback function when data has been received.
No jQuery is required, as all the machinery is
available in the MicrosoftMvcAjax.js file that must be included in the page
along with the MicrosoftAjax.js file from the Microsoft AJAX client library:
<script
src="<%= Url.Content("~/Scripts/
MicrosoftAjax.js") %>"
type="text/javascript"></script>
<script
src="<%= Url.Content("~/Scripts/
MicrosoftMvcAjax.js") %>"
type="text/javascript"></script>
You use the Ajax.ActionLink method within code
blocks, as shown here:
<%=
Ajax.ActionLink("Get customers",
"/GetCustomers", new AjaxOptions {
OnSuccess="addCustomersEx" })%>
The code generates a hyperlink that points to the
same 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 third
argument is a collection of optional settings to use in the call. At the very
minimum, you must specify the JavaScript callback that runs upon a successful
completion of the call. The ActionLink method has several more overloads; the
one 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 the
source code of the addCustomersEx callback function:
function
addCustomersEx(context)
{
// Grab the method s response
var response = eval(context.get_data());
// Invoke the addCustomers function of
Figure 1
// to populate the dropdown list
addCustomers(response);
};
The success callback, as well as any other
callbacks you can specify in the AjaxOptions object, receives only one argument
of type AjaxContext. Figure 2 shows the members of the object. To get the
response as plain data, call the get_data method and pass it through the
JavaScript s eval function to transform a JSON string into a usable JavaScript
object.
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 following
form:
<a
href="/Home/GetCustomers"
onclick="Sys.Mvc.AsyncHyperlink.handleClick(
this, new Sys.UI.DomEvent(event), {
insertionMode:
Sys.Mvc.InsertionMode.replace, onSuccess:
Function.createDelegate(this, addCustomersEx)
}
);">
Get
customers
</a>
As you can see, AJAX in ASP.NET MVC is definitely
possible, but all of it happens through JavaScript.
Partial Rendering in ASP.NET MVC
The AJAX ActionLink method also can be used to
implement a sort of partial rendering. If the controller action returns HTML
markup, then this content can automatically be inserted in to the inner space
of the specified DOM element. To get this, simply specify the element to update
in the AjaxOptions settings and, optionally, the desired insertion mode:
<%=
Ajax.ActionLink("Details",
"/GetCustomerDetails", new
AjaxOptions {
LoadingElementId="lblWait",
UpdateTargetId="pnlDetails" })%>
The lblWait DOM element is displayed for the time
it takes to download the response, then is hidden. The pnlDetails DOM element,
instead, is updated with the content downloaded. Of course, this approach works
well if the downloaded content is an HTML string. Here s a sample controller
method:
public
string GetCustomerDetails(string id)
{
// Return HTML
:
}
I recommend that any element you use as the loading
element be initially hidden from view using CSS.
Conclusion
In this article I ve demonstrated the simplest way
to get AJAX in an ASP.NET MVC solution. In particular, I focused on direct
scripting and the AJAX ActionLink helper method. The ActionLink approach,
though, also lends itself very well to implement a sort of partial rendering in
ASP.NET MVC. Because of space constraints I could only scratch the surface of
this topic. Next month I ll provide full coverage of partial rendering in
ASP.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.