I spent some time this weekend learning about the new Unobtrusive Javascript Validation mechanism that ships with ASP.NET MVC 3 and, after a bit of hair pulling, got it to work properly with custom validators. This post will summarize the steps necesssary.
- Include the correct JavaScript files
- Create your custom validator
- Tie the custom validator to the Model
- Set up validation on the field in your View
- Intercept the form post to force validation
- Create your custom client validation scripts
Overview
The point of unobtrusive Javascript validation is to seamlessly apply validation on form submit events, AND allow you to combine both server-side and client-side validation. Client-side validation alone is almost worthless, because if you turn off Javascript in your browser you can bypass the validation scripts. Server-side validation is (more) secure, but typically requires a round-trip and leaves some nice UX options off the table.
So, you need both. In this example, I’ll be showing a custom Email validator that runs on the server side and has a client-side counterpart. (I know that there is already a built-in “email” validator available, but this is just for illustration).
1. Include the correct Javascript files. I have the following in my master layout view:
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript">
To tell the truth, I’m not yet sure what the modernizr Javascript file is for – it may not be necessary.
2. Create your custom validator
In order for your custom validator to be able to function both on the server side and client side, you need to inherit from bothIValidationAttribute and IClientValidatable. Here’s my class, which lives in my MVC 3 web project (NOT a library project):
public class ValidEmailAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
var isValid = false;
string email = Convert.ToString(value);
if ((String.IsNullOrWhiteSpace(email))
|| (email.Contains("@")))
{
isValid = true;
}
return isValid;
}
public override string FormatErrorMessage(string name)
{
return "The " + name + " field contains an invalid email address.";
}
public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "validemail"
};
}
}
3. Tie your custom validator to your model This step is easy. All you do is decorate the property you want to validate with an attribute of type ValidEmailAttribute, as follows:
[ValidEmail(ErrorMessage="Please enter a valid email address.")]
[DisplayName("Email")]
public string Email { get; set; }
4. Set up validation on the field in your View This step involves using the HtmlHelper .ValidationMessageFor() to show the validation message. You could also use the ValidationSummary helper, and I’m not yet clear on my preferred UX for validation errors. To each her own.
@Html.TextBoxFor(m=>m.Email) @Html.ValidationMessageFor(m=>m.Email)
5. Intercept the form post to force validation This one was a little tricky. I wanted to do an AJAX post to a controller method, and MVC 3 provides an Ajax.BeginForm() method that appears to do what I want, but I’m a little old school and wanted to separate out the plain-old HTML
