Practical .NET

AJAX Without JavaScript

You like the idea of an AJAX application, but would rather not write the JavaScript yourself. You're in luck: ASP.NET MVC provides two tools that write the client-side code for you. And these tools even make sense if you’re comfortable with writing your own JavaScript.

Not only are AJAX-enabled applications considered to be cool, they’re good for your server, too. By distributing the work between the client and the server, you reduce the demand on your shared resource (the server), which makes your application more scalable. Plus, AJAX changes your application from the "big bang" approach of submitting a whole page at a time into a series of calls, each of which involves a smaller amount of processing. This helps even out the load on your server.

All of this is good … provided you’re willing to write and test the required JavaScript.

Here are two ASP.NET MVC tools that will write AJAX-enabled JavaScript for you while supporting two typical scenarios. One tool invokes server-side code to validate data as soon as the user enters it in the browser; the other tool fetches and inserts HTML from the server when the user clicks on a link. These scenarios are typical enough that, even if you’re comfortable writing your own client-side script, using these tools will free you up to work on the hard stuff.

One note: If you like either of these tools, read the Setting Up sidebar before you rush to use them. The sidebar provides the changes you need to make to your project to enable the tools.



Integrating Server-Side Validation
When someone enters some data into your page they don’t want to have to wait until they click the submit button to find out if they got it wrong. You can give your users the immediate feedback they want, AJAX-style, without having to write any JavaScript code. All you have to do is decorate the relevant property on your Model object with the Remote attribute and write an Action method/Partial Page combination.

When you add the Remote attribute to your Model class, you must pass the name of the Action method to call, the name of the Controller that the Action method is inside of, and then set the attribute’s ErrorMessage property to the message you want to display when the data is invalid. The following example decorates the FirstName property with the Remote attribute, setting the error message to "Bad Name," and specifying that the ValidateName method in the Validate controller is to be used to check the data the user enters:

Public Class Customer
   Public Property Id As Integer?
   <Remote("ValidateName", "Validate", ErrorMessage:="Bad Name")>
   Public Property FirstName As String
   '...more properties...

The next step is write a server-side method that will accept your field’s data and return a JSON object containing True (if you’re happy with the data) or False (if you’re not). You must give your method a name and put it in the controller that match the settings in your Remote attribute. Your method must also accept a parameter whose name matches the property name in your Model object (Microsoft also says you should put the OutputCache attribute on the method).

Here’s a very simple method that validates my FirstName property:

<OutputCache(Location:=OutputCacheLocation.None, NoStore:=True)>
Function ValidateName(FirstName As String) As ActionResult
  Dim result As Boolean   
  If FirstName = "Peter" Then
    result = True
  Else
    result = False
  End If
  Return Json(result, JsonRequestBehavior.AllowGet)
End Function

The final step is to add a field that uses the property to your View, along with a ValidationMessage to display your error message:

@Html.TextBoxFor(Function(m) m.FirstName)
@Html.ValidationMessageFor(Function(m) m.FirstName)

Cross-Field Validation
If you need additional properties from your Model to validate any particular property, you can include those: Just set the attribute’s AdditionalFields property to a comma-delimited list of property names, drawn from your Model object. This example includes the LastName and Id properties in the data sent with the FirstName:

Public Class Customer
  <Remote("ValidateName", "Validate", AdditionalFields:="LastName, Id", ErrorMessage:="Bad Name")>
  Public Property FirstName As String
  Public Property Id As Integer
  Public Property LastName As String
  '...more properties...

One warning: The properties listed in the AdditionalFields list must be included in the page, if only as hidden fields. A typical View, that includes the Id and LastName properties might look like this:

@Html.HiddenFor(Function(m) m.Id)
FirstName: @Html.TextBoxFor(Function(m) m.FirstName)
@Html.ValidationMessageFor(Function(m) m.FirstName)
LastName: @Html.TextBoxFor(Function(m) m.LastName)

The corresponding Action method would need to have three parameters to accept the FirstName property and the two AdditionalFields. Something like this would do it:

Function ValidName(FirstName As String, LastName As String, Id As Integer?) As ActionResult

Of course, as the number of properties being passed increases, it might make sense just to have your validation method accept your Model object.

Some warnings about timings are in order. The JavaScript code generated by the Remote attribute calls your server-side code the first time the user makes a change to the data and leaves the field. After the user has made a change, though, if the user returns to the field, then your server-side method will be called every time the user presses a key. This is all done asynchronously, so a long-running validation method won’t hold up the user. Still, you’ll want to make sure your method executes quickly so that your user gets his feedback promptly on each keypress.

On the other hand, if the user never makes a change to your data, your validation method will still be called when the page is submitted. This isn’t done asynchronously (the submit will be held up until your validation completes), so that’s another reason to be quick.

Inserting HTML from the Server
The second tool is the AjaxHelper. While you’ve been using the HtmlHelper object from your View’s Html property, the Ajax property (which holds the AjaxHelper object) has at least one cool method also. For example, the ActionLink method lets you retrieve some HTML in an AJAX way and shove it into a page when the user clicks on a link. All you need to write on the server is an Action method to retrieve the data and a Partial View to generate the HTML. In your View you just need to pass some parameters to the ActionLink method.

As with the HtmlHelper’s ActionLink, the first four parameters you pass to the AjaxHelper’s ActionLink method are: The text to display in the hyperlink on the page, the name of the Action method to call, the name of the Controller where your Action method lives and an anonymous object that lets you specify any parameters you want to pass to your server-side method. The fifth parameter for the AjaxHelper’s ActionLink is new: It’s an AjaxOptions object whose properties let you control the AJAX query that will be generated for you.

You must set at least two properties on the AjaxOptions object: UpdateTargetId, which specifies the element on the page that will be updated with the HTML, and InsertionMode, which specifies what happens to anything already in the element being updated.

Here’s an example that calls the GetCustomerById Action method in the Customer Controller, passing the Model’s CustId property. When the HTML is returned, it will replace whatever is inside the element with an id attribute set to CustomerInfo:

@Ajax.ActionLink("Click Me", "GetCustomerById", "Customer", 
                 New With {.CustId = Model.CustId}, 
                 New AJAXOptions With {.UpdateTargetId = "CustomerInfo",                                                                                                                                     
                                       .InsertionMode = InsertionMode.Replace})
<div id="CustomerInfo" />

On the server, you’re going to need an Action method that accepts the data specified in the ActionLink, retrieves the relevant information and passes it to the Controller’s PartialView method, specifying which Partial View method to use.

This example accepts an Id, then uses it to retrieve a Customer object. It then passes that to the PartialView method, specifying the CustInfo Partial View:

Function GetCustomerInfo(Id As Integer) As ActionResult
  Dim cust As Customer
  Using db As New CustomerOrdersContext
    cust = db.Customers.Where(Function(c) c.Id = Id).FirstOrDefault
    Return PartialView("CustInfo", cust)
  End Using
End Function

The CustInfo View might look something like this:

@ModelType CustomerOrders.Customer
<dt>@Html.DisplayNameFor(Function(model) model.FirstName)</dt>
<dd>@Html.DisplayFor(Function(model) model.FirstName)</dd>
<dt>@Html.DisplayNameFor(Function(model) model.LastName)</dt>
<dd>@Html.DisplayFor(Function(model) model.LastName) </dd>

A series of these links (one for each customer, for example) would let the user switch from displaying one customer to another in the CustomerInfo div element -- all without JavaScript.

There’s more to be said about the ActionLink method. You can also specify JavaScript functions to run before and after the generated code, for example … but now you’re back to writing JavaScript. If you want more than an anchor tag, the AjaxHelper’s BeginForm will send all of your form’s data to your Action method as an AJAX request, rather than just a single anchor tag. That might be more AJAX than you want, though.

With these tools you can take care of some typical AJAX-related tasks without having to write any JavaScript. Regardless of whether you just want to make yourself more productive or just have an abiding dislike of JavaScript, these tools are worth adding to your toolkit.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.