How-To: Remote Validation in ASP.NET MVC3
ASP.NET MVC3 has been a major boon to my productivity as a web developer since I started using it at the beginning of November – the new Razor view engine has been attracting most of the attention with this iteration of MVC, but one extremely sexy feature has gone unnoticed thus far: Remote validation.
Remote validation was one of the new features added in the November Release Candidate (RC) for MVC3 – I had a chance to demo it in front of the LA .NET User Group last night and it was a monster hit.
Example:
You’ve all seen remote validation before – ever see one of these when you’re signing up for a service like Twitter?
That’s remote validation at work. Twitter, Facebook et al make an AJAX call to a remote service hook that checks the database to see if the username is available as the user types it. It’s a major user experience improvement as they get that feedback instantly instead of having to wait until after they fill out the rest of the form and submit it.
In the past with ASP.NET MVC you had to write your own custom jQuery scripts on top of the jQuery validation engine to achieve this end-result; in ASP.NET MVC3 this is taken care of for you automatically!
Let me show you how it works:
1. Decorate a model class with the Remote attribute
Take the class you want to validate and decorate the attributes you need to remotely validate with the Remote attribute.
public class NewUser { [Remote("UserNameExists", "Account", "Username is already taken.")] public string UserName { get; set; } [Remote("EmailExists", "Account", "An account with this email address already exists.")] public string EmailAddress { get; set; } public string Password { get; set; } }
In both of these instances of the Remote attribute, I’ve passed the following arguments:
- The name of an action method;
- The name of the controller where the action method lives; and
- A default error message should user input fail this validation challenge.
2. Implement an action method to support your Remote attribute
You will need to implement an action method that supports your Remote attribute. Add an action method which returns a JsonResult to the controller you named in your Remote attribute arguments. In this example I’ll need to add these action methods to the “Account” controller:
public JsonResult UserNameExists(string username) { var user = _repository.GetUserByName(username.Trim()); return user == null ? Json(true, JsonRequestBehavior.AllowGet) : Json(string.Format("{0} is not available.", username), JsonRequestBehavior.AllowGet); } public JsonResult EmailExists(string emailaddress) { var user = _repository.GetUserByEmail(emailaddress.Trim()); return user == null ? Json(true, JsonRequestBehavior.AllowGet) : Json( string.Format("an account for address {0} already exists.", emailaddress), JsonRequestBehavior.AllowGet); }
If the repository returns null, meaning that a user account with this particular user name or email address doesn’t already exist, then we simply return a JsonResult with a value of true and go off on our merry way. This will suppress the jQuery validation library from raising a validation error.
If the action method returns false, then jQuery will raise a validation error and display the default error message provided in the Remote attribute arguments – if you didn’t provide a default error message yourself then the system will use an ambiguous default one.
If the action method returns a string, jQuery will raise a validation error and display the contents of the string, which is what I’m doing here in this example.
Small Gotcha – Naming Action Method Parameters
There’s one small gotcha that can be easy to miss – the name of the argument on your action method must match the name of your property on your model. If I changed the EmailExists body to look like this:
public JsonResult EmailExists(string email);
Then ASP.NET would pass a null value to this method.
Case 1: parameter name matches the name of the model’s property
Case 2: parameter name does not match the name of the model’s property
This is because the jQuery parameter takes the name of the property on the model and passes it as a querystring argument to your action method – here’s what your validation request looks like in Firebug:
[host]/account/emailexists?area=an%20account%20with%20this%20email%20address%20already%20exists.&EmailAddress=test%40test.com
The ASP.NET MVC model-binder isn’t all-knowing – it’s not going to be able to tell that EmailAddress and email are the same thing, thus it ultimately won’t bind an argument to your action method, hence why the null value is passed.
If you follow the convention of using a common name for your Remote validator action method arguments and your model properties, you won’t run into this issue.
UPDATE: Thanks to Rick Anderson for directing me to the much more extensive MSDN documentation on how to implement custom Remote validaiton in ASP.NET MVC3.