Partager via


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

p2-Code.zip