Janga – A Validation Framework with a Fluent API September 26, 2010
Posted by ActiveEngine Sensei in .Net, ActiveEngine, Business Processes, C#, Design Patterns, Expression Trees, Fluent, LINQ, New Techniques, Problem Solving.Tags: C#, delegate, Expression Tree, Fluent API, Lambda, LINQ, Validation Framework
trackback
Why can’t we write code that read likes this:
bool passed = employee.Enforce() .When("Age", Compares.IsGreaterThan, 45) .When("Department", Compares.In, deptList) .IsValid(); if(passed) { SomeProcess(); }
One of the enduring challenges for software developers and business is to create abstractions that accurately represent concrete rules for business operations. As opposed to operating like our tribal ancestors where you had to kill a goat, start a fire and listen to the blind boy tell the tale told for thousands of years, today we’d like to be able to read stories ourselves. Hopefully the story that we read matches the reality of what we have implemented in our code. Many nested if statements can quickly make verifying that the code matches the story very difficult.
A fluent validation API can assist with this. Look at the code at the top of the post. You can show that most people without having to get out the smelling salts. For your fellow developers its creates a succinct way to express precisely what the logic is. They’ll love you for it.
Janga, a fluent validation framework for creating such an API. There are three goals to be met here, and Janga fulfills these goals:
Goal 1 – Be able to chain “When” clauses together. Each test – represented by the “When” clause – needs to chained together.
Goal 2 – Accept a test on any object property where the test criteria is defined in the form of x <= y at runtime. The types of objects and their properties will not be known until runtime, so our framework must be able to analyze an object and construct a test against each property as it is presented. This is NOT the specification pattern, where you define a delegates ahead of time.
Goal 3 – Flexibly handle errors by either halting on the first error, or by proceeding with each test and logging each error as it is encountered.
The code Sensei will present here fulfills all of these goals and gives us the fluent magic we see in the sample at the top of this post. Before we delve into the details, the sources for the ideas and explanations of Lambda Expressions, fluent apis, Expression trees, should be acknowledged and applauded, because they got Sensei thinking along the right path:
Fluent Validation API
Roger Alsing – Fluent Argument Validation Specification
Raffaele Garofalo – How to write fluent interface with C# and Lambda.
Lambdas, Expression Trees, Delegates, Predicates
Expression Tree Basics – Charlie Calvert’s Community Blog
Marc Gravell – Code, code and more code.: Explaining Expression
Marc Gravell – Code, code and more code.: Express yourself
Implementing Dynamic Searching Using LINQ (check the section regarding dynamic expressions.)
Creating this api is a twisted cluster-wack of a zen puzzle. The code for this solution consists of one class and three extension methods. We’ll make use of generics, delegates and expression trees to evaluate our When clauses. In the end we’ll see that with very little code we get a lot of mileage. It took Sensei a long time to wrap his head around how to piece all of these things together, so hopefully the explanation will be clear. Note that the solution has tests that demonstrate how to use the framework, so if you want to skip the madness and just try things out, go for it.
Goal 1: Chaining When clauses together
To get the ball rolling, there is an extension method Ensure that will accept the object you wish to evaluate, encapsulate that object into a Validation class.
public static Validation<T> Enforce<T>(this T item, string argName, bool proceedOnFailure) { return new Validation<T>(item, argName, proceedOnFailure); }
Creating a chain of tests is accomplished with the Validation class and successive calls to the extension method When. Validation encapsulates the object you wish to test. In our examples that’s Employee. Employee will be passed on to When, When executes a test and stores the results in Validation. After the test, When returns Validation, and this creates the opportunity to execute another extension method.
public class Validation<T> { public T Value { get; set; } public string ArgName { get; set; } public bool ProceedOnFailure { get; set; } public bool IsValid { get; set; } public IList<string> ErrorMessages { get; set; } public Validation(T value, string argName) { this.ArgName = argName; this.Value = value; this.ProceedOnFailure = false; // Set to valid in order to allow for different chaining of validations. // Each validator will set value relative to failure or success. this.IsValid = true; this.ErrorMessages = new List<string>(); } public Validation(T value, string argName, bool proceedOnFailure) { this.ArgName = argName; this.Value = value; this.ProceedOnFailure = proceedOnFailure; // Set to valid in order to allow for different chaining of validations. // Each validator will set value relative to failure or success. this.IsValid = true; this.ErrorMessages = new List<string>(); } }
Signature of When (note that we return Validation):
public static Validation<T> When<T>(this Validation<T> item, string propertyName, Compare compareTo, object propertyValue)
Before we continue on with reviewing dynamic evaluation by the When clause, you could stop here and still have a useful mechanism for creating validation routines. That is, you could create a extension method for each validation you want to perform. One example could be:
public static Validation<Employee> LastNameContains( this Validation<Employee> employee, string compareValue) { var result = employee.Value.LastName.Enforce("LastName", employee.ProceedOnFailure).Contains(compareValue); employee.IsValid = result.IsValid; result.ErrorMessages .ToList() .ForEach(x => employee.ErrorMessages.Add("LastName => " + x)); return employee; }
So instead of Ensure.When you will use Ensure.LastNameContains(“Smi”). You will also have to create a new method for each condition. This is still quite expressive and would go a long way to keeping things organized. This would be more in the spirit of the specification pattern.
Goal 2: Dynamically Evaluating Tests at Runtime
As stated, the “tests” are performed with extension method When. When accepts the Validation object, along with propertyName and the propertyValue that you are testing. The enum Compare determines the type of test to perform. The comparisons are:
public enum Compare { Equal = ExpressionType.Equal, NotEqual = ExpressionType.NotEqual, LessThan = ExpressionType.LessThan, GreaterThan = ExpressionType.GreaterThan, LessThanOrEqual = ExpressionType.LessThanOrEqual, GreaterThanOrEqual = ExpressionType.GreaterThanOrEqual, Contains = ExpressionType.TypeIs + 1, In = ExpressionType.TypeIs + 2 }
The magic of When stems from the use of Expression trees as delegates. As defined on MSDN, an expression tree is:
Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y.
You can compile and run code represented by expression trees. This enables dynamic modification of executable code, the execution of LINQ queries in various databases, and the creation of dynamic queries.
This gives you the ability, at runtime, to dynamically evaluate an expression in the form of x = y, also referred to as a binary expression. And in our case, we wish to evaluate: Employee.Age = = 45. The delegate takes care of presenting the type of the Expression and it’s components to the runtime engine.
Marc Gravell explains the difference between a delegate and an Expression as:
- The delegate version (Func<int,int,bool>) is the belligerent manager; “I need you to give me a way to get from 2 integers to a bool; I don’t care how – when I’m ready, I’ll ask you – and you can tell me the answer”.
- The expression version (Expr<Func<int,int,bool>>) is the dutiful analyst; “I need you to explain to me – if I gave you 2 integers, how would you go about giving me a bool?”
In standard programming, the managerial approach is optimal; the caller already knows how to do the job (i.e. has IL for the purpose). But the analytic approach is more flexible; the analyst reserves the right to simply follow the instructions “as is” (i.e. call Compile().Invoke(…)) – but with understanding comes power. Power to inspect the method followed; report on it; substitute portions; replace it completely with something demonstrably equivalent, etc…
.NET 3.5 allows us to create “evaluators” with Lambda Expressions compiled as delegates that will analyze an object type, the comparisons we can make, and the values we want to compare dynamically. It will then execute that tiny block of code. This is treating our code as a set of objects. A graph representing this tree looks like so:
Each node on the tree is an Expression. Think of this as a “bucket” to hold a value, a property or an operation. For the runtime engine to know what the type and parameters of the Expressions are, we create a delegate from the Lambda expression of that node. In other words, we let the compiler know that we have an expression of type Employee and will evaluate whether Employee.Age is equal to 45.
To accomplish the magic at runtime, you need to set up “buckets” to hold Employee.Age or Employee.FirstName and their values with their respective type for evaluation. Furthermore we want to be able to evaluate any type of binary expression, so our Expression will make use of generics and a tiny bit of reflection so that we will have code that “parses” the object and it’s properties dynamically.
The Extension Method When:
public static Validation<T> When<T>(this Validation<T>; item, string propertyName, Compare compareTo, object propertyValue)
Creating the delegate of the Lambda expression:
// Determine type of parameter. i.e. Employee ParameterExpression parameter = Expression.Parameter(typeof(T), "x"); // Property on the object to compare to. i.e. Employee.Age Expression property = Expression.Property(parameter, propertyName); // The propertyValue to match. i.e 45 Expression constant = Expression.Constant(propertyValue, propertyValue.GetType());
This takes care of the X and Y of the binary expression, but the next task is to create the comparison as an Expression as well:
Expression equality = CreateComparisonExpression<T>(property, compareTo, constant);
The type of comparison is determined by the enum Compare. Once these steps are completed we convert the expression into a delegate with the statement:
var executeDelegate = predicate.Compile();
If you are worried about performance and the use of reflection, note that the use of static will greatly minimize this impact. Basically you’ll take the performance hit on the first run but not on the subsequent runs.
Goal 3: Error Reporting
For error reporting, Validation requires the name of the object with the property ArgName, and asks that you specify whether you wish to halt when there is an error. This is accomplished with ProceedOnFailure. An error log is created when you wish all tests to complete despite their respective results. When you want to halt on the first error and throw an exception set the ProceedOnFailure to false.
Reporting the errors themselves takes place in each When clause, and this is implemented at the end of the When extension method.
// Report Error handling if(item.IsValid == false) { if(item.ProceedOnFailure) { item.ErrorMessages.Add("When " + item.ArgName + "." + propertyName + " " + compareTo.ToString() + " " + propertyValue + " failed."); } else { throw new ArgumentException("When " + item.ArgName + "." + propertyName + " " + compareTo.ToString() + " " + propertyValue + " failed."); } }
Finally we need to return the Validation object so that we can chain another When operation
To recap, When is a dynamic filter where at runtime, code is evaluated and created on the fly to analyze and execute a tree representing code as an object. The expression trees can be applied to any object and evaluate the object’s properties. Holy snikes!!! If that doesn’t scare you how ‘bout chaining When’s together by always returning a Validation object so that you continue to apply another extension method to it. Twisted Zen mind torture indeed, since we have complicated looking code so that we can less complicated “business code”.
Comments»
No comments yet — be the first.