Fun with Pipelining, Chaining & Linq – P2 (Actions from Text)
In a previous post I created a pipeline & executed it using extension methods & Action<t> function pointer. From quickly reading the code you will realize that you are bound only by 2 things
1) You are bound by design time. All the pipelines has to be structured during development.
2) You cannot dynamically load actions from text (configuration files, database etc..)
I modified the code as the following:
Created a class for validation methods (just to give a bit of a structure to my code)
class CustomerValidationMethods
{
public static void SaveCustomer(Customer c)
{
Console.WriteLine("SAVE CUSTOMER");
}
public static void ValidateCustomer(Customer C)
{
//throw exception here if customer is not valid
Console.WriteLine("Validate Customer");
}
public static void RefreshCustomerCache(Customer c)
{
Console.WriteLine("REFRESH Cache");
}
}
Now in addition to classical way of creating the pipeline as discussed in part 1
Action<Customer>[] normalPipeline =
{
c => CustomerValidationMethods.ValidateCustomer(c),
c => CustomerValidationMethods.SaveCustomer(c),
c => CustomerValidationMethods.RefreshCustomerCache(c)
};
You can also create the pipeline using strings as the following
Action<Customer>[] strBasedPipeline =
{
"ValidateCustomer".ToAction<Customer>(typeof(CustomerValidationMethods)),
"SaveCustomer".ToAction<Customer>(typeof(CustomerValidationMethods)),
"RefreshCustomerCache".ToAction<Customer>(typeof(CustomerValidationMethods))
};
These strings can be loaded from any external configurable source, you can have 2 tables in database one defining pipelines, the other defines the stages. You can also load assemblies on the fly and call validation methods on them.
How did it work:
when the .NET team created Linq it was not enough to use lambda expressions, a mean by which a downstream query evaluator can walk through the query to translate it to native data source calls had to be created. Think of how your Linq queries are translated to highly efficient where statement on SQL when you use Linq to XML or when you use Linq to EF. This mechanism is called Linq Expressions & Expressions Trees, encapsulated all in System.Linq.Expressions. I am piggybacking on the mechanism to use for something else.
check out https://msdn.microsoft.com/en-us/library/bb397951.aspx https://blogs.msdn.com/b/charlie/archive/2008/01/31/expression-tree-basics.aspx
in order to encapsulate the code I created another extension method for type String which creates an expression tree representing our action
public static Action<tActionTarget> ToAction<tActionTarget>(this string str, Type ActionContainer)
{
//we know that we will recieve our actions as one method call that takes one parameter as customer
// we are creating this paramter in our expression
ParameterExpression pExp = Expression.Parameter(typeof(tActionTarget));
// our Expresion is one method call, as static method on a specific type (action Container)
// the string we are extending is the method name
MethodCallExpression mcExp = Expression.Call(ActionContainer, str, null, pExp);
// generate our lamda
Expression<Action<tActionTarget>> exp = Expression.Lambda<Action<tActionTarget>>(mcExp, pExp);
return exp.Compile();
}
Both pipeline can be executed using the extension method I created previously
normalPipeline.ExecuteForEach(act => act(newCustomer));
strBasedPipeline.ExecuteForEach(act => act(newCustomer));
in this discussion and the previous I skipped on other parts a typical pipeline would have like a results objects being moved from call to call or a property bag that has some sort of context, this is to focus on the core idea I am trying to discuss. The above implementation can be generalized for to include results objects, prop bags & log writers as needed.
Code files attachde
Next enter the Rules World!
Find me on Twitter https://twitter.com/khnidk