samedi 3 octobre 2009

xVal - ValidationAspects RulesProvider

Le framework xVal permet de déployer les règles de validation côté serveur vers l'interface web. Il peut utiliser différents frameworks du côté client comme JQuery ou ASP.net. De la même manière il peut s'appuyer sur différents framework de validation pour déduire les règles de validation serveur. Et il s'intègre particulièrement bien à ASP.net MVC !

De son côté ValidationAspects est un framework de validation assez simple d'utilisation.

Ni l'un ni l'autre de ces deux frameworks ne propose d'implémentation les rendant compatibles mais en regardant le source des fournisseurs de règles xVal pour Castle ou NHibernate, on ne met pas bien longtemps à en construire un pour ValidationAspects ... d'ailleur le voici :



using xVal.RuleProviders;
using VA = ValidationAspects;
using xVal.Rules;
using System.Reflection;
using ValidationAspects.Sdk;
using ValidationAspects.Factories;

namespace TestsApp.Validation.xVal
{
public class ValidationAspectsRulesProvider : CachingRulesProvider
{
private readonly RuleEmitterList<ValidatorAttribute> validationRuleEmitters = new RuleEmitterList<ValidatorAttribute>();

public ValidationAspectsRulesProvider()
{
var e = validationRuleEmitters;
e.AddSingle<VA.GreaterThanAttribute>(MakeGreaterThanRule);
e.AddSingle<VA.InRangeAttribute>(MakeInRangeRule);
e.AddMultiple<VA.IsDateAttribute>(x => new Rule[] {
new RequiredRule(),
new DataTypeRule(DataTypeRule.DataType.Date)
});
e.AddSingle<VA.IsEmailAttribute>(x => new DataTypeRule(DataTypeRule.DataType.EmailAddress));
e.AddSingle<VA.LengthInRangeAttribute>(MakeLengthInRangeRule);
e.AddSingle<VA.LessThanAttribute>(MakeLessThanRule);
e.AddSingle<VA.MatchesRegexAttribute>(MakeMatchesRegexRule);
e.AddSingle<VA.MaximumAttribute>(MakeMaximumRule);
e.AddSingle<VA.MaximumLengthAttribute>(MakeMaximumLengthRule);
e.AddSingle<VA.MinimumAttribute>(MakeMinimumRule);
e.AddSingle<VA.MinimumLengthAttribute>(MakeMinimumLengthRule);
e.AddSingle<VA.NotNullAttribute>(x => new RequiredRule());
e.AddSingle<VA.NotNullOrEmptyAttribute>(x => new RequiredRule());
}


protected override RuleSet GetRulesFromTypeCore(Type type)
{
// Rêgles de validation
var rules = from member in type.GetMembers(BindingFlags.Instance | BindingFlags.Public)
// pour les champs et propriétés
where (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
// joints à leurs attributs hérité du framework de validation
from att in GetMemberAttributes(member)
// et joints aux rêgles qui en découle
from rule in ConvertToXValRules(att)
select new { MemberName = member.Name, Rule = rule };
;

return new RuleSet(rules.ToLookup(x => x.MemberName, x => x.Rule));
}

IEnumerable<ValidatorAttribute> GetMemberAttributes(MemberInfo member)
{
return member.GetCustomAttributes(true).Where(att => att is ValidatorAttribute)
.Cast<ValidatorAttribute>();
}

private IEnumerable<Rule> ConvertToXValRules(ValidatorAttribute att)
{
return validationRuleEmitters.EmitRules(att);
}


#region MakeRules

private Rule MakeInRangeRule(VA.InRangeAttribute att)
{
var facto = att.Factory as InRange;
return new RangeRule(Convert.ToDecimal(facto.Minimum), Convert.ToDecimal(facto.Maximum));
}
private Rule MakeGreaterThanRule(VA.GreaterThanAttribute att)
{
var facto = att.Factory as GreaterThan;
return new RangeRule(Convert.ToDecimal(facto.Value), null);
}
private Rule MakeMinimumRule(VA.MinimumAttribute att)
{
var facto = att.Factory as Minimum;
return new RangeRule(Convert.ToDecimal(facto.Value), null);
}
private Rule MakeLessThanRule(VA.LessThanAttribute att)
{
var facto = att.Factory as LessThan;
return new RangeRule(null, Convert.ToDecimal(facto.Value));
}
private Rule MakeMaximumRule(VA.MaximumAttribute att)
{
var facto = att.Factory as Maximum;
return new RangeRule(null, Convert.ToDecimal(facto.Value));
}

private Rule MakeLengthInRangeRule(VA.LengthInRangeAttribute att)
{
var facto = att.Factory as LengthInRange;
return new StringLengthRule(facto.Minimum, facto.Maximum);
}
private Rule MakeMinimumLengthRule(VA.MinimumLengthAttribute att)
{
var facto = att.Factory as MinimumLength;
return new StringLengthRule(facto.Length, null);
}
private Rule MakeMaximumLengthRule(VA.MaximumLengthAttribute att)
{
var facto = att.Factory as MaximumLength;
return new StringLengthRule(null, facto.Length);
}

private Rule MakeMatchesRegexRule(VA.MatchesRegexAttribute att)
{
var facto = att.Factory as MatchesRegex;
return new RegularExpressionRule(facto.Regex);
}

#endregion
}
}



Enjoy !

Asp.net MVC Editors using lambda expressions

Asp.net MVC c'est génial mais ASP.net MVC 2 sera encore mieux, notamment grâce à une amélioration des helpers et des méthodes d'extensions des vues permettant de typer l'accès aux membres du model grâce aux expressions lambda.

Si vous ne voulez pas attendre et que vous voulez vous aussi pouvoir écrire :


<%= Html.TextBoxFor(Model, m => m.Libelle) %>


la suite vas vous intéresser.

Commencez par écrire une classe permettant de stocker pour un membre de classe, son type, son nom et sa valeur :



class PropertyDef
{
public Type Type { get; private set; }
public string Name { get; private set; }
public object Value { get; private set; }

public PropertyDef(Type type, String name, object value)
{
this.Type = type;
this.Name = name;
this.Value = value;
}
}



Ensuite nous allons devoir créer un helper permettant de parser une expression lambda récupérer précisément ces éléments :



static class LambdaExpressionParser
{
internal static PropertyDef GetPropertyDef<TModel, TValue>(
this Expression<Func<TModel, TValue>> expression, TModel model)
{
ExpressionType nodeType = expression.Body.NodeType;
if (nodeType != ExpressionType.MemberAccess)
throw new InvalidOperationException("MemberAccess expression type needed");

// Récupération de la propriété et de son type
MemberExpression body = (MemberExpression)expression.Body;
MemberInfo member = body.Member;
var name = member.Name;
var type = typeof(TValue);

// Récupération de la valeur
object value = null;
try { value = expression.Compile()(model); }
catch (NullReferenceException) { }

return new PropertyDef(type, name, value);
}
}



Et maintenant, il ne reste plus qu'à encapsuler l'usage d'une méthode d'extension ASP.net MVC dans notre propre méthode d'extension :



public static string TextBoxFor<TModel, TValue>(this HtmlHelper html, TModel model, Expression<Func<TModel, TValue>> expression)
{
var def = LambdaExpressionParser.GetPropertyDef(expression, model);
return System.Web.Mvc.Html.InputExtensions.TextBox(html, def.Name, def.Value);
}



A noter que l'implémentation de MVC 2 sera bien plus pratique puisque les ViewPage vont exposer un HtmlHelper générique qui permet de raccourcir encore la syntaxe (voir l'article de Scott Guthrie).
Et voilà ! vivement ASP.net MVC 2.