Friday, October 21, 2011

AJAX and Unobtrusive Javascript in MVC3

AJAX is a fundamental aspect of many web applications, and unobtrusive Javascript, supporting the Separation of Concerns (SOC) principle, has really come into it's own with the introduction of the HTML5 specification. In this post I want to introduce the built-in Microsoft MVC3 support for these features, just enough to get you off the ground.

I have embedded Pluralsight training videos into the post as I go along. The free Pluralsight training videos are often a great quick-and-easy primer to .NET framework features, which show you idealised scenarios from which you can jump off to more tailored solutions.

For each video I provide a short summary of it's contents for easy-access later.

This is the table of contents for the entire 'Introduction to MVC3' series. The videos embedded below are from the section 'AJAX and JavaScript with ASP.NET MVC 3'.

Video 1. CDNs, scripts at the bottom, and RenderSection
The first video is all about ways of laying out your scripts:

Play video
  • First it talks about the performance benefits of using CDN-hosted resources and placing <script> tags etc. at the bottom of your page (as opposed to in the <head>). In particular, unobtrusive javascript allows us to put our <script> tags at the bottom, because we are unhindered by inline javascript dependencies on <head>-loaded .js files.
  • Next, the 'magic' App_Code folder is demonstrated. Razor @helpers (concise versions of HtmlHelpers) can be dropped into this folder and will be precompiled into static methods, accessible from any view.
  • Finally, the RenderSection method is introduced. Like it's MVC2 equivalent, this method allows the master page to control page layout by defining sections. While the pages themselves can optionally define content to populate each section.
        <!-- Master-page scripts, included on all pages -->
        @Content.Script("jquery-1.4.4.min.js", Url)
        @Content.Script("jquery.unobtrusive-ajax.min.js", Url)
        <!-- Page-specific scripts -->
        @RenderSection("Scripts", false)
        <!-- (false means not required) -->

Video 2. Using Ajax.ActionLink to return a PartialViewResult to a specified page element
The second video is a very straight-forward example of an asynchronous request:

Play video
  • An Ajax.ActionLink is embedded into a Razor view
  • ... and configured so that when the link is clicked, a particular controller action is called.
  • The controller action returns a PartialViewResult
  • ...and another parameter of the ActionLink directs the PartialViewResult into a page element (by specifying it's ID)

Note that we haven't written any Javascript or frontend code - we will discuss that shortly.

The view:


@Ajax.ActionLink("Click here to see the latest review",
     "LatestReview",      //The controller action to call
     new AjaxOptions
         UpdateTargetId = "latestReview", //The page id to
         InsertionMode.InsertAfter,       //fill with the
         HttpMetho = "GET"                //PartialViewResult

<div id="latestReview">
    <!-- The PartialViewResult will be inserted here -->
The controller:
public class HomeController : Controller
    public PartialViewResult LatestReview()
        var review = reviewRepository.FindLatest(1).Single();
        return PartialView("Review", review);

The only other things discussed are using different InsertionMode's (InsertAfter, Replace etc.) This specifies how to handle content insertion to the specified <div>.

And adding an extra argument to specify an element to display while the content loads - i.e. a progress / status indicator image.

Video 3: Unobtrusive Javascript
The next video is where things get interesting. We get to see what exactly it is that our Ajax.ActionLink call spits out to the page...

Play video

...which is this:

<div id="latestReview">
    <a data-ajax="true" 
           Click here to see the latest review

All those 'data-dash' attributes (data-ajax, data-ajax-loading etc.) are a part of the HTML5 specification. Or rather, they are not. Anything prefixed by data- is considered by the specification to be a valid attribute, but is known to be there only to support dynamic features of the page. The attributes therefore are ignored by browser rendering engines and HTML markup validation services.

With these attributes carrying all of the custom data for this link's behaviour, all we need is a generic javascript file whose job it is to go looking for elements with data-ajax-* attributes, and to manipulate them to achieve the desired behaviour. This file is jquery.unobtrusive-ajax.js, provided by Microsoft, which must ofcourse be included after the main jQuery library.

3b. Unobtrusive jQuery validation
The video then jumps on to a related topic. Microsoft also happen to include an extension to the jQuery validation library: jquery.validate.unobtrusive.js. This makes validation message-handling also unobtrusive.

The video goes on to show how data annotations on model classes can be easily piped through onto unobtrusive Views.

The model:

public class Review : IValidatableObject
    public virtual int Id { get; set; }
    [Range(1, 10)]
    public virtual int Rating { get; set; }


The view:

@using (Html.BeginForm())
        <div class="editor-label">
            @Html.LabelFor(model => model.Rating)
        <div class="editor-field">
            @Html.EditorFor(model => model.Rating)
            @Html.ValidationMessageFor(model => model.Rating)
        <!-- etc -->

Basically then the HtmlHelpers do all the javascript work for you.

I'll explain how in a minute, but as a brief aside - The data annotations are from the System.ComponentModel.DataAnnotations namespace introduced in .NET 3.5 (notice the the IValidatableObject interface). For those of us using the Enterprise Library's Validation Application Block, it seems that Enterprise Library 5.0 introduces interoperability between the two annotation frameworks, although I haven't tried this myself yet. For a quick intro to the features of the two systems, check out this Stack Overflow post.

Anyway, moving on - The way the HtmlHelpers render your javascript depends on your appSettings:

    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="false" />

Obviously the first needs to be true to render any javascript validation at all. But the second determines the method. When it is false, standard XHTML tags are rendered with a bunch of inline, on-the-page javascript. When it is true, the HTML5 data- tags are rendered and jquery.validate.unobtrusive.js (and jquery.unobtrusive-ajax.js) do all the work.

Video 4. Using Ajax.BeginForm
The fourth video shows how to use Ajax.BeginForm to create small, Ajax-only forms, such as a search query:

Play video

As you can see, it starts to get a bit obvious at this point. Here's the View...

@using(Ajax.BeginForm("Search", "Home", 
    new AjaxOptions
        HttpMethod = "GET",
        InsertionMode = InsertionMode.Replace,
        UpdateTargetId = "searchResults"
    <input type="text" name="q" />
    <input type="submit" value="Search" />

<table id="searchResults">
    <!-- The PartialViewResult will be inserted here -->

...and you can imagine what the Controller method looks like - pretty much the same as the Controller method from video 2!

The rest of the videos go on to discuss:

So it's a bit off-topic for this article, but useful how-to's. I hope you found this article useful, and be sure to check out the rest of the Pluralsight MVC3 training videos.

Monday, October 3, 2011

Selected Items with HtmlHelpers

I came across an odd 'feature' of MVC Html Helpers today: Even if you explicitly provide a selected value when you call an option-based Helper, there's no guarantee that selected value will be used.

Suppose you are creating a view - an edit page for a Book. You declare something like this in your view. Note that we are passing in a new Author object as our currently selected item - so it shouldn't match anything and no items should be selected:

<% var selectList = 
    new SelectList(Model.Authors, "Id", "Name", new Author()); %>

<%=Html.DropDownList("book.Author.Id", selectList, 
    "Please select")%>

The fourth argument to your SelectList constructor is a new Author object. As such when you view your edit page you would expect the drop-down to default to "Please select".

In reality though, the drop-down defaults to the author associated in your model..? This may be the behaviour you normally want from an edit page, but when your business rules suggest that the field not be pre-populated, you can't live up to it without a mild hack.

What's happening is the Html Helper is looking at your View Model object, for an item that looks like the first argument: "book.Author.Id"

When it finds it, the Helper is favouring that Id value over your suggestion. Not exactly putting you in the driving seat is it?

One workaround? It's not the nicest-looking, but set the View Model value to what you want just before calling the Helper:

Model.Book.Author = new Author();
<% var selectList = 
    new SelectList(Model.Authors, "Id", "Name", new Author()); %>

<%=Html.DropDownList("book.Author.Id", selectList, 
    "Please select")%>

It may not be pretty, but it works.