ASP.NET MVC – A Declarative Model Validation Technique

18 Sep

I have been doing a lot of work lately with ASP.NET MVC and found that many of the MVC examples are set up where the server that the web application runs has direct access to a database and use LINQ to SQL style validation. See Nerd Dinner example to see how the Microsoft guys validate.

My application cannot use the Nerd Dinner example because my front end IIS server does not have direct access to a database (Or in my case CICS mainframe!). So I needed a simple declarative way of validating my data before I sent it off to a web service to be inserted into the mainframe.

So, using attributes, reflection and a little help from a colleague of mine, I came up with this solution. The main algorithm in the solution is an extension method that attaches itself to the FormCollection class used by MVC. The method iterates through each method attribute attached to the properties of a given class. Inside each attribute is a validation technique that is applied to the data stored in the FormCollection, if the validation fails a model error is added to the model state.

public static void Validate(this FormCollection fc, Type t, ModelStateDictionary msd)
{
    foreach (PropertyInfo pi in t.GetProperties())
    {
        foreach (ValidatorAttribute attr in pi.GetCustomAttributes(typeof (ValidatorAttribute), true))
        {
            msd.SetModelValue(attr.Name, fc.GetValue(attr.Name));
            //ignore attributes that are not in the value list
            bool attrInValueList = (from key in fc.Keys.Cast<string>()
                                    where key.Equals(attr.Name)
                                    select key).Count() > 0;
            if (attrInValueList && !attr.Validate(fc))
                msd.AddModelError(attr.Name, attr.ErrorMessage);
        }
    }
}

As mentoned, the validation algorithm uses attributes attached to the properties of a model. The validation attributes are simple static classes that are initialised with the name of the property, an error message to display and any extra parameters that are necessary. Below is an example of regular expression and date validation attributes.

public abstract class ValidatorAttribute : Attribute
{
    protected ValidatorAttribute(string name, string errorMessage)
    {
        Name = name;
        ErrorMessage = errorMessage;
    }
    public string Name { get; set; }
    public string ErrorMessage { get; set; }
    public abstract bool Validate(FormCollection fc);
}
public class RegexValidatorAttribute : ValidatorAttribute
{
    public RegexValidatorAttribute(string name, string regex, string errorMessage)
        : base(name, errorMessage)
    {
        Regex = regex;
    }
    public string Regex { get; set; }
    public override bool Validate(FormCollection fc)
    {
        if (fc[Name] == null)
            return false;

        return new Regex(Regex).Match(fc[Name].Trim()).Success;
    }
}
public class DateValidatorAttribute : ValidatorAttribute
{
    public DateValidatorAttribute(string name, string errorMessage)
        : base(name, errorMessage) { }
    public override bool Validate(FormCollection fc)
    {
        DateTime d;
        return DateTime.TryParse(fc[Name], out d);
    }
}

Now all I did was apply these validation attributes to the properties of my model that I wanted to validate. A big note here is that the name passed as a parameter to the validation attribute must match the name of the property to be validated.

public class Client
{
    [RegexValidator("FirstName", @"^([a-zA-Z0-9\s\-]{0,12})$", "Please enter a valid first name.")]
    public string FirstName { get; set; }

    [RegexValidator("FamilyName", @"^([a-zA-Z0-9\s\-]{0,18})$", "Please enter a valid family name.")]
    public string FamilyName { get; set; }

    [DateValidator("DateOfBirth", "Please enter a valid date of birth.")]
    public string DateOfBirth { get; set; }
}

Finally, to validate your form collection all you need to do is the following. Again the keys in the form collection must match the properties of the model that is being validated.

collection.Validate(typeof(Client), ViewData.ModelState);
if (ViewData.ModelState.IsValid)
    UpdateModel(client, collection.ToValueProvider());

That’s it! Now I can clearly see in one location, the model, what the validation requirements of each property are and easily adjust them if need be. The same validation algorithm and validation attributes can be used for any model!

 
 

Tags: , , ,

  1. Sowjanya

    February 10, 2015 at 6:28 pm

    Hi,

    I am run into a situation where the Model is defined in a webservice. I am not able to get the attributes across to the controller to validate. Any help provided would be greatly appreciated.

     
    • Mikey

      February 26, 2015 at 3:56 am

      That is a difficult one because the model will be serialized by the web service and rebuilt on the controller side. I assume you are using Visual Studio import the web service that will in turn create its own version of the web service’s entity class and strips out the validators.

      One method I have used in the past is to have two separately named entity classes that inherit from a central entity class. The parent entity class can be defined within its own separate project and included within the controller project and the web service project.

      Then within the two projects you can inherit the parent entity class in two differently named sub classes. That way you can validate the entity model with the controller before converting it to the web service class and sending it to the web service.

      To be more secure you can abstract out the controller validation process into a single package too and verify the data again on the web server side using the same code.

      I hope this helps.

      Cheers,
      Mikey