Unobtrusive Client and Server Side Age Validation in MVC using Custom Data Annotations

By | February 24, 2014

One of the best things with MVC is the built-in validation using Data Annotations, these special attributes are simply applied to a class or its property and validation simply just happens on both Client and Server side (taking into consideration you set yours up properly). There are lots of Data Annotations built in that you can use and the most popular ones are the Required, Range and Data Type Attributes but sometimes these are not enough so we need something more usable that fits well with our business logic. Good thing we can create our own custom validation by overriding methods in the Validation Attribute class as well as with IClientValidatable.

So how do we do that? Lets start with an example, lets say you want to validate a users age by default you cannot use the built-in validators so you will have to create your own validation. Lets say you have a registration form like this screenshot below and you want to restrict sign ups for people only aged between 15 to 65 years old.

01 Form

First lets create your own custom validation, let us call it ValidateAgeAttribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ValidateAgeAttribute : ValidationAttributeIClientValidatable
{
    private const string DefaultErrorMessage = "Your age is invalid, your {0} should fall between {1} and {2}";

    public DateTime MinimumDateProperty { getprivate set; }
    public DateTime MaximumDateProperty { getprivate set; }

    public ValidateAgeAttribute(
        int minimumAgeProperty,
        int maximumAgeProperty)
        : base(DefaultErrorMessage)
    {
        MaximumDateProperty = DateTime.Now.AddYears(minimumAgeProperty * -1);
        MinimumDateProperty = DateTime.Now.AddYears(maximumAgeProperty * -1);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            DateTime parsedValue = (DateTime)value;

            if (parsedValue <= MinimumDateProperty || parsedValue >= MaximumDateProperty)
            {
                return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            }
        }
        return ValidationResult.Success;

    }
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ValidationType = "validateage",
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
        };

        rule.ValidationParameters.Add("minumumdate", MinimumDateProperty.ToShortDateString());
        rule.ValidationParameters.Add("maximumdate", MaximumDateProperty.ToShortDateString());

        yield return rule;
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(ErrorMessageString, name, MinimumDateProperty.ToShortDateString(), MaximumDateProperty.ToShortDateString());
    }
}

In this class you notice you have three methods, first is IsValid.   We override this method from the ValidationAttribute and this basically validates the specified value with respect to the current validation attribute, it outputs an instance of the System.ComponentModel.DataAnnotations.ValidationResult class.  The next method is FormatErrorMessage which is from the same ValidationAttribute overriden class, as the name suggests this gets or sets an error message to associate with a validation control if validation.  Now we have GetClientValidationRules which come from IClientValidatable where it provides a way for the ASP.NET MVC validation framework to discover at run time whether a validator has support for client validation.

Now let use this in your view model

Lets say your Birth day field is called Birthdate, we want it to be required and we want it to display as “Date of Birth”.

[DisplayName("Date of Birth")]
[Required]
[ValidateAge(15, 65)]
public DateTime? BirthDate { getset; }

Now if  you notice you have 2 parameters in Validate Age 15 and 65, this is the property you set on minimumAgeProperty and maximumAgeProperty of your ValidateAgeAttribute class.  You will also notice we use ValidateAge and not ValidateAgeAttribute, you can use both as it auto maps with the class.

At this point your server-side validation will now work all you need to do is before doing anything check the ModelState in your controller like this

[AcceptVerbs(HttpVerbs.Post)]
[Transaction]
public ActionResult CreateAccount(RegisterPageViewModel viewModel)
{
    var errorMessage = string.Empty;

    if (ModelState.IsValid)
    {
        var newExtendedUserDetails = activeDirectoryTasks.CreateUser(
            viewModel.FirstName,
            viewModel.LastName,
            viewModel.Email,
            viewModel.BirthDate.Value,
            viewModel.SecretQuestion1Id,
            viewModel.SecretQuestionAnswer1,
            viewModel.SecretQuestion2Id,
            viewModel.SecretQuestionAnswer2,
            out errorMessage
            );

        if (errorMessage == string.Empty)
        {
            //Success
            viewModel = registerQuery.GetSuccessPageViewModel(viewModel);
            return View("Success", viewModel);
        }
        //Error on Save
        viewModel = registerQuery.GetErrorPageViewModel(viewModel);
        return View("Error", viewModel);
    }
    //Validation Error
    viewModel = registerQuery.GetRegisterPageViewModel(viewModel);
    return View("Index", viewModel);
}

So if you turn off your JavaScript and run then it should throw a validation error like such.

02 Server Error

Now lets enable your Unobtrusive Validation,  first is you need to create a separate js file.  I called mine custom-validators.js and I include all my custom validators there.  Inside that fill would be something like this

// The validateage function
$.validator.addMethod(
    'validateage',
    function (value, element, params) {
        return Date.parse(value) >= Date.parse(params.minumumdate) && Date.parse(value) <= Date.parse(params.maximumdate);
    });

$.validator.unobtrusive.adapters.add(
    'validateage', ['minumumdate''maximumdate'], function (options) {
        var params = {
            minumumdate: options.params.minumumdate,
            maximumdate: options.params.maximumdate
        };
        options.rules['validateage'] = params;
        options.messages['validateage'] = options.message;
    });

First function just adds the method and the second adds the unobtrusive adapter.  Take note of the names as this is the one you declared early on your ModelClientValidationRule on the ValidateAgeAttribute.

Next is to make sure you reference to the following JavaScript in this specific order.

<script src="@Url.Content("~/Scripts/jquery-2.0.3.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/custom-validators.js")" type="text/javascript"></script>

And if you are using Bundle Collections then it will be like this

bundles.Add(new ScriptBundle("~/bundles/jquery")
    .Include("~/scripts/jquery-2.0.3.min.js")
    .Include("~/scripts/jquery.validate.min.js")
    .Include("~/scripts/jquery.validate.unobtrusive.min.js")
    .Include("~/scripts/custom-validators.js")
    );

and if you’re using Bundle Collection clear the default ordering as it will sort it alphabetically, you can do so by calling FileSetOrderList.Clear() after all the bundles have been created.

bundles.FileSetOrderList.Clear();

Dont also forget to put this on your web.config

<appSettings>
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />

Now lets see if everthing works fine, code behind should show your data-val’s created from your attribute

03 Validations Showing

And it should do its validation

04 Client Validation

Recommended

6 thoughts on “Unobtrusive Client and Server Side Age Validation in MVC using Custom Data Annotations

  1. craftbeertraders

    Im having an issue with the javascript validation. Ive added as described above but when you enter a date that is outside the range it does not show the error message until you post back to server. I was hoping that it would alert dynamically like the password or invalid email address does on registration

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.