NHibernate Client Validator ASP.NET MVC 2 Model Validation

ASP.NET MVC 2 improves Model Validation in a number of ways, including the addition of client side validation (ala xVal).  If you would like more information on Model Validation in ASP.NET MVC 2, see Scott Gu’s detailed post on this subject.  Out of the box ASP.NET MVC 2 includes support for DataAnnotations, and there are some extensibility points available for plugging in your own framework.

In post series I am creating my own implementations of Model Validation using NHibernate Validator (aka NHV, part of NHibernate Contrib).  This post will focus on the client-side model validation – you can find the previous post covering server-side validation here.

Note:  If you just want to see the final code, including a sample project, go to http://github.com/srkirkland/NHValModelValidators.

The Setup – Validating Customers

Below is the simple Customer class which we will use for validation.

public class Customer : BaseObject
{
    [NotNullNotEmpty]
    [Length(10)]
    public virtual string CompanyName { get; set; }
 
    [NotNullNotEmpty(Message = "Don't forget the contact name")]
    public virtual string ContactName { get; set; }
 
    [NotNullNotEmpty]
    public virtual string Country { get; set; }
 
    [NotNullNotEmpty]
    [Pattern("^[0-9]{6}")]
    public virtual string Fax { get; set; }
 
    [Range(1, 200)]
    public virtual int Age { get; set; }
}

So we are using some NotNullNotEmpty validators, a String Length validator, a Range and a Regular Expression Pattern validator for testing.

More Setup – Getting the Validator Engine

Eventually we will need to get the validation engine in order to interact with the NHibernate Validator library, and everyone probably has a different way of getting their configured engine.  For these examples (and simplicity’s sake) I’m going to use the following engine factory:

public class ValidatorEngineFactory
{
    public static ValidatorEngine ValidatorEngine
    {
        get
        {
            if (NHibernate.Validator.Cfg.Environment.SharedEngineProvider == null)
            {
                NHibernate.Validator.Cfg.Environment.SharedEngineProvider = new NHibernateSharedEngineProvider();
            }
 
            return NHibernate.Validator.Cfg.Environment.SharedEngineProvider.GetEngine();
        }
    }
}

 

Setting up the ModelValidatorProvider

The first step towards hooking up Model Validation is to create a class which inherits from System.Web.Mvc.ModelValidatorProvider.  For added convenience and utility I am going to inherit from  System.Web.Mvc.AssociatedValidatorProvider, which is a helper class provided by MVC to find associated information from your validatable fields (in our case, we want the associated validation attributes).  This class has one method you need to override, which is: 

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

What we want to do with this provider is to return a list of ModelValidators that know which ModelClientValidationRule instances are associated with the given property.  Hopefully this will all become clear over the reminder of this article.

Since we are doing client-side validation and want to utilize the exiting ASP.NET MVC2 client-size validation rules then we need a way to map the NHibernate Validator constraints to MVC2 System.Web.Mvc.ModelClientValidationRule instances.  First we will create a class to help us with the mapping:

public class RuleEmitterList<TInputBase>
{
    public delegate IEnumerable<ModelClientValidationRule> RuleEmitter(TInputBase item);
 
    private readonly List<RuleEmitter> _ruleEmitters = new List<RuleEmitter>();
 
    public void AddSingle<TSource>(Func<TSource, ModelClientValidationRule> emitter) where TSource : TInputBase
    {
        _ruleEmitters.Add(x =>
                              {
 
                                  if (x is TSource)
                                  {
                                      ModelClientValidationRule rule = emitter((TSource)x);
                                      return rule == null ? null : new[] { rule };
                                  }
                                  else
                                  {
                                      return null;
                                  }
                              });
    }
 
    public IEnumerable<ModelClientValidationRule> EmitRules(TInputBase item)
    {
        foreach (var emitter in _ruleEmitters)
        {
            var emitterResult = emitter(item);
            if (emitterResult != null)
            {
                return emitterResult;
            }
        }
 
        return new ModelClientValidationRule[] { }; //No matching emitter, so return an empty set of rules
    }
}

This RuleEmitterList will take a type and “convert” that type into ModelClientValidationRules.  This technique is similar to how xVal solved a related problem.  You will see how this is used in just a minute.

In the constructor (which is run only once) we do the mapping:

public class NHibernateValidatorClientProvider : AssociatedValidatorProvider
{
    private readonly RuleEmitterList<IRuleArgs> _ruleEmitters;
 
    /// <summary>
    /// ctor: Hook up the mappings between your attributes and model client validation rules
    /// </summary>
    public NHibernateValidatorClientProvider()
    {
        _ruleEmitters = new RuleEmitterList<IRuleArgs>();
 
        _ruleEmitters.AddSingle<NotNullNotEmptyAttribute>(x => new ModelClientValidationRequiredRule(x.Message));
        _ruleEmitters.AddSingle<NotEmptyAttribute>(x => new ModelClientValidationRequiredRule(x.Message));
        _ruleEmitters.AddSingle<NotNullAttribute>(x => new ModelClientValidationRequiredRule(x.Message));
 
        _ruleEmitters.AddSingle<LengthAttribute>(
            x => new ModelClientValidationStringLengthRule(x.Message, x.Min, x.Max));
 
        _ruleEmitters.AddSingle<MinAttribute>(x => new ModelClientValidationRangeRule(x.Message, x.Value, null));
        _ruleEmitters.AddSingle<MaxAttribute>(x => new ModelClientValidationRangeRule(x.Message, null, x.Value));
 
        _ruleEmitters.AddSingle<NHibernate.Validator.Constraints.RangeAttribute>(
            x => new ModelClientValidationRangeRule(x.Message, x.Min, x.Max));
        
        _ruleEmitters.AddSingle<PatternAttribute>(x => new ModelClientValidationRegexRule(x.Message, x.Regex));
    }

So here we decided to map IRuleArgs (an NHibernate Validator interface) to their related MVC2 ModelClientValidator*Rule instances.  NonNullNotEmptyAttribute, for instance, is really a RequiredRule. Getting slightly more complex, a MaxAttribute is really a MVC2 RangeRule with the min value set to null.  All NHibernate Validator attributes that are not mapped will be ignored.

 

Implementing the ModelValidatorProvider

So now that we have our rules mapped, we have to do the actual work of inspecting our properties and emitting rules.  To do this we will basically use the NHibernate Validator library to get the constraints for every “property” and then convert our constraints to the desired rules.

/// <summary>
/// Returns the validators for the given class metadata.  This gets called for each property.
/// </summary>
/// <returns>Yield returns client validator instances with a list of rules for the current property</returns>
protected override IEnumerable<ModelValidator> GetValidators(
    ModelMetadata metadata, 
    ControllerContext context, 
    IEnumerable<Attribute> attributes
)
{
    if (metadata.ContainerType == null) yield break; //Break if there is no metadata container
 
    var engine = ValidatorEngineFactory.ValidatorEngine;
 
    var validator = engine.GetClassValidator(metadata.ContainerType);
    var constraints = validator.GetMemberConstraints(metadata.PropertyName).OfType<IRuleArgs>();
 
    var rules = new List<ModelClientValidationRule>();
 
    foreach (var constraint in constraints) //for each constraint, emit the rules for that constraint
    {
        foreach (var validationRule in _ruleEmitters.EmitRules(constraint))
        {
            validationRule.ErrorMessage = constraint.Message; //Temporarily give validation rule the error message provided by the validator
 
            validationRule.ErrorMessage = MessageOrDefault(validationRule, metadata.PropertyName); //Get a true error message if not provided
 
            rules.Add(validationRule);
        }
    }
 
    yield return new NHibernateValidatorClientValidator(metadata, context, rules);
}

There is nothing too crazy here, but we do have to be aware that one property can have many constraints, and for each constraint we must emit the proper rule (using our ruleEmitters mapping).  Then for each validation rule we must get the proper error message (described later) and add it to our list of rules for that property.  Then we yield return a simple ModelValidator instance (called NHibernateValidatorClientValidator) which just a dumb container for presenting the rules we provided.  Let’s take a look at it:

 

Implementing The Simple ModelValidator

As mentioned above, our model validator is pretty simple since our provider did all of the work.  We store the list of ModelClientValidationRule classes that was passed in to the constructor and override the GetClientValidationRules method to return this list.  We are forced to override the Validate method as well, but here we just return an empty list since we aren’t concerned about server-size validation here.

/// <summary>
/// Simple validator class which overrides GetClientValidationRules to return a list of ModelClientValidationRules, which cause client side validation
/// </summary>
public class NHibernateValidatorClientValidator : ModelValidator
{
    public NHibernateValidatorClientValidator(ModelMetadata metadata, ControllerContext controllerContext, List<ModelClientValidationRule> rules)
        : base(metadata, controllerContext)
    {
        Rules = rules;
    }
 
    protected List<ModelClientValidationRule> Rules { get; set; }
 
    /// <summary>
    /// Simply returns the supplied list of ModelClientValidationRule instances.
    /// </summary>
    /// <returns></returns>
    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        return Rules;
    }
 
    /// <summary>
    /// Returns an empty enumeration since this is not a server-side validator
    /// </summary>
    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        return Enumerable.Empty<ModelValidationResult>();
    }
}

 

Getting Good Error Messages

One tricky aspect of client validation is that NHV creates messages at runtime, so there isn’t a static message that can be displayed in advance.  If you provide NHibernate with a static message using the Message = ‘msg’ parameter then we can display that easily, but if no message is provided then we have to go another route.  So what I decided to do was workaround this by having the built-in System.ComponentModel.DataAnnotations validators provide the messages for me (of course, they rely on a static resource file under the covers, but it is internal and not directly accessible).

Our strategy will be to inspect the rule we have to see if the error message was provided.  If is was not provided, we will inspect the validation rule to and determine its type.  Using this type we will create a new instance of the corresponding DataAnnotations validator and use the message it provides.

protected string MessageOrDefault(ModelClientValidationRule rule, string propertyName)
{
    // We don't want to display the default {validator.*} messages
    if ((rule.ErrorMessage != null) && !rule.ErrorMessage.StartsWith("{validator."))
        return rule.ErrorMessage;
 
    switch (rule.ValidationType)
    {
        case "stringLength" :
            var maxLength = (int) rule.ValidationParameters["maximumLength"];
            return
                new StringLengthAttribute(maxLength).FormatErrorMessage(propertyName);
        case "required" :
            return new RequiredAttribute().FormatErrorMessage(propertyName);
        case "range" :
            var min = Convert.ToDouble(rule.ValidationParameters["minimum"]);
            var max = Convert.ToDouble(rule.ValidationParameters["maximum"]);
            return
                new System.ComponentModel.DataAnnotations.RangeAttribute(min, max).FormatErrorMessage(propertyName);
        case "regularExpression":
            var pattern = (string)rule.ValidationParameters["pattern"];
            return new RegularExpressionAttribute(pattern).FormatErrorMessage(propertyName);
        default:
            throw new NotSupportedException(
                "Only stringLength, Required, Range and RegularExpression validators are supported for generic error messages.  Add a custom error message or choose another validator type");
    }   

 

Hooking up the Client-Side ModelValidatorProvider

Now we have our new ModelValidationProvider but we still have to register it in the global.asax file:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    RegisterRoutes(RouteTable.Routes);
 
    ModelValidatorProviders.Providers.Clear(); //Optional: Remove the default data annotations validations
 
    ModelValidatorProviders.Providers.Add(new NHibernateValidatorProvider()); //Server side validation provider
    ModelValidatorProviders.Providers.Add(new NHibernateValidatorClientProvider()); //Client side validation provider
}

The last line here is where we add our new client side NHibernateValidatorClientProvider.

 

Results – Validation In Action

Here is a quick action/view which should test many of the validators we configured above:

image

image

To show there is no magic here I used the awesome Html.EditorForModel() helper to create the form (the model was our Customer class shown at the top of this post).  The other important line is <% Html.EnableClientValidation(); %>, which tells ASP.NET MVC 2 to emit its client validation scripts (note: you’ll have to reference the proper javascript files – I used MicrosoftAjax.js and MicrosoftMvcValidaton.js).

Now let’s try to create the Customer and see what happens:

image

As expected, we fail validation and get pretty good error messages.  Note the “Don’t forget the contact name” custom message got through properly.

Let’s try typing into the company name box without hitting submit and see what happens:

image

Excellent!  So now we have fully integrated client and server side validation with ASP.NET MVC.

 

Show Me Code Or It Didn’t Happen

All of the code needed to get server and client side validation integrated with ASP.NET MVC 2, along with a sample project, can be found at http://github.com/srkirkland/NHValModelValidators.

Enjoy!

9 Comments

  • this is the kind of posting that should end with a donations link. bravo!

  • Excelente!, muy buen artículo. Muchas gracias, es lo que estaba buscando..

  • i really like archeage gold! they are simply as a result nice and i would produce these individuals all around you only may. i would most certainly endorse them how to everyone. be sure to sprinkle these individuals.

  • All of these Buy Mulberry are really fantastic. I got all of them today plus morning soooo fired up! My local freinds have experienced these types of for years with made it through! in case you're choosing Buy Mulberry you should definitely use them relating to initial. look at the dimension and look at all of them are the real deal, certainly not on the web. buy a colouring which will satisfy your clothing. and if you're saving up, dont stop learning .! wait. it is worth every penny. managed to get all of the simple common Buy Mulberry during proverb. they can be great! we do hope you get them :)

  • i take these people time as i merely find it difficult to endure to come up with a later date together with particular little feet. theser buy guild wars 2 gold seem to be so attractive however lovely. complement all. most certainly a fundamental beauty tool in doing my wardrobe

  • What's up, I wish for to subscribe for this website to obtain latest updates, so where can i do it please help out.

  • It is my favorite fifth swtor credits;) Now i am excited about this;)) you should nice grown to be incredibly enticing then;) this can be a silver precious metal one which I found last time... Herbal bud used the it then designed for people.... All of us examines everyone.. Oh yea remorseful , absolutely not through others... Inside my brand-new swtor credits:)It is model and thus method throughout equal timeI should definetely recommend it again to each and every not likely simple man;)

  • I like swtor credits!!!

  • NHibernate Client Validator ASP.NET MVC 2 Model Validation - Scott's Blog btuqqicqk sac longchamp

Comments have been disabled for this content.