public static void ValidateIdentity(Identity identity, IScope scope)
{
scope.Validate(identity.Name, "Name", StringIs.ValidEmail);
scope.Validate(identity.Pass, "Pass", StringIs.Limited(3,64));
}
Almost everything is sticking to the DRY principle here (with the ability to shape the validation in any way, since that’s plain code). Well, everything except for one thing – name of the property being checked. We duplicate it as a string literal in order to get the proper error path in the description (this is really useful for scenarios when rules, as well as the objects being checked, have deep nesting).
This yields following limitations:
- Extra typing is needed to define such rules.
- Refactoring class by renaming the property will make the rule out of the sync with the object. This type of problem is called dormant, since it is not found by the compiler or simple unit tests.
- This will become quite a problem, if we want to reuse our existing rules at the UI level for the validation (Windows.Forms, AspNet.Forms etc).
There are three obvious solutions to the problem via some kind of Rules DSL:
- Implement custom rule declaration syntax in Boo
- Wait till C# gets compiler extensibility
- Use slightly different declaration syntax
The third option could be implemented in less than an hour and, thus, is a bit more preferable.
Alternative syntax to compose rule for validating the domain objects is:
public static readonly Rule<Identity> ValidIdentity =
Validate<Identity>
.Property(i => i.Name, StringIs.ValidEmail)
.Property(i => i.Pass, StringIs.Limited(3,64));
As you can see, we mention the member only once here. Yet, due to some C# magic, the name of the property will be derived properly.
This approach has another slight advantage. It is more easy for the ReSharper to figure out the situation in some cases, since we are using strongly-typed delegates instead of the method groups.
The performance impact is negligible, since the code flow does not perform any compilation or reflection after the rule has been created.
This syntax is not in the Validation and Business Rules Application Block for .NET, yet. It will require a bit of usage validation against production scenarios, first. But it should be there quite soon, since leveraging existing rules at the UI level, makes the code more reliable and efficient.
Implementation
Here’s the C# code to have this done on top of the latest Lokad.Shared.dll:
// Lokad.Shared.Test/Rules/Case2/RuleSyntax.cs
public static class Validate<T>
{
public static RuleSyntax<T> Property<P>(
Expression<Func<T, P>> expression, params Rule<P>[] rules)
{
return new RuleSyntax<T>().Property(expression, rules);
}
}
public sealed class RuleSyntax<T> : Syntax<ICollection<Rule<T>>>
{
internal RuleSyntax() : base(new List<Rule<T>>())
{
}
public RuleSyntax<T> Property<P>(
Expression<Func<T, P>> expression, params Rule<P>[] rules)
{
Enforce.Argument(expression, "expression");
var memberExpression = expression.Body as MemberExpression;
Enforce.That(memberExpression != null,
"Expression should be a member expression");
var member = memberExpression.Member;
Enforce.That(member.MemberType == MemberTypes.Property,
"Expression should be a property reference");
var getValue = expression.Compile();
var name = member.Name;
Target.Add((t, scope) => scope.Validate(getValue(t), name, rules));
return this;
}
public static implicit operator Rule<T>(RuleSyntax<T> syntax)
{
var rules = syntax.Target.ToArray();
return (t, scope) => rules.ForEach(rule => rule(t,scope));
}
}
The snippet above features:
- Usage of Linq Expressions
- Usage of some helper extensions defined in the System namespace of the library
- Usage of the Syntax{T} class (from the Lokad Shared Libraries) that helps in defining Fluent APIs
So what do you think about this type of syntax? Any feedback or comments are appreciated.