Extending EntLib VAB - Part 1: Enable client side support EntLib VAB and align the validation error keys with MVC ModelState keys


Note: Thank you for visiting my blog. However, this blog is not maintained actively anymore. This post is now also in my new blog. Feel free to leave a comment there.

As discussed in my last post EntLib VAB doesn't support client side validation (in js/jQuery) out-of-the-box. And also while validating object graph, EntLib VAB generate Validation Result keys which are different from the ModelState keys in MVC.

In this post we will discuss on this problem.

Solution to this problem is simple. We just need to create an adapter for EntLib VAB validator to MVC ValidationAttribute, i.e. a piece of code is needed that will read the EntLib Validation configuration xml and for each validator it will return the MVC counterpart of the validator, i.e. ValidationAttribute.

As for aligning the validation error keys with MVC ModelState keys, we need to leverage the default model validation of ASP.NET MVC.

And the great news is that one single piece of code addresses combines these two solutions.


So what is the story behind the scene? 
Check the below process flow:

  1. On receiving request from client, default MVC Model Binder tries to bind model from various value providers. After the model binding is done, it raises Model Updated event.
  2. Model Updated event handler in turn calls ModelValidator.GetModelValidator().Validate() method.
  3. Inside Validate() method, ModelMetaData.GetValidators() is invoked which in turn invokes ModelValidatorProviders.Providers.GetValidators(). 
  4. ModelValidatorProviders holds a collection of three built-in validators [DataAnnotationsModelValidatorProvider(), DataErrorInfoModelValidatorProvider(), and ClientDataTypeModelValidatorProvider()], out of which DataAnnotationsModelValidatorProvider is our guy. It is this class, which provides default model validation for MVC.


So long story short, all we have to do is to override DataAnnotationsModelValidatorProvider class, which will hold our adapter logic inside it and after that to register our own Validator Provider to ModelValidatorProviders.Providers.

If we do this far, we have fairly done the job of addressing the two issues as said above: enabling client side validation and getting appropriate validation error keys (Keys are taken care of at the time of model binding process itself. Hence as we are just hooking in our custom implementation in the default processing of model binding, we don't need to do anything additional on top of this.). Note that in this process, we also do not need to make explicit call to the Validator.Validate() method which people usually do with EntLib VAB to make server side validation as the DefaultModelBinder does that by invoking all the registered Validator Providers.

Cool Right?

Ok enough of theory… Lets see some code now, because a piece of code says a lot more than thousand words... ;)

Disclaimer: I personally like to create the validation config xml file in a separate file other than web.config/app.config as I find the same to be a lot more cleaner approach. Hence all the code example shown below consider the same. If you are more comfortable working with defining the same in app.config/web.config

  • So first we are creating our own custom Data Annotation provider inheriting from DataAnnotationsModelValidatorProvider (remember the guy who provides default model validation for MVC). It has a method GetValidators() which we need to override. Inside this function we are invoking a custom extension function that reads the EntLib VAB configuration xml file and retrieve the defined validators for the model. And then the custom adapter functions are invoked that translate EntLib VAB validators to MVC Validation attributes.
  • Below code sample shows how to do the translation for two of EntLib VAB validators(NotNullValidator and RegExValidator), but that will be enough to give you the idea of how to do the translation for other validators as well. It also shows how to work with composite EntLib VAB validators, sample includes example for OrCompositeValidator. Being a composite validator, OrCompositeValidator holds a collection of other validators; the trick is to extract those validators and make recursive calls to the adapter methods so that those validators gets translated to MVC validation attributes.
The custom ModelValidatorProvider:
   1: namespace Validation

   2: {

   3:     public class EntLibDataAnotationProvider : DataAnnotationsModelValidatorProvider

   4:     {

   5:         #region Fields

   6:         private static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory> 

   7:         {         

   8:             { 

   9:                 typeof(RequiredAttribute), 

  10:                 (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute) 

  11:             }, 

  12:             { 

  13:                 typeof(RegularExpressionAttribute), 

  14:                 (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)             

  15:             } 

  16:         };

  17:         #endregion

  18:  

  19:         protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)

  20:         {

  21:             List<ModelValidator> validators = nw List<ModelValidator>();

  22:             List<ValidatorData> validatorDataList = metadata.ContainerType != null ? metadata.ContainerType.ExtractRules(metadata.PropertyName) : new List<ValidatorData>();

  23:             return GetValidators(metadata, context, validatorDataList);

  24:         }

  25:  

  26:         #region Private Methods

  27:         private IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<ValidatorData> validatorData)

  28:         {

  29:             List<ModelValidator> validators = new List<ModelValidator>();

  30:             foreach (var item in validatorData)

  31:             {

  32:                 if (item.GetType().Name == "OrCompositeValidatorData")

  33:                 {

  34:                     validators.AddRange(GetValidators(metadata, context, ((OrCompositeValidatorData)item).Validators.Where(v => !((ValueValidatorData)v).Negated)));

  35:                 }

  36:                 else

  37:                     validators.Add(GetValidator(metadata, context, item));

  38:             }

  39:  

  40:             return validators.Where(item => item != null);

  41:         }

  42:         /// <summary>

  43:         /// The mapper method that translates EntLib validator to ASP.NET MVC Validation Attribute

  44:         /// </summary>

  45:         private ModelValidator GetValidator(ModelMetadata metadata, ControllerContext context, ValidatorData item)

  46:         {

  47:             DataAnnotationsModelValidationFactory factory;

  48:             if (item.GetType().Name == "NotNullValidatorData")//So for a EntLib NotNullValidator what we really need in MVC is a RequiredAttribute.

  49:             {

  50:                 ValidationAttribute attribute = new RequiredAttribute { ErrorMessage = item.MessageTemplate };

  51:                 if (AttributeFactories.TryGetValue(attribute.GetType(), out factory))

  52:                     return factory(metadata, context, attribute);

  53:             }

  54:             else if (item.GetType().Name == "RegexValidatorData")//Likewise for a EntLib RegExValidator we need RegularExpressionAttribute in MVC.

  55:  

  56:             {

  57:                 RegexValidatorData regexData = (RegexValidatorData)item;

  58:  

  59:                 RegularExpressionAttribute regexAttribute = new RegularExpressionAttribute(regexData.Pattern) { ErrorMessage = item.GetMessageTemplate() };

  60:  

  61:                 if (AttributeFactories.TryGetValue(regexAttribute.GetType(), out factory))

  62:                     return factory(metadata, context, regexAttribute);

  63:             }           

64: 

  65:             return null;

  66:         }

  67:         #endregion

  68:     }

  69: }
The extension method to extract defined validation settings:
   1: public static List<ValidatorData> ExtractRules(this Type targetType, string propertyName)

   2: {

   3:     FileConfigurationSource fileConfigurationsource = new FileConfigurationSource(validationFilePath);

   4:     ValidationSettings settings = (ValidationSettings)fileConfigurationsource.GetSection("validation");

   5:  

   6:     ValidatedTypeReference rules = settings.Types.FirstOrDefault(item => item.Name.Equals(targetType.FullName));

   7:  

   8:     return rules.Rulesets.SelectMany(item => item.Properties).Where(item => item.Name.Equals(propertyName)).SelectMany(item => item.Validators).ToList();

   9: }
Now the final step: register our custom ModelValidatorProvider to ModelValidatorProviders.Providers in Application_Start() in Global.asax:
   1: using Validation;

   2:  

   3: public class MvcApplication : System.Web.HttpApplication

   4: {

   5:     protected void Application_Start()

   6:     {

   7:         /*Usual code removed for the sake of Brevity*/

   8:  

   9:         ModelValidatorProviders.Providers.Add(new EntLibDataAnotationProvider());

  10:     }

  11: }

And now you are good to go. Try this on your own now.

Happy Coding.
Sayan

P.S: Don’t forget to add the jQuery unobtrusive validation libraries in the master layout (view). Also this is written based on my personal experience, if there is a better way to do it, please don't hesitate to leave your comments.

Edit: Next in this series: Extending EntLib VAB - Part 2.1: Create custom validators and integrate that with EntLib CABC

13 comments: (+add yours?)

Unknown said...

Hi, After following your instructions i am not able to get this to work, i receive the following error, "Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: required"

I tried using "DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;" to remove the default required validators unsuccessfully as described for a similar problem here http://stackoverflow.com/questions/9746186/validation-type-names-in-unobtrusive-client-validation-rules-must-be-unique

Could you assist, thanks

Unknown said...

Thanks Neil for reading my blog and leaving comments... :) Now I know at least someone reads my blog. :)

Now to answer your question, this kind of error occurs when a validation type is applied for multiple times. It may happen that you have already a required validator in place (may be in form of DataAnnotation attribute on top of your model property) and on top of that you are again applying a not-null validator using Entlib VAB for the same property. I would suggest to check on this line.

If that is not the case, is it possible to share your code, which will give more insight to the problem?

And anyway please don't hesitate to share your findings. :)

Thank You.

Unknown said...
This comment has been removed by the author.
Unknown said...

Hi Sayan,

You article is the only one of its type i could find on the net, even when stackoverflow questions maintain that VAB for clientside is not possible, so my hats off to you.

I understand the problem, i just don't know how to get MVC to stop adding the default RequiredValidator, my code to follow:

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(NumbersOnlyAttribute), typeof(RegularExpressionAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LettersOnlyAttribute), typeof(RegularExpressionAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(IPAddressAttribute), typeof(RegularExpressionAttributeAdapter));
//I have not converted these to VAB validators yet as described in your latter articles
ModelValidatorProviders.Providers.Add(new EntLibDataAnotationProvider());
}

Model:
[Display(Name = "AccountNumber", ResourceType = typeof(labels))]
public string AccountNumber { get; set; }

Validation.config:
validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.OrCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Or Composite Validator">
validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
negated="true" messageTemplate="Account Number is required"
name="Not Null Validator" />
validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
upperBound="30" messageTemplate="Account Number length should be between 0 and 30 "
name="String Length Validator" />


I have added all the code you have posted in this article.

Thanks!

Unknown said...

You validation config looks right. Though I am not sure where the exact problem is, I think if you add your custom DataAnnotationProvider as ModelValidatorProvider then, you don't need to register those adapters, and I am not completely sure about what you are trying to achieve there.

Here is a old copy of my work which I referred while writing this blog entry: http://goo.gl/HtgTQv. You can refer the same, if that helps.

Unknown said...

Hi Sayan,

I am implementing what you advised in your previous post, i did have some fields that still had regex and required DataAnnotations on them, this did break some stuff.

I did change your original code for negated in GetValidators() since OrCompositeValidatorData does not inherit from ValueValidatorData (MVC5 with VAB 5.505, i have moved the check to the else block.

It got me past most of the problems except now a Html.DropDownFor is complaining about the same required duplicate, it has a string backing for the SelectedProperty, so its not the default MVC required kicking in, i will post here if i am successful.

Thanks for all your help.

Unknown said...

Hi Neil,

Glad to hear that you have found a way.

Happy Coding!!

Anonymous said...

Man your articles about VAB is very very good, and nailed two nails in one blow, thank you a lot on this very good.

it would be more appreciated if you have the whole articles in one working sample project, for a full overview.

Thanks again on the good work and help for others.

Unknown said...

Thank You Reader Man for reading my blog.
I have created a GitHub repository VABEntLib6Playground for the complete source code.

Hope this helps.

Thank You.

Unknown said...

Hi Sayan,

I'm back to getting this to work again, and i have gotten most parts working, this includes rulesets, multiple configuration files, mixing normal and custom validation, with entlib, etc.

I have an issue with the GetValidators method, where on a POST, the ModelMetaData.Container is a default instance of my model and not the one passed from the browser. I require this due to a RequireIfDependency type of validation, i need to check the values of other properties to know if the current field must be validated by entlib.

This functions correctly on a normal GET, i am posting the entire model on the POST.
Any idea why?

Kind Regards
Neil Naidoo

Unknown said...

Hi Neil,
It is good to know that you have made all the parts work.

I am not completely sure about your question. Is it possible for you to share your code with me, as it will help me in analyzing the problem.

In case it is not possible for you to share the code, you might want to configure multiple rulesets for your class (to be validated) and while extracting the ruleset try to select appropriate ruleset instead of the default one.

Kind Regards,
Sayan

Unknown said...

Hi Sayan, thanks for the feedback, i did get it to work eventually. The problem i stated previously was because during a POST the getvalidators method is called multiple times for the same property, so eventually the method was called with a populated ModelMetaData.

The system now works with a mixture of entlib validators and builtin data annotations with rulesets.

Thanks for all your help, i could never have gotten this to work without your article and feedback.

Regards
Neil Naidoo

Unknown said...

Hi Neil,
Thanks for your kind words. Also it is good to know it helped you.

Happy Coding!!

Sayan

Post a Comment