Using xVal with NHibernate Validator 1.2
This will be a quick and dirty post about how to get xVal 1.0 (http://xval.codeplex.com/) to work with the new NHibernate Validator 1.2beta (http://nhforge.org/media/p/7.aspx).
The problem is that xVal 1.0 ships with a NHibernate Validator (NHVal) Ruleset Provider which does not compile against the 1.2beta DLLs because of some API changes. Meanwhile, S#arp Architecture uses NHVal 1.2 so using those Dlls is a requirement for me.
In the process of rewriting the NHVal ruleset provider I stripped out all XML rule definition support, so for my code to work you must be using NHVal attributes.
With those disclaimers, here is the code:
public class NHibernateValidatorRulesProvider : CachingRulesProvider
{
private readonly ValidatorMode configMode;
private readonly RuleEmitterList<IRuleArgs> ruleEmitters = new RuleEmitterList<IRuleArgs>();
public NHibernateValidatorRulesProvider(ValidatorMode configMode)
{
this.configMode = configMode;
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<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 = ClassMappingFactory.GetClassMapping(type, configMode);
var rules = from member in type.GetMembers()
where member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property
from att in classMapping.GetMemberAttributes(member).OfType<IRuleArgs>()
// All NHibernate Validation validators attributes must implement this interface
from rule in ConvertToXValRules(att)
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 att)
{
foreach (var rule in ruleEmitters.EmitRules(att))
{
if (rule != null)
{
rule.ErrorMessage = MessageIfSpecified(att.Message);
yield return rule;
}
}
}
private string MessageIfSpecified(string message)
{
// We don't want to display the default {validator.*} messages
if ((message != null) && !message.StartsWith("{validator."))
return message;
return null;
}
private 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 class ClassMappingFactory
{
public static IClassMapping GetClassMapping(Type type, ValidatorMode mode)
{
const IClassMapping result = null;
switch (mode)
{
case ValidatorMode.UseAttribute:
break;
default:
throw new NotImplementedException("Only attributes are supported");
}
return result ?? new ReflectionClassMapping(type);
}
}
}
Now in the global.asax file, you just need to hook xVal and this class together like so:
xVal.ActiveRuleProviders.Providers.Add(
new NHibernateValidatorRulesProvider(
ValidatorMode.UseAttribute));