The MVC framework: adding AJAX to your app

In the last post we’ve already seen one way to add AJAX to an MVC app: using the AJAX JQuery methods in order to get a JsonResult from the server. In this post we’re going to take a look an another approach: using the MVC AJAX helpers (which are based on the MS client AJAX API) for submitting all the forms’ fields and refreshing a specific zone on the page (ok, if you know ASP.NET AJAX, you can say that this is "”AJAX a la UpdatePanel” :),,). Before going on, you should keep in mind that I do prefer to use the previous approach where the data are returned from the server and the UI is built on the client side. However, this other approach might also be a valid in some scenarios (and I’ve noticed that many people do prefer it over the other option). After this short intro, it’s time to start writing code…

To illustrate how everything work, we’ll suppose that we want to perform the update of the info introduced on a form without a postback.  To achieve this, we’ll start by creating a new class that will be used by the default binder to recover the info submitted by the form:

public class User {
  public Int32 UserId { get; set; }
  public String Name { get; set; }
}

Now, we can start thinking about the way we’re going to ajaxify the form. In this case, we’ll be replacing the form’s content with the HTML that is returned from the server side. To achieve that, we’ll start by creating a partial view which will only generate the form’s fields. I’m going to call it myform:

<%@ Import Namespace="MVCRC.Controllers"%>
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<User>" %>
<label id="userIdLabel" for="userid">User id:</label>
<%= Html.TextBox( "userid") %>
<%= Html.ValidationMessage( "userid") %>
<br />
<label id="nameLabel" for="username">Name:</label>
<%= Html.TextBox( "name") %>
<%= Html.ValidationMessage( "name") %>
<br />
<input type="submit" value="Change info" />
<br />
<%= Html.ValidationSummary() %>
<p />
<small>Last update: <%= DateTime.Now %></small>

As you can see, the partial view contains only the fields (and validators) required for presenting the user and allowing its edition. It’s important to notice that the HTML form element isn’t rendered in the partial view. Since we’re at it, I think that it’s good time to mention that, in this case, we’ll need to use an “AJAX Form” (a term I‘ve just made up:) – I hope I’m not breaking any copyright) . An “AJAX form” is an HTML form which injects JS code that intercepts its submission and starts a partial postback (instead of the traditional full postback). We’ll be talking more about this code when we look at the client infrastructure. Generating an “AJAX form” is easy: just invoke the BeginAjaxForm (extension) method available on the AjaxHelper class.

Until now, I hadn’t mentioned this class (it introduces further methods, which we’ll leave for a future post). For today, we’re only going to take a look at the BeginForm method. As you might expect, you have several overloads of this method that you can use to have complete control over the url that will be used for posting data back to the server. We’ve already talked about these parameters when we saw how to generate traditional forms, so we’re not looking at them here again.

However, we do need to talk about a new parameter that these methods introduce (when compared with the helpers that generate traditional forms). I’m talking about the ajaxOptions parameter (of type AjaxOptions) which is used for setting several properties which are reused by the client infrastructure. In the current version, you can set the following properties:

  • Confirm: string which presents a message (window.confirm) that is shown to the user before the AJAX call is made. If you don’t set this property, the confirm window won’t be shown;
  • HttpMethod: user for setting the HTTP method that is going to be used by the AJAX call. Notice that you must specify a string, not a value from an enumeration;
  • InsertionMode: a value from the InsertionMode which decides how the HTML returned from the AJAX call is inserted on the page;
  • LoadingElementId: the client ID of an element which is shown during the AJAX call. You can use this to specify the ID of an HTML element that shows some sort of progress bar during the partial postback;
  • OnBegin: name of a method that is called at the beginning of the partial postback on the client side. You can return false from this method if you which to stop the current AJAX request;
  • OnComplete: lets you pass the name of a method that will be called when the response arrives from the server. Returning false from this method cancels the rest of the default client processing;
  • OnFailure: you can pass the name of a method that will be called whenever you get an error status on the response (excluding 304 and 1223);
  • OnSuccess: lets you indicate the name of a method that will be called after the payload is loaded and the HTML of the page is updated;
  • UpdateTargetId: string which identifies the client ID of the container that should be updated with the HTML returned from the server;
  • Url: you can use this property to specify the url to which the AJAX call should be made. If you don’t pass a value to this property, the form’s action attribute is used instead.

Before showing the code of the view, I’d also like to add is that there’s also a BeginRouteForm that lets you specify the url in terms of routes. Now, we’re ready to take a look at the code of the view:

<%
    var options = new AjaxOptions{
      HttpMethod = "GET",
      UpdateTargetId = "holder",
      LoadingElementId = "info",
      Confirm = "Are you sure you want to try to update this user?",
      InsertionMode = InsertionMode.Replace,
      OnBegin = "handleBegin",
      OnComplete = "handleComplete",
      OnFailure = "handleFailure",
      OnSuccess = "handleSuccess"
    };
    using( var ajaxForm = Ajax.BeginForm( "HandleAjaxUpdate", options ) )
    {
  %>
    <div id="holder">
     <% Html.RenderPartial("myform"); %>
     </div>
  <%}%>
  <div id="info" style="visibility:hidden">Trying to update…</div>

Since this is just demo code, I’ve decided to set as many properties as I could 🙂 Lets start with the AjaxOptions instance. We’re saying that we’re making a GET request and the target whose contents should be updated is the #holder element (the div that is placed inside the form). We’re
also specifying the loading element. In the previous example, we’ll be showing a div which contains a simple message. Do notice that you need to hide it on the initial display because you’re only interested in seeing it during the partial postback. The confirm message (notice that the Confirm property is set) will be show whenever the users submits the form.

We’ve also passed strings that identify all the client methods that can be called, though we won’t be doing any work on them (if you’re copying the code, you can add breakpoints and see what going on at each moment).

As we’ve seen, we need to use the BeginForm method to generate the form. In this case, we’re redirecting the form to the HandleAjaxUpdate method (which we’ll take a look at in a few moments). Before that, lets take a look at the signatures of the client methods:

<script type="text/javascript">
    function handleBegin(context) {
      printInfo(context);
    }

    function handleComplete(context) {
      printInfo(context);
    }

    function handleFailure(context) {
      printInfo(context);
    }

    function handleSuccess(context) {
      printInfo(context);
    }

    function printInfo(context) {
      var info = ["insertion mode:", context.get_insertionMode(), "-",
                  "loading id; ", context.get_loadingElement().id, "_",
                  "target id; ", context.get_updateTarget().id];
      alert(info.join(""));
    }
</script>

AS you can see, they’re really similar: all of them receive an instance of the client Sys.Mvc.AjaxContext class. This class exposes several properties which let you access the current request object, the update target element, the loading element, etc. We’ll come back to this class in a future post when we take a deep dive at the client infrastructure. There’s still an important detail that needs to be done to conclude the work in the view: we need to add the script references to the client AJAX files. Here’s how you can do that:

<script src=”<%=Url.Content( "~/Scripts/MicrosoftAjax.debug.js") %>” type="text/javascript"></script>
<script src=”<%=Url.Content( "~/Scripts/MicrosoftMvcAjax.debug.js") %>” type="text/javascript"></script>

Now it’s time to take a quick peek at the server HandleAjaxUpdate method:

public ActionResult HandleAjaxUpdate(User user) {
  Thread.Sleep(2000); //sleeping for seeing message
  ValidateUserId(user); 
  ValidateName(user); 
  return ModelState.IsValid ? 
         View(“Infosaved”) : View("myform", user);

private void ValidateName(User user) {
  if (String.IsNullOrEmpty(user.Name)) {
     ModelState.AddModelError("name", "Name cannot be empty");
  }

private void ValidateUserId(User user) {
  if (user.UserId < 0) {
   ModelState.AddModelError("userid",
                         ser Id must be a positive number");
  }
}

As you can see, we’ve got similar code to the one I’ve in several of the previous posts. The main difference here is that when there’s an error, we simply return the partial view (which is strongly typed and is automatically filled from the current info available on the action method). WHen everything is ok, we return a view which gives that message to the user.

And that’s it: with these simple steps you know have “partial postbacks a la UpdatePanel”. There’s still more to say about AJAX in MVC but I’ll leave that for the next posts. Keep tuned!

Advertisements

~ by Luis Abreu on March 3, 2009.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: