Validator providers in ASP.NET MVC

Recently I have given a speech on MVC3 validations where we covered a quite graphic example on the framework’s validation providers. Specifically, we implemented a provider capable of obtaining the annotations staring from the restrictions defined in the web.config. In other words, we don’t define the verification rules such as Required or StringLength at code level through attributes, but in the configuration file: this grants interesting advantages when it comes to making our solutions more flexible.

In this post we are going to see a new example on how to use the same provider mechanism, but this time to achieve in a very simple manner simplifying the code of model classes in which all it’s properties are by default mandatory, except those in which we indicate explicitly otherwise.

1. Starting from the end…

What we want to do is create two new attributes, AllRequired and NotRequired. The first, applied to a class will indicate the compulsory nature of all its properties, while the second, applied to a property, will indicate that it is optional.

Notice in the following table the effect we could achieve on the code:

[wpcol_1half id="" class="" style=""]

public class Friend
{
  [Required]
  public string Name { get; set; }
  [Required]
  public string Surname { get; set; }
  [Required]
  public string Nickname { get; set; }
  [Required]
  public string Address { get; set; }
  [Required]
  public string City { get; set; }
  [Required]
  public string Country { get; set; }
  public string Phone { get; set; }
  [Required]
  public string Mobile { get; set; }
  [Required]
  public string Email { get; set; }
}

[/wpcol_1half]
[wpcol_1half_end id="" class="" style=""]

[AllRequired]
public class Friend
{
  public string Name { get; set; }
  public string Surname { get; set; }
  public string Nickname { get; set; }
  public string Address { get; set; }
  public string City { get; set; }
  public string Country { get; set; }
  [NotRequired]
  public string Phone { get; set; }
  public string Mobile { get; set; }
  public string Email { get; set; }
}

[/wpcol_1half_end]

2. Model validator providers

When the framework needs to obtain the validators associated to a class and its properties, it doesn’t do it directly. It does it through the components named model validator providers. These are the ones in charge of accessing the different sources in which information on validation rules can be found for each class.

Therefore, there is a specific “default” model validator provider to obtain the validators staring from the annotations performed on the class (DataAnnotationsModelValidatorProvider), which is the method used normally; there is another one to obtain the implementations of the IDataErrorInfo interface (DataErrorInfoModelValidatorProvider), and yet another one in charge of implicit validations such as (ClientDataTypeModelValidatorProvider).

All of the are found registered in the ModelValidatorProviders.Providers public collection, in a way that when the framework needs to obtain the validators associated to an entity, what it does is go through such collection and calls the providers one by one requesting them validation information for each one of its properties.

This process is performed when the validation in the client is active, to obtain rules and parameters that have to be taken to the View, or also when binding, to obtain the validators, run them and update the ModelState with the result.

However, without a doubt, the best thing about this system is how easy it is to extend it. We can very easily add new providers to the ModelValidatorProviders collection which I reffered to above, and this way we can enter our own validator generation logic in the framework’s request execution flux.

And that’s what we are going to do next: we are going to implement our provider to generate automatically Required type validations for all the classes decorated with the [AllRequired] attribute, except those properties in which we have indicated the opposite [NotRequired].

3. The AllRequired and NotRequired attributes

Before anything, we have to define these two attributes in order to be able to continue. The code is as simple as displayed below:

[AttributeUsage(AttributeTargets.Class)]
public class AllRequiredAttribute : Attribute
{

}

[AttributeUsage(AttributeTargets.Property)]
public class NotRequiredAttribute: Attribute
{

}

Notice that it is not necessary that these inherit from ValidationAttribute; in fact, we are not creating custom annotations, only attributes which allow us to “mark” in classes and properties to generate later, from the provider, the needed annotations.

The only thing [AttributeUsage] does is restrict its use so that [AllRequired] can be used only on classes, while  [NotRequired] is only usable in properties, specially so these aspects are verified in compilation.

4. Implementing our model validator provider

The implementation of a model validator provider is quite simple. The only thing we have to do is inherit from ModelValidatorProvider and implement its GetValidators() method, which will invoke the framework in order to obtain the validators from each one of the properties of the class.

In our case, we will simply check that the class is checked as [AllRequired], returning a Required type validator for all those properties that haven’t been checked with [NotRequired]. Maybe it’s better to see it with code:

public class AllRequiredValidatorProvider : ModelValidatorProvider
{
  public override IEnumerable<ModelValidator> GetValidators(
      ModelMetadata metadata, ControllerContext context)
  {
      var allRequiredAttribute =
              TypeDescriptor.GetAttributes(metadata.ContainerType)
              .OfType().FirstOrDefault();

      if (allRequiredAttribute != null)
      {
        var property = metadata.ContainerType.GetProperty(metadata.PropertyName);
        var isOptional = property.GetCustomAttributes(
                                    typeof(NotRequiredAttribute), 
                                    false
                                 )
                                 .Any();
        if (!isOptional)
        {
          var requiredValidator = new RequiredAttribute() 
                                      { ErrorMessage = "Required" };
          yield return new RequiredAttributeAdapter(
                                metadata, 
                                context, 
                                requiredValidator
                            );
        }
      }
  }
}

Notice the signature of the method: it returns a collection of objects that inherit from ModelValidator, which are adapters of the different validation mechanisms we can use in MVC. In practice, they enable us to make the different view validators homogeneous, and sometimes include some logic, like the generation of validation rules in the client.

The framework includes already several adapters for each one of the habitual annotations (RequiredAttributeAdapter, StringLengthAdapter, RegularExpressionAttributeAdapter …), which we can instantiate directly in order to build the collection which returns the GetValidators() method. In the previous code, notice how in the last line we return, using yield, a new RequiredAttributeAdapter type adapter, whose builder we provide an instance of the Required attribute conveniently initialized.

One last detail: bear in mind that because I have simplified the example haven’t paid attention to things such as the performance, which could be improved by entering a cache, or customizing error messages, which could be easily implemented enabling parameters in the [AllRequired] attribute and providing them further on in the instantiation of the RequiredAttribute which we return form the provider, like we do now with the “Required” constant text string.

5. Provider registration

At last, all we have to do is indicate the framework that it has to use our provider in order to obtain the validators. Therefore, we add it in global.asax to the provider collection:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    ModelValidatorProviders.Providers.Add(new AllRequiredValidatorProvider());

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

And there we have it! From this moment onward, we can make use of these new attributes in our model classes or view models, as shown below:

[AllRequired]
public class Friend
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Nickname { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    [NotRequired]
    public string Phone { get; set; }
    public string Mobile { get; set; }
    public string Email { get; set; }
}

In runtime we have, of course, validation both in client and server; in fact, the result will be identical to the one obtained if we had decorated each one of the previous properties with the Required attribute. This is so because what we are really doing is injecting it in all the properties automatically, except in those were the opposite has been stated explicitly.

Jose M. Aguilar

ASP.NET/IIS MVP

Jose M. is a well-known world expert in web technologies. He is the author of Microsoft Press SignalR Programming in Microsoft ASP.NET. He works as an independent consultant and developer, helping companies and institutions to reach their goals by using software. He also works with company development teams providing consultancy services and support in several fields. Follow Jose M on Twitter.

No comments yet.

Leave a Reply