ASP.NET MVC: Adding client-side validation to ValidatePasswordLengthAttribute

This is the first post in what has become a mini-series:

When you create a new ASP.NET MVC 2 project in Visual Studio there are a number of files that are created. One of these is AccountModel.cs. If we ignore the fact that this one file contains multiple classes (I’ve no idea why!), we will see that there is some nice example code lurking in there. One such piece of code is the ValidatePasswordLengthAttribute. This attribute can be applied to your model to enforce a minimum password length (based on the minimum specified by the currently configured MembershipProvider). The code below shows the attribute usage (taken from ChangePasswordModel):

         [Required]
        [ValidatePasswordLength]
        [DataType(DataType.Password)]
        [DisplayName("New password")]
        public string NewPassword { get; set; }

This lets us take advantage of the model binding and validation that is baked into ASP.NET MVC 2. One nice feature of the validation is that the built in validators make it very easy to enable client-side validation by adding the following line to your view:

 <% Html.EnableClientValidation(); %>

With this in place, ASP.NET MVC will emit the necessary javascript to wire up the client-side validators (you need to reference the script files from your view). Scott Guthrie has a good blog post that goes through the in-built validation in more depth – the remainder of this post will look at adding creating your own custom client-side validation.

If you enable client-side validation for the ChangePassword view (the snippet above) then you will find that the required field validation is triggered in the browser but the minimum length validation only happens on a postback. This is because the in-built Required validator has client-side support, but the ValidatePasswordLength supplied as part of the template doesn’t.

So, how do you go about adding client-side validation? That’s what the rest of this post will cover.

The ValidatePasswordLengthAttribute derives from ValidationAttribute which is the base class for validation attributes from System.ComponentModel.DataAnnotations. ASP.NET MVC 2 has built in support for Data Annotations but to get client-side support we need some extra elements:

  1. The client-side validation code!
  2. ModelClientValidationRules that specify how to hook up to the client-side validation
  3. An adapter class that ASP.NET MVC uses to translate our validation attribute into ModelClientValidationRules and register this with the DataAnnotationsModelValidationProvider

So, let’s write the client-side code:

 Sys.Mvc.ValidatorRegistry.validators.passwordLength = function (rule) {
    var minCharacters = rule.ValidationParameters.minCharacters;
    var message = rule.ErrorMessage;
 
    return function (value, context) {
        if (!value || !value.length) {
            return true; // return valid if value not specified - leave that to the 'required' validator
        }
 
        if (value.length < minCharacters) {
            return false; // value too short - return invalid
        }
        return true;
    };
}; 

This code registers a factory function with the ValidatorRegistry. The factory takes a rule definition and returns a function that performs validation for that rule. Notice that we access rule.ErrorMessage and rule.ValidationParameters – these are passed to ASP.NET MVC by our

     public class ModelClientPasswordLengthValidationRule : ModelClientValidationRule
    {
        public ModelClientPasswordLengthValidationRule(string errorMessage, int minCharacters)
        {
            ErrorMessage = errorMessage;
 
            ValidationType = "passwordLength";
 
            ValidationParameters.Add("minCharacters", minCharacters);
        }
    }

In this class we are simply taking the error message and minimum number of characters and passing them to the base class properties. The error message is a standard property so we can assign that, but the minimum number of characters is a custom property so we add it to a dictionary. Notice that the key for the ValidationParameters dictionary (“minCharacters”) is the key we use to access the value client-side: rule.ValidationParameters.minCharacters.

Also, notice that we also specify ValidationType = “passwordLength”. This tells ASP.NET MVC which client-side validator to use and must match the client-side registration: Sys.Mvc.ValidatorRegistry.validators.passwordLength = ...

So far, so good! Next we need to provide an adapter class to hook up the PasswordLengthValidationAttribute to the ModelClientPasswordLengthValidationRule:

     public class DataAnnotationsPasswordLengthValidator : DataAnnotationsModelValidator<ValidatePasswordLengthAttribute>
    {
        public DataAnnotationsPasswordLengthValidator(ModelMetadata metadata, ControllerContext context, ValidatePasswordLengthAttribute attribute) : base(metadata, context, attribute)
        {
        }
        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            yield return new ModelClientPasswordLengthValidationRule(ErrorMessage, Attribute.MinCharacters);
        }
    }

This class is quite simple – the main point of note is that we are accessing the Attribute property which is strongly typed and then passing these values into our ModelClientPasswordLengthValidationRule constructor.

The final step is to register the adapter. Add the following line to Application_Start in global.asax.cs:

     DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidatePasswordLengthAttribute), typeof(DataAnnotationsPasswordLengthValidator));

This simply notifies the DataAnnotationsModelValidationProvider about our adapter and tells it to use it to get the client rules for the ValidatePasswordLengthAttribute.

And that’s it!

Providing we have added the standard Mvc validation scripts and our custom script, we can simply add <% Html.EnableClientValidation(); %> to our Register.aspx and bask in the glory of client-side validation that the password is sufficiently long :-)

Comments

  • Anonymous
    January 05, 2011
    Went through your steps and created a validation in MVC3 but the client side validation is not invoked. Pls suggest ..

  • Anonymous
    January 06, 2011
    Resolved .. was missing the IClientValidtable implementation.

  • Anonymous
    February 29, 2012
    There is no IClientValidatable in MVC2, thats an mvc3 feature.