Introduction
I’ve been doing Web development since the mid-90s. I started in PERL and CGI, but when someone
showed me Active Server Pages, I went over wholeheartedly. I loved the productivity gains available in
ASP as compared to PERL. I was efficient
and powerful. Forward 10 years and MVC
and jQuery are similarly exhilarating.
Conventions became standardized, and I could build apps within a
framework quickly, not to mention that determining which HTML SELECT OPTION was selected was
suddenly trivial. Could it get any
better? Well, yes. I recently had a similar “woo-hoo” moment with
jQuery’s Unobtrusive extensions within ASP.NET MVC. While figuring out how to make it work, I did
not find many clear how-tos online, and I'm not sure it's that obvious, so I’d like to share with you the
basics here.
Form Validation
It’s nigh impossible to write a Web application without validating
user input. It’s good to try to
constrain the user to doing The Right Thing™ through the UI, but inevitably a
user will misuse/abuse your form, and you need to be sure this doesn’t
break your software. I lobby strongly for doing
server-side validation. While there are sometimes
good reasons to do validation on the client, the majority of the time, I’d
rather put validation in the single place it has to be anyway: the server.
Aside: Why do I say validation must go on the server? Consider someone deliberately trying to attack
your application. They won’t be using
the client-side validation you programmed in JavaScript. They’ll be making direct HTTP calls with some
utility, bypassing the validation you put in script. So if you have any hope of writing a secure
application, the DRY Principal (Don’t Repeat Yourself) and process of
elimination indicates that validation goes on the server.
In the “old days”, developers wrote FORM tags and INPUT
buttons, which sent an HTTP requests to the server, and when the responses came
back, the pages reloaded with the result. Along came the
[strangely cased] XMLHttpRequest, and if you had the patience for writing the
plumbing, you could figure out ways to submit forms (or any arbitrary data in response to any DOM event for
that matter) and receive responses without the page reload. AJAX and jQuery (among other libraries)
improve dramatically on that programming model, but there is still potentially a
significant amount of response-handling to code which might often be more cumbersome than necessary.
If you’re doing server-side validation, taking advantage of
ASP.NET MVC’s partial views, testable controllers, DataAnnotations or custom
validation attributes, etc., and you haven’t used the Unobtrusive JS approach,
you’re in for a treat. There are
tradeoffs for this approach as compared to doing it by hand with jQuery
$.ajax(), which I will address at the end, but it’s certainly handy for a great
many cases and important to have in your Web developer’s utility belt.
The Project Requirements
My arbitrary project to demonstrate Unobtrusive JS in an
account setup page for a microbrewery.
It will accept the user’s username, a password, and a birth date. It will pretend to validate the username’s
uniqueness, ensure the password is good, and the input birth date makes the
user at least 21. If any of the form
inputs is invalid, the user will get some red error text and be allowed to try
again until they succeed. The page will
not be reloaded during the setup since this is the 21st century;
everything will be done with AJAX. Also,
the development work will be done as efficiently as possible.
Create the MVC App
I’m using Visual Studio 2012, but since there’s nothing I
need from MVC 4 for this, I’ll keep things a little simpler and
backward-compatible for Studio 2010 by going with MVC 3.
Create a new ASP.NET MVC 3 Web Application
called “Microbrewery” and choose to set it up as Empty since we want to
understand everything required.
You now have a Web.config, Global.asax, basic
_Layout.cshtml, some CSS and JS files.
Create the Model
While I typically separate my domain models (part of the
business layer) from my view models (part of the presentation layer), I’m
eschewing that approach for simplicity, so there will just be “models”. I do not recommend this convenience for
production code.
Add a new class under Models called “Account”
Give Account string properties Username,
Password, ConfirmPassword, and DateTime property BirthDate.
Apply the
System.ComponentModel.DisplayNameAttribute to ConfirmPassword and BirthDate so
they’re presented nicely in the UI.
Apply the System.ComponentModel.DataAnnotations.RequiredAttribute
to every property.
Apply the StringLengthAttribute
to each of the
string properties setting the maximum length to 20 and the minimum to 4.
Apply the CompareAttribute
to the password
fields, having each one point to the other for equality comparison.
Hm, how to do validation for the birth date to
check that the users are 21? No, JavaScript
is not the answer.
Create a Custom AgeValidation Attribute
Determining whether a user is 21 or older is a simple date
subtraction, but it’s not so common or simple to be included in the standard
set of DataAnnotations. Fortunately, it
is easy to create a custom ValidationAttribute.
Create a project folder called “Utility”
Within Utility, create a class called
“AgeValidationAttribute
” that derives from
System.ComponentModel.DataAnnotations.ValidationAttribute
.
Create a member integer called “MinimumAge” and
create a parameterized constructor that initializes MinimumAge with a specified
value.
Override the IsValid
method that takes an object
and ValidationContext as parameters.
In the overridden IsValid, from DateTime.Today
, calculate
the latest date someone could be born and also be the age specified by
MinimumAge. If they are not old enough,
return a ValidationResult
with a meaningful error message. Otherwise return null.
Now go back to where we left off and apply
AgeValidationAttribute
to BirthDate in the model and specify 21 in the
constructor.
Create the Views
The model is ready, so create a view to present the user. Also create a simple “thank you” page for
when the user successfully registers.
Create a directory under Views called “Account”.
Add a Razor view that does
not specify a master page, as we want to use the _ViewStart and
_Layout.cshtml. Check the box to create it as a partial view so that no HTML is included. Specify that the view should be strongly-typed and select the Account model class. Name the view "Index" and add it.
Create a DIV to contain the page content.
In the DIV, start by doing things the old way
with a using statement and Html.BeginForm
.
In the form’s scope, have an Html.EditorForModel
and an <input type=”submit”>
. EditorForModel
may be too crude for many production scenarios, but it’s a great time-saver for
demonstrations.
- Add a similar partial view called “Thanks”
under Account and just have it output “Thanks” in an H2.
Create the Controller
Now create the controller to handle the POST when the user
submits the form.
Create a controller under Controllers called
“AccountController”.
A default HttpGet Index method is created by
default and is fine as-is.
Create an overloaded Index method that requires
HttpPost and accepts the Account model as a parameter. Clicking “Submit” will POST to here.
In the POST-accepting Index, if the ModelState
is valid, send the user to the Thanks view using the View method.
Obviously, in a real application, this would offload some work to the
business layer.
If ModelState
is not valid, send them back to Index partial view, again using the View method, to
try again.
The First Run
I’m still pretty much infatuated with the state of the art
in that hitting F5 with so little work results in a page that accepts user
input and validates it. I feel so
productive! But wait, this much has been
around a while, and oh, there’s more.
At this point, I filled out the form, pressed a button, and
if I didn’t do the form right, I got some red text that indicates what to
fix. But nowadays that page flash while
the POST is being moved over the Internet looks rough. It would be nice if things looked more like they
do using client-side validation. It’s
that immediacy and clean presentation that some might argue is the benefit of
client-side validation. With AJAX we can
get the best of both worlds: pretty and well-designed.
Logical Flow
The flow is straightforward and should be familiar. The container/framework for the site is the standard _Layout.cshtml. Each time a View is rendered on GET or POST, it is placed within _Layout during RenderBody and the entire HTML document is returned to the client, replacing the current page.
Switch from HTML to AJAX
This is a surprisingly simple thing to change using
Unobtrusive JS. Just about all the heavy
lifting will be done for you, and almost all the code written this far remains
unchanged.
In Index.cshtml, change the Html.BeginForm
to
Ajax.BeginForm
.
You will notice you need to provide some
direction to Ajax.BeginForm, namely the HTTP method and a DIV to target for updates. These and several other options are neatly wrapped in the AjaxOptions
class.
Create a new AjaxOptions and set the following
values. By providing an AjaxOptions instance
to Ajax.BeginForm, the output HTML FORM will include a number of attributes starting
with “data-ajax”, mapping to the properties set.
HttpMethod = “POST”
UpdateTargetId = “ParentDiv”
You will notice I specified UpdateTargetId
as “ParentDiv”,
which does not yet exist. We will want this
as the pattern to use for the entire microbrewery site, so in Views/Shared edit
_Layout.cshtml and wrap the RenderBody
call in a DIV with an ID of
“ParentDiv
”. This will place a DIV
around the entirety of the Index view when it’s rendered, whether as a normal
GET or loaded up after POST with an error-ridden view! Future pages can rely on using this immediate
parent as a target too.
In the POST-accepting Index, change the returned
View calls to PartialView
calls. If left as View,
the entire HTML document from the top of _Layout.cshtml would be returned. While this should probably work, it’s sloppy. Since the page is already loaded, we’re only
looking to replace the DIV containing the page-specific content, so it’s best
to use PartialView which includes only the essential HTML from the .cshtml.
By default, the MVC 3 application’s web.config
has an appSettings key indicating UnobtrusiveJavaScriptEnabled = true
. I think Unobtrusive JS is awesome enough to
leave it there, but in case you’re extending an older app for which that might
create an issue, it’s also possible to set this to true in the controller’s
constructor with HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
And finally the secret sauce. In the _Layout.cshtml, right after the jQuery library, add a script tag
for jquery.unobtrusive-ajax.min.js.
F5 it now!
The Second Run
As you can see, when you receive the response from the
server (valid or not) there’s no whole-page refresh. If there are errors, the problem textboxes
just seem to light up and error messages appear. If it went alright, you immediately see “Thanks”. Be sure to try various validation scenarios,
where the passwords are too short or too long or don’t match. And of course put in a Birth Date that would
make you younger than 21 to see that custom work.
You may note there is a minor visual bug because I used
EditorForModel and the out-of-the-box styles for everything. You’ll notice when the page redraws with
errors, the textboxes are just a hair shorter than they were during the
data-entry part. This happened before in
the page-reload version, but it’s really obvious now and breaks the spell. The simplest fix for now is just to edit
input-validation-error in Site.css so that border is 2px instead of 1px. In your production solutions, you will
likely have more sophisticated styling.
Logical Flow with AJAX
The flow is still straightforward but more targeted. In this demonstration, ParentDiv surrounds the call to RenderBody, which returns the Index View when the users GET the page. This is standard behavior. The key change is that when users POST, the controller returns its result as a Partial View and replace the HTML contents of ParentDiv.
Final Analysis
I think by now the advantages of this approach are pretty
clear. Server-side validation (the “only”
validation) is well-supported and the development process is ultra-streamlined. Developers using this approach to build forms
will find themselves highly productive, particularly because there is so little
to learn to make things seamless and Ajaxy.
But I know some developers are thinking this approach is too heavy-handed. This is because
the controller is responsible for providing a full-blown PartialView, whole blocks of HTML, to replace on the client.
This is behavior similar to the old ASP.NET UpdatePanel which has its
advocates and detractors. I’ll admit to
generally being in the second group since too often developers do not know what’s
going on behind the scenes, leading to unmaintainable, sluggish and/or spaghetti-like
pages. Regardless, the bottom line is that more data is being
passed back and forth than is strictly necessary. Instead of sending a concise block of JSON
result data, this approach sends formatted, verbose HTML.
So we are led to a very common consideration in all
development: productivity versus performance.
To be clear, I never advocate bad performance. What I advocate is “good enough” performance. "Make it work. Make it right. Make it fast." Those are in order. If writing a search engine or popular social portal, yeah,
performance is pretty important, and you may be writing XMLHttpRequests by hand. If you are writing a LOB application, which I’m
guessing you are, performance timings below a second are probably not going to
be noticed by any user ever. So since,
say, 500-millisecond versus 100-millisecond response times don’t matter, I’d
rather spend my time making the business more money by getting more work done. Later when my form grows in size and
complexity, or the user base increases load, and I can actually see performance
degradation, then I will refactor to using $.ajax()
calls and handling specific
responses.
Finally, there are many special cases where the UI isn't doing simple forms input data-collection. Imagine a page that continually grows in size dynamically at the user adds data to it. Perhaps weekly summaries are collapsed into jQuery UI Accordions, and each week's data can be expanded and edited. If treated as one large dynamically-growing form, performance will degrade over time as more data is added. Unobtrusive AJAX validation as used here is inappropriate. This is but one example, and I'm sure there are others. Ultimately, you as the designer and developer need to exercise good judgment.
Finished Project
The finished project is attached as a Visual Studio 2012 solution for your reference.