Click here to Skip to main content
15,886,110 members
Articles / Web Development / HTML

Unobtrusive AJAX Form Validation in ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
4.96/5 (21 votes)
15 Nov 2012CPOL12 min read 126.9K   3.9K   46   11
A complete step-by-step tutorial explaining how and why to use Unobtrusive AJAX to do forms validation in ASP.NET MVC.

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. 

  1. 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.
  2. 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.

  1. Add a new class under Models called “Account
  2. Give Account string properties Username, Password, ConfirmPassword, and DateTime property BirthDate.
  3. Apply the System.ComponentModel.DisplayNameAttribute to ConfirmPassword and BirthDate so they’re presented nicely in the UI.
  4. Apply the System.ComponentModel.DataAnnotations.RequiredAttribute to every property.
  5. Apply the StringLengthAttribute to each of the string properties setting the maximum length to 20 and the minimum to 4.
  6. Apply the CompareAttribute to the password fields, having each one point to the other for equality comparison.
  7. 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.

  1. Create a project folder called “Utility
  2. Within Utility, create a class called “AgeValidationAttribute” that derives from System.ComponentModel.DataAnnotations.ValidationAttribute.
  3. Create a member integer called “MinimumAge” and create a parameterized constructor that initializes MinimumAge with a specified value.
  4. Override the IsValid method that takes an object and ValidationContext as parameters.
  5. 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.
  6. 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.

  1. Create a directory under Views called “Account”.
  2. 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.
  3. Create a DIV to contain the page content.
  4. In the DIV, start by doing things the old way with a using statement and Html.BeginForm.
  5. 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.
  6. 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.

  1. Create a controller under Controllers called “AccountController”.
  2. A default HttpGet Index method is created by default and is fine as-is.
  3. Create an overloaded Index method that requires HttpPost and accepts the Account model as a parameter. Clicking “Submit” will POST to here.
  4. 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.
  5. 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 

Image 1 

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.

  1. In Index.cshtml, change the Html.BeginForm to Ajax.BeginForm.  
  2. 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. 
  3. 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. 
    1. HttpMethod = “POST”
    2. UpdateTargetId = “ParentDiv”
  4. 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.
  5. 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.
  6. 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; 
  7. And finally the secret sauce.  In the _Layout.cshtml, right after the jQuery library, add a script tag for jquery.unobtrusive-ajax.min.js.
  8. 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 

Image 2

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United States United States
Architect, designer and coder since the late 90's and still going strong. Cut my teeth on Perl and C++, but soon moved to VB6/ASP and then onto .NET bliss and here I remain (until something better comes along). Love talking and thinking about coding practices and techniques, so feel free to shoot me a line. Lately been getting stronger at SOA and WCF and messing around with jqGrid.

Comments and Discussions

 
PraiseNice article, thank you Pin
Member 815630117-Jul-17 23:46
Member 815630117-Jul-17 23:46 
QuestionUnobtrusive AJAX post not working with EditorTemplates Pin
Sormita20-May-15 20:40
Sormita20-May-15 20:40 
Question"Specified argument was out of the range of valid values" Error when running Pin
godfreytoby@gmail.com11-Jun-13 11:08
godfreytoby@gmail.com11-Jun-13 11:08 
GeneralMy vote of 5 Pin
Member 920417511-Jun-13 7:53
Member 920417511-Jun-13 7:53 
QuestionDynamically growing webpage Pin
Roshan Jha21-Feb-13 9:19
Roshan Jha21-Feb-13 9:19 
QuestionThis was very good Pin
kaz.net25-Dec-12 20:09
kaz.net25-Dec-12 20:09 
Thanks a lot. I've never included unobtrusive in my projects, for lack of understanding.
GeneralMy vote of 5 Pin
mattohare23-Nov-12 5:16
mattohare23-Nov-12 5:16 
QuestionCan't find a download link for your Visual Studio solution file Pin
MarkEdward31-Oct-12 8:11
MarkEdward31-Oct-12 8:11 
AnswerRe: Can't find a download link for your Visual Studio solution file Pin
Todd Sprang15-Nov-12 1:22
Todd Sprang15-Nov-12 1:22 
GeneralMy vote of 5 Pin
Kanasz Robert19-Sep-12 4:48
professionalKanasz Robert19-Sep-12 4:48 
QuestionNice Pin
Ryan Criddle17-Sep-12 12:31
Ryan Criddle17-Sep-12 12:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.