Unobtrusive JavaScript in ASP.NET MVC3 with JQuery

Unobtrusive JavaScript in ASP.NET MVC3 with JQuery

Published on: Monday, June 20, 2011 9:45:00 AM)

I've recently experimented a little bit with some of the new unobtrusive JavaScript stuff that is now included as part of the default ASP.NET MVC 3 project template. While not an overly complex subject, it's been one of them plugins I wanted to have a dabble with, but I didn't quite find the time until now. And I must say, it was a lot easier to sort out than I thought it might be! I should point out that although this is an ASP.NET MVC 3 example, the concepts and the fact that this is mostly just JQuery means that it's not limited to that, earlier versions of ASP.NET MVC can use happily, as can WebForms, PHP, Ruby and even Classic ASP!

Some things we'll try and do in this blog post:

Creating a basic application:

Firstly, I created a ASP.NET MVC 3 Web Application (Empty) in VS2010 and got to work updating the JavaScript libraries that were in it. This can be done via the NuGet Console, or by right clicking "References" in the Solution Explorer, "Add Library Reference" and then clicking the update side-tab. From there you can update all the default stuff.

Done that? Good. Next we'll add a default controller, a view model to pass the data around & two views (one for allowing us to edit the form and the other as a success page). It's worth noting that I'm doing nothing that special on these and that I'm using the default _Layout.cshtml to apply the header and footer as it sees fit.

/Controllers/BlackCoffeeController.cs

using System.Web.Mvc;
using UnobtrusiveAjaxExample.ViewModels.BlackCoffee;
 
namespace UnobtrusiveAjaxExample.Controllers
{
    public class BlackCoffeeController : Controller
    {
        [HttpGet]
        public ActionResult Rocks()
        {
            RocksViewModel viewModel = new RocksViewModel();
            return View(viewModel);
        }
 
        [HttpPost]
        public ActionResult Rocks(RocksViewModel viewModel)
        {
            if (!ModelState.IsValid) return View(viewModel);
            // Do something Database-y here.
            return View("Success");
        }
    }
}

/ViewModels/BlackCoffee/RocksViewModel.cs

using System.ComponentModel.DataAnnotations;
 
namespace UnobtrusiveAjaxExample.ViewModels.BlackCoffee
{
    public class RocksViewModel
    {
        [Required]
        public string Forename { get; set; }
 
        [Required]
        public string Surname { get; set; }
 
        [Required]
        public string Message { get; set; }
    }
}

Then the two views, firstly: /Views/BlackCoffee/Rocks.cshtml

@model UnobtrusiveAjaxExample.ViewModels.BlackCoffee.RocksViewModel
 
@{
    ViewBag.Title = "Rocks";
}
<h2>Black Coffee Rocks?</h2>
<div id="content">
@Html.ValidationSummary()
@using(Html.BeginForm())
{
    <ul>
	<li>
            @Html.LabelFor(x => x.Forename)
            @Html.EditorFor(x => x.Forename)
            @Html.ValidationMessageFor(x => x.Forename)
        </li>
	<li>
            @Html.LabelFor(x => x.Surname)
            @Html.EditorFor(x => x.Surname)
            @Html.ValidationMessageFor(x => x.Surname)
        </li>
	<li>
            @Html.LabelFor(x => x.Message)
            @Html.EditorFor(x => x.Message)
            @Html.ValidationMessageFor(x => x.Message)
        </li>
</ul>
    <div><input name="Continue" type="submit" value="Continue" /></div>
}
</div>

And then /Views/BlackCoffee/Success.cshtml

@{
    ViewBag.Title = "Success";
}
<h2>Success</h2>
<p>Congratulations! You've successfully declared BLack Coffee as being totally rockin'!</p>

At this point now, you should be able to build and run your application and navigate to /BlackCoffee/Rocks and verify that the page redraws the form unless all boxes are filled in. If you do fill them all in, you should get the success page.

Adding JQuery Validation with barely any effort for standard Data Annotations.

With certain DataAnnotations such as the [Required] attribute and [StringLength], you should be able to get simple JQuery client side checks, which while not a replacement for server side checks, add some speedy feedback which will be especially welcomed by users who find themselves struggling over long distance or slow connections. The key point to note here is that the checks are performed both sides (client and server) - so it doesn't matter if the user has JavaScript disabled or is using a browser not capable of running it - the checks will still get done. It's just an added bonus.

To do, simply add references to the JavaScript files to your html's <head> tag. In this case, I'm going to add them directly to the _Layout.cshtml file.

<script src="/Scripts/jquery-1.6.1.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>

You'll spot that there are 4 included files there - the last one (the JQuery.unobtrusive-ajax.min.js) is for the next part - but we'll put it in now in preparation.

You should find that the Web.Config file already has the following lines in already (specifically lines 10 & 11), but have a gander for the following and if it doesn't exist or is set different to this, update the file appropriately.

<appSettings>
    <add key="webpages:Version" value="1.0.0.0" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

We're now cooking with gas! Save, Build and try out your application. You should be able to spot that when clicking your submit button, the page doesn't go away to the server at all to check the field validates, it's doing it on the client. Good stuff!

Adding Unobtrusive AJAX into the mix.

This should be a relatively easy process, but there are a few things we need to consider sorting out before we continue too much further:

The first part is quite simple. Within the Rocks.cshtml we created earlier which is our edit form view, we used the HtmlHelper class to create the form tag. All we need to do now is change that to use the AjaxHelper class and pass in some options and we're off to a winner!

@using(Ajax.BeginForm(new AjaxOptions() { HttpMethod = "Post", UpdateTargetId = "content" }))

You should be able to see there that there is AjaxOptions class "newed up" directly in the view. If you're uncomfortable with doing this, you could create it directly in the view model and pass it in from there. There are a ton of options that you can go into, most of which are touched upon on Brad Wilson's blog post on this subject. You'll also spot we provide a UpdateTargetId - this represents the HTML element into which all the view result is going to get dumped into. In this case, our DIV on our Rocks.cshtml page - but this could be anything and in fact nothing, if you don't want to spew out content back to the user for whatever reason.

If you ran the application now, you'd find that all the AJAX and the JQuery would work great, but as part of the Ajax Request, the header and footer would be returned and very soon you'd end up with a webpage that looks like it's been designed by someone who's been watching Inception too much. This thankfully (or more specifically thanks to the ASP.NET team), isn't a problem as we can do a little check before returning the view while in the appropriate controller method. Using the example above, you'd be able to change it to something like this:

[HttpPost]
public ActionResult Rocks(RocksViewModel viewModel)
{
    if (!ModelState.IsValid) return View(viewModel);
    // Do something Database-y here.
    if (Request.IsAjaxRequest()) return PartialView("Success");
    return View("Success");
}

You'll spot with this example we are returning exactly the same view. However, by returning it as a Partial view, we are ensuring it does not get rendered as part of a bigger page, so all of the headers / footers and other things included via layout pages are ignored.

Other things you can do to improve stuff:

There are a plenty of other very easy things you can add too, such as: