This post is over 6 months old. Some details, especially technical, may have changed.

Unobtrusive JavaScript in MVC3

One of the "it's not new but it's cool" features that appeared in the ASP.NET MVC3 Beta was Unobtrusive JavaScript (well actually Unobtrusive Ajax and an unobtrusive validation adapter for the jQuery Validation plugin).  Before we dive into how this differs from MVC2 lets talk about JavaScript in MVC.

First things first - jQuery is now the defacto standard for any ASP.NET MVC solution.  In the MVC2 Project Template you got both the Microsoft Ajax Library and jQuery.  This hasn't changed in MVC3 but the Microsoft stuff is only there for any potential backward compatibility issues a solution may have - feel free to just delete these files and embrace jQuery.  All the new client side stuff is all facilitated through jQuery which means you wont have to have some other framework on your page just because generated code mandates it.

What Is It?

Unobtrusive JavaScript (in the MVC3 sense) is a strategy that ensures that no JavaScript is embedded within the markup (unless you do it yourself).  100% no generated code muddying your markup.  No code islands, no inline event handlers, better handling of failure cases and no dependence on any specific framework.  To me, being a web focused developer, this is HUGE and it should be to you too - it is after all considered best practise.

Turn It On

There are two ways to turn Unobtrusive Ajax/Validation on,

1. Web.Config

Within the <appSettings> config node in Web.Config you can specify whether unobtrusive JavaScript is on or off

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

2 On a Per-Page Basis

Just like EnableClientValidation it is possible to activate unobtrusive JavaScript  at a page level.

<% Html.EnableUnobtrusiveJavaScript(); %>

What Does It Do

Best way to show what it does is by comparison to, um, Obtrusive JavaScript.  So lets create a scenario in both MVC2 and MVC3 and seeing how they compare.  The scenario will be a simple ajaxified login form (Username and Password) with some client validation and no server side magic.  Most of this, bar the client side scripts, is the same across both solutions. 

The Model

public class Credentials
{
    [Required]
    public string Username { get; set; }
    [Required]
    public string Password { get; set; }
}

Two properties with basic [Required] validation. 

The Controller

public class HomeController : Controller
{
    public ActionResult Login()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Login(Credentials credentials)
    {
        return new EmptyResult();
    }
}

Again there isn't much going on here, this all about the client side!

The View

Common Parts

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Common.Model.Credentials>" %>

<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
	Login
</asp:Content>

<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <h2>Login</h2>
    <% using (Ajax.BeginForm(new AjaxOptions { OnSuccess = "onSuccess" })) { %>
        <%=Html.ValidationSummary(true)%>
        <p>
            <%=Html.LabelFor(c => c.Username)%>
            <%=Html.TextBoxFor(c => c.Username)%>
            <%=Html.ValidationMessageFor(c => c.Username)%>
        </p>
        <p>
            <%=Html.LabelFor(c => c.Password)%>
            <%=Html.PasswordFor(c => c.Password)%>
            <%=Html.ValidationMessageFor(c => c.Password)%>
        </p>
        <input type="submit" value="Login" />
    <%} %>

    <!-- JAVASCRIPT LIBRARIES GO HERE! -->

    <script type="text/javascript">
        function onSuccess() {
            // logged in, carry on
        }
    </script>
</asp:Content>

A simple view that is the same across both projects the only thing that is going to differ is the actual libraries/scripts that do all the wiring up. 

MVC2

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

Using the out of the box Microsoft Ajax Library (which is now deprecated)

MVC3

    <script src="<%=Url.Content("~/Scripts/jquery-1.4.1.min.js")%>" type="text/javascript"></script>
    <script src="<%=Url.Content("~/Scripts/jquery.validate.min.js")%>" type="text/javascript"></script>
    <script src="<%=Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")%>" type="text/javascript"></script>
    <script src="<%=Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")%>" type="text/javascript"></script>

Using the out of the box jQuery plugins.

Done.  That's all we need for our solutions to function.  The form will be validated on the client side and submission will be performed through ajax.  Both will behave exactly the same but the markup and code they generate will be quite different.  I've tidied both up in terms of formatting just so it's slightly easier to read but I've tried to keep the layout style consistent across both.

MVC2 Output

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Login </title>
    <link href="/Mvc2/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <a href="/Mvc2/">Home</a>
    <h2>
        Login</h2>
    <form action="/Mvc2/Home/Login"
          id="form0"
          method="post"
          onclick="Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"
          onsubmit="Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, onSuccess: Function.createDelegate(this, onSuccess) });">
    <div class="validation-summary-valid" id="validationSummary">
        <ul>
            <li style="display: none"></li>
        </ul>
    </div>
    <p>
        <label for="Username">Username</label>
        <input id="Username" name="Username" type="text" value="" />
        <span class="field-validation-valid" id="Username_validationMessage"></span>
    </p>
    <p>
        <label for="Password">Password</label>
        <input id="Password" name="Password" type="password" />
        <span class="field-validation-valid" id="Password_validationMessage"></span>
    </p>
    <input type="submit" value="Login" />
    </form>
    <script type="text/javascript">
    //<![CDATA[
        if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
        window.mvcClientValidationMetadata.push({
            "Fields": [{
                "FieldName": "Username",
                "ReplaceValidationMessageContents": true,
                "ValidationMessageId": "Username_validationMessage",
                "ValidationRules": [{
                    "ErrorMessage": "The Username field is required.",
                    "ValidationParameters": {},
                    "ValidationType": "required"
                }]
            }, {
                "FieldName": "Password",
                "ReplaceValidationMessageContents": true,
                "ValidationMessageId": "Password_validationMessage",
                "ValidationRules": [{
                    "ErrorMessage": "The Password field is required.",
                    "ValidationParameters": {},
                    "ValidationType": "required"
                }]
            }],
            "FormId": "form0",
            "ReplaceValidationSummary": false,
            "ValidationSummaryId": "validationSummary"
        });
    //]]>
    </script>
    <script src="/Mvc2/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="/Mvc2/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
    <script src="/Mvc2/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
    <script type="text/javascript">
        function onSuccess() {
            // logged in, carry on
        }
    </script>
</body>
</html>

MVC 3 Output

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Login </title>
    <link href="/Mvc3/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <a href="/Mvc3/">Home</a>
    <h2>
        Login</h2>
    <form action="/Mvc3/Home/Login"
          data-ajax="true"
          data-ajax-success="onSuccess"
          id="form0" method="post">
    <p>
        <label for="Username">Username</label>
        <input data-val="true" data-val-required="The Username field is required." id="Username" name="Username" type="text" value="" />
        <span class="field-validation-valid" data-valmsg-for="Username" data-valmsg-replace="true">
        </span>
    </p>
    <p>
        <label for="Password">Password</label>
        <input data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="password" />
        <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true">
        </span>
    </p>
    <input type="submit" value="Login" />
    </form>
    <script src="/Mvc3/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
    <script src="/Mvc3/Scripts/jquery.validate.min.js" type="text/javascript"></script>
    <script src="/Mvc3/Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>
    <script src="/Mvc3/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        function onSuccess() {
            // logged in, carry on
        }
    </script>
</body>
</html>

Analysis

The first obvious thing we can see is that the UnobJS code is slightly lighter, even with strict formatting turned it generally produces less LOC's.  But that's not really that important in the grand scheme of things, but is important is the whole unobtrusiveness of the UnobJS source.

In the old MVC2 code look at those inline event handlers at lines 14 and 15, look at that huge code island at line 33, smack bang in the middle of our HTML.  It's not wrong per say but it's certainly not best practise.  No it's always best to keep your View (HTML) and your Code (JavaScript) separated as much as possible.  Imagine if some wayward script was added between line 61 and 62 did something like this

window.mvcClientValidationMetadata = null;
document.forms[0].onclick = function() { };
document.forms[0].onsubmit = function() { };

OHT3HNOES END OF THE WORLD!

Or what if your CDN that served your scripts was down?  You are actually going to get JavaScript errors during form submission which can, in various browsers, prevent the form being submitted.

OHT3HNOES END OF THE WORLD AGAIN!

MVC 3/UnobJS on the other hand isn't going to error out and will fall back to a normal form submission - progressive enhancement or just expected behaviour?

On other thing... those inline event handlers pretty much need to have the MS Ajax Library available to work (and not cause errors).  What if I was already using jQuery for my project?  I'd still need to include MS Ajax on the page even though only generated code required it (well I could write my own API mimicking the required MS Ajax API but why should I?).

Now Unobtrusive JavaScript isn't without it's issues.  Using HTML5's data- attributes can invalidate your HTML which can be a showstopper for some projects.  One other issue that we may start to see is that EVERYONE is starting to use data- attributes (e.g. jQuery Mobile) I wonder if we are going to start seeing collisions between different libraries?

All in all I don't think there should be an option to turn Unobtrusive JavaScript off, it should just be the only way of doing things :-) but thats just me.

Published in JavaScript .NET on October 22, 2010