Skip navigation

Using AJAX with ASP.NET MVC

Lessons learned writing AJAX code for ASP.NET MVC

Today, any Web framework hosts a good deal of AJAX features, and ASP.NET MVC is no exception. When it comes to AJAX, all programming interfaces differ from one another in the way each hides its own calls to the underlying XMLHttpRequest object. Typically, in ASP.NET MVC you use two wrappers for low-level XMLHttpRequest calls: the jQuery API for AJAX and native AJAX helpers. AJAX helpers are sort of markup factories based on a specific AJAX-oriented JavaScript library that comes with ASP.NET MVC.

If you use the jQuery API for AJAX, then you do everything from the client side. Your code that places the calls and processes the results is written entirely in JavaScript and is under your control. The HTTP endpoint that is the target of the AJAX call can be an existing Web service (reached in accordance with standing same-origin policies) or, more likely, an action method on some ASP.NET MVC controller that returns JSON data. This approach offers maximum flexibility at the cost of padding your pages with a lot of JavaScript code.

In this article, however, I want to address another ASP.NET MVC approach to AJAX based on helper components. This approach doesn’t have an official name. In many ways, it's similar to classic ASP.NET partial rendering, but done better. To make a distinction, I’ll call it selective update. In the rest of the article, I’ll discuss the subtleties of using selective update extensively.

The Mechanics of Selective Update

Selective update refers to an application’s ability to refresh only a fragment of the current view in response to some user actions. User actions are essentially of two types: clicking on hyperlinks (known as action links in ASP.NET MVC jargon) and submitting the content of a form.

With action links, a user triggers an asynchronous action on some HTTP endpoint, receives any markup being returned, and uses that to refresh a specific section of the existing view. You can configure the AJAX request to be GET or POST; the composition of the URL determines which parameters are passed to the target.

With AJAX forms, you simply post the content of the HTML form to which the clicked submit button belongs. You can configure the HTTP verb to be GET or POST and include additional parameters (beyond the content of the input fields) at will.

As mentioned, selective update results in an asynchronous request that is controlled via some script code injected by the framework. The script code lives in the MicrosoftMvcAjax.js file that is automatically included in any new ASP.NET MVC project you create in Visual Studio. The following code shows the script tags you need to incorporate in any page that uses selective update. This code snippet downloads the scripts from the Microsoft CDN, but you're free to modify the URL to suit any of your more specific needs.

 

<script type="text/javascript"
   src="http://ajax.microsoft.com/ajax/4.0/MicrosoftAjax.js">
</script>
<script type="text/javascript"
   src="http://ajax.microsoft.com/ajax/mvc/MicrosoftMvcAjax.js">
</script>

 

An AJAX form in ASP.NET MVC looks like the code Figure 1 shows. The form markup is generated using the Ajax.BeginForm helper method and posts the content of the form to the Update method of the current controller. (The helper offers an additional overload for you to also specify the name of the target controller.)

The form submission request executes according to the specified options. In particular, based on the code Figure 1 shows, a user will see an update progress screen during the operation and a particular segment of the user interface will be replaced at the end. Figure 2 shows the markup generated by the code in Figure 1.

As you can see, a JavaScript function named handleSubmit hooks up the form submission event. Its first step consists of stopping the default event handler from doing its job. This means that the classic browser-led form submission process won’t take place. Next, the JavaScript function replaces the default behavior with a custom process that posts the form content asynchronously.

The request is placed using XMLHttpRequest and an internal JavaScript callback function receives any response generated by the target URL. The callback wraps the response in a JavaScript object and triggers a client-side life cycle process that gives you the ability to handle the completion, success, or failure of the operation. Unless you change the default behavior, the DOM element pointed to by the UpdateTargetId is updated with any returned markup in case of success. The modification applied can be a replacement or insertion depending on the specified insertion mode. The handleSubmit function is also responsible for displaying and hiding any markup associated with the DOM element pointed to by LoadingElementId. Typically, this markup will be an animated GIF or wait message to notify the user of the pending operation.

Client-Side Events

To some extent, you can customize the default behavior of the AJAX form post by using a few client-side events fired by the aforementioned handleSubmit function. Figure 3 lists the JavaScript events you can handle from the client page to customize the form processing.

Note that names under the Events column are names of properties defined on the AjaxOptions class. You set them with strings that correspond to the names of JavaScript functions; Figure 4 shows an example.

The OnComplete function is invoked before the outcome of the operation is determined; the function fires regardless of whether the next call will be for OnSuccess or OnFailure. You can programmatically stop the AJAX request in either of two ways. If you assign a value to the Confirm property of the AjaxOptions class, ASP.NET MVC will display a confirmation message box. By clicking Cancel, the user stops the operation. In addition, by writing a handler for OnBegin that returns false you tell the framework not to proceed with the operation.

During the execution of an AJAX request, three JavaScript callbacks may be involved. The first is OnBegin, which fires just before the request is placed. Next, you receive OnComplete followed by either OnSuccess or OnFailure.

Partial Views

Transforming an ASP.NET MVC application into an AJAX application is not a hard task; it can be summarized in two steps. First, you turn all HTML forms into AJAX forms using the AJAX BeginForm helper and its options. Second, if the default behavior you get out of it is not exactly what you want, use client-side events to alter it as you wish.

However, in doing so, sooner or later you'll run into some snags. The following code illustrates the standard structure of a controller method when you employ AJAX calls:

 

\\[AjaxOnly, HttpPost\\]
public ActionResult Insert(InsertInputModel inputModel)
\\{
    var model = GetInsertViewModel(inputModel);
    return PartialView(model);
\\}

 

The AjaxOnly attribute (shown below) is a custom one you can use to ensure that the given method is only invoked via AJAX. The PartialView method loads a user control and builds a view object that contains a chunk of HTML instead of an entire page. This is an easy way to handle AJAX calls especially when you have master pages around.

 

public class AjaxOnlyAttribute: ActionMethodSelectorAttribute
\\{
    public override Boolean IsValidForRequest(
          ControllerContext controllerContext, MethodInfo methodInfo)
    \\{
       return controllerContext.HttpContext.Request.IsAjaxRequest();
    \\}
\\}

 

Now let’s review some of the challenges you might run into when using AJAX with ASP.NET MVC.

Handling Failures Effectively

The AJAX infrastructure you find in ASP.NET MVC offers two distinct callbacks to handle success and failure of a request. These callbacks might seem sufficient, but let's say you have a view that contains a classic HTML form and a submit button. All you want to do is post any content asynchronously and refresh a particular DIV in the page with any response. What should you do in case of a server error?

The first option is to signal an error from within the controller method by raising an exception. In this case, the remote call ends with an internal server error notification (HTTP 500) and the client receiver code understands it has to call the OnFailure JavaScript callback.

What if you want to display an error message or, more generally, what if you want to control the next user interface from the server? In this case, you must return a partial view but with the clear indication that an error has occurred. If you simply point the controller method to the error view and return that, the JavaScript receiver code has no way to determine that a failure occurred. The HTTP status is 200 (OK) and, subsequently, the OnSuccess callback will be invoked. On a pragmatic note, you should consider that in some cases it doesn’t really matter whether you're routed through OnSuccess or OnFailure.

The difficulty occurs if you need to apply a different behavior on the client for success and failure. In an AJAX scenario, a successful operation likely requires that you display a temporary message to notify success and then route the user to the next screen. In case of failure, you want to display the message and stay on the current page.

To solve the issue, I created a new action result class (Figure 5) that operates like a standard view result—except that it allows you to set the HTTP status. Figure 6 shows how you use this action result class. The action result object will take the specified view, populate it with the response object (it's a custom class that typically contains a Boolean flag and error information), and return the final markup.

Detecting Cookie Expiration

In classic ASP.NET, you're allowed to have protected pages that only authorized users can access. In ASP.NET MVC, the same underlying mechanism is exposed through the Authorize attribute. The feature works well, except when you have a lot of AJAX requests in the application and the authentication cookie expires.

If you place a non-AJAX request, and the authentication cookie has expired (i.e., the timeout is set to just a few minutes and the user leaves his desk), you run into some problems. On the next request, the mechanism detects the cookie is invalid and correctly redirects you to the specified login page. Unfortunately, the login markup is sent in the context of an AJAX call so it likely will end up being inserted in any DOM location where the original response was expected. The solution consists of adding code to the method that will display the login view. The sample method below produces a view with a login box:

 

public ActionResult Index(String returnUrl)
\\{
   if (!String.IsNullOrEmpty(returnUrl))
   \\{
       return RedirectToAction("Relogin", "Account");
   \\}
   :
\\}

 

If the login URL is invoked with the return URL parameter set, and the application is only doing its work with AJAX, then you can conclude that you need to return just the login box instead of the classic login page. You redirect to an AJAX login page that outputs markup like that shown below:

 

<div>
   

You need to reconnect


    <a href="/home/index"> Click here to log in again </a>
</div>

 

This message will appear no matter what the user does after the authentication cookie expires. To log back in, a user simply follows the link. If you’re using both AJAX and HTML requests, things are trickier and you have to track in some way whether or not the original request is AJAX. Checking the Request object from a method like Index won’t work because that request has been originated as HTTP 302 and doesn’t natively contain any specific AJAX header.

Final Thoughts

Another common issue you might encounter when using AJAX in ASP.NET MVC is updating multiple segments of the view after an async request. To address this issue, though, some modifications are required to the AJAX programming interface of the entire MVC framework. You can find your way to that by serving your own copy of the MicrosoftMvcAjax.js file, or you can wait for MVC3, where this feature is expected to be addressed.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish