An xVal Provider For NHibernate Validator

I wrote a post about a month ago about using xVal with NHibernate Validator 1.2 which solved a problem I was having upgrading the xVal ‘in-the-box’ provider to work with a newer version of NHibernate Validator. There was a caveat that my solution only worked for ValidatorMode.UseAttribute and I wouldn’t catch XML or Loquacious (or other?) validation.  This seemed to work OK, but Fabio Maulo wrote a comment to that post saying NHV has metadata which should be the same no matter which validation mode was used.

So I decided to investigate how I could get the metadata without resorting to the NHibernate.Validator.Mappings validationMode specific engines (I was using ReflectionClassMapping, but there are others like XmlClassMapping).

I think I found the solution (at least, it does work…) in the ValidatorEngine’s GetClassValidator method.  GetClassValidator takes in a System.Type and returns an IClassValidator for that Type.  Once you have the class validator, you can get constraints for any member of that class by using GetMemberConstraints(memberName).

After getting the constraints you are onto the easy part, which is basically just mapping the rules into xVal rules and the code is much the same as before.

So without further ado, here is the new ValidatorRulesProvider implementation:

public class ValidatorRulesProvider : CachingRulesProvider
{
    private readonly RuleEmitterList<IRuleArgs> _ruleEmitters;
 
    public ValidatorRulesProvider()
    {
        _ruleEmitters = new RuleEmitterList<IRuleArgs>();
 
        _ruleEmitters.AddSingle<LengthAttribute>(x => new StringLengthRule(x.Min, x.Max));
        _ruleEmitters.AddSingle<MinAttribute>(x => new RangeRule(x.Value, null));
        _ruleEmitters.AddSingle<MaxAttribute>(x => new RangeRule(null, x.Value));
        _ruleEmitters.AddSingle<RangeAttribute>(x => new RangeRule(x.Min, x.Max));
        _ruleEmitters.AddSingle<NotEmptyAttribute>(x => new RequiredRule());
        _ruleEmitters.AddSingle<NotNullNotEmptyAttribute>(x => new RequiredRule());
        _ruleEmitters.AddSingle<NotNullAttribute>(x => new RequiredRule());
        _ruleEmitters.AddSingle<PatternAttribute>(x => new RegularExpressionRule(x.Regex, x.Flags));
        _ruleEmitters.AddSingle<EmailAttribute>(x => new DataTypeRule(DataTypeRule.DataType.EmailAddress));
        _ruleEmitters.AddSingle<DigitsAttribute>(MakeDigitsRule);
    }
 
    protected override RuleSet GetRulesFromTypeCore(Type type)
    {
        var classMapping = new ValidatorEngine().GetClassValidator(type);
 
        var rules = from member in type.GetMembers()
                    where member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property
                    from constraint in classMapping.GetMemberConstraints(member.Name).OfType<IRuleArgs>()
                    // All NHibernate Validation validators attributes must implement this interface
                    from rule in ConvertToXValRules(constraint)
                    where rule != null
                    select new { MemberName = member.Name, Rule = rule };
 
        return new RuleSet(rules.ToLookup(x => x.MemberName, x => x.Rule));
    }
 
    private IEnumerable<Rule> ConvertToXValRules(IRuleArgs ruleArgs)
    {
        foreach (var rule in _ruleEmitters.EmitRules(ruleArgs))
        {
            if (rule != null)
            {
                rule.ErrorMessage = MessageIfSpecified(ruleArgs.Message);
                yield return rule;
            }
        }
    }
 
    private static RegularExpressionRule MakeDigitsRule(DigitsAttribute att)
    {
        if (att == null) throw new ArgumentNullException("att");
        string pattern;
        if (att.FractionalDigits < 1)
            pattern = string.Format(@"\d{{0,{0}}}", att.IntegerDigits);
        else
            pattern = string.Format(@"\d{{0,{0}}}(\.\d{{1,{1}}})?", att.IntegerDigits, att.FractionalDigits);
        return new RegularExpressionRule(pattern);
    }
 
    private static string MessageIfSpecified(string message)
    {
        // We don't want to display the default {validator.*} messages
        if ((message != null) && !message.StartsWith("{validator."))
            return message;
        return null;
    }
}

Of course in the global.asax file you just need to hook xVal and this class together like so:

xVal.ActiveRuleProviders.Providers.Add(
    new ValidatorRulesProvider(
    ValidatorMode.UseAttribute));

If you aren’t using xVal I would highly recommend it, and if you are using ASP.NET MVC and not using xVal, what are you waiting for?  They are a great match!

 

Enjoy!

11 Comments

Comments have been disabled for this content.