[C#/.NET] dynamically build middleware pipeline on console

As shown in the figure above: we will deform step by step in the following article to realize such a function.

1, Fool execution demonstration

First, create a console project and create three functions: Begin() FirstMiddleware() SecondMiddleware() End()

 1        /// <summary>
 2        ///Before starting execution
 3        /// </summary>
 4        public static void Begin()
 5        {
 6            Console.WriteLine("begin executing");
 7        }
 8
 9        /// <summary>
10        ///End of execution
11        /// </summary>
12        public static void End()
13        {
14            Console.WriteLine("end executed");
15        }
16
17        public static void FirstMiddleware()
18        {
19            Console.WriteLine("First Middleware");
20        }
21        public static void SecondMiddleware()
22        {
23            Console.WriteLine("Second Middleware");
24        }

Execute in the following order and display the results

 1        static void Main(string[] args)
 2        {
 3            //Console.WriteLine("Hello World!");
 4            #region fool demonstration middleware execution method
 5            Begin();
 6            FirstMiddleware();
 7            End();
 8            Console.WriteLine("-------------------");
 9            Begin();
10            SecondMiddleware();
11            End();
12            #endregion
13        }

2, Encapsulated fool execution function

The Begin() and End() methods will be executed multiple times, so we can choose to encapsulate them

 1        /// <summary>
 2        ///Encapsulate an execution method to reduce duplication of code
 3        ///The input parameter can pass a function with no parameter and no return value, which is accepted by the built-in delegate
 4        /// </summary>
 5        /// <param name="function"></param>
 6        public static void CommonExecute(Action function)
 7        {
 8            Begin();
 9            function();
10            End();
11        }

Call as follows, and the execution result is the same as before

1            #region encapsulates fool like execution functions
2            CommonExecute(FirstMiddleware);
3            Console.WriteLine("-------------------");
4            CommonExecute(SecondMiddleware);
5            #endregion

3, Simplification by lambda deformation

We need to pass another parameter of execute () as the function of execute;
An ExtraExecute() function is created

 1        public static void ExtraExecute(Action function)
 2        {
 3            try
 4            {
 5                Console.WriteLine("try here");
 6                function();
 7            }
 8            catch (Exception)
 9            {
10
11                throw;
12            }
13        }

We pass the wrapped CommonExecute() as an input parameter to the ExtraExecute() call, as shown below:

1            ExtraExecute(() => CommonExecute(FirstMiddleware)); //Execute the first middleware, execute ExtraExecute() first and then CommonExecute()
2            Console.WriteLine("-----------");
3            ExtraExecute(() => CommonExecute(SecondMiddleware)); //Execute the second Middleware

Through observation, it is found that the signatures of CommonExecute() and ExtraExecute() methods are the same, so we can change the execution order of the two methods, as shown below:

1            Console.WriteLine("-----------");
2            //Change the calling sequence of middleware} execute CommonExecute() first and then execute ExtraExecute()
3            CommonExecute(() => ExtraExecute(FirstMiddleware));

Create middleware call chain with parameters

We just demonstrated the middleware call without parameters. Now we add parameters to the middleware executive body, and add three functions: FirstPipe(string msg, Actionfunc), SecondPipe(string msg, Actionfunc) and ThirdPipe(string msg, Actionfunc). The core principle is to wrap the middleware executive function into parameters and pass them to the next executive function

1 Action<string> pipe = (msg) => ThirdPipe(msg,
2                                                        (msg1) => SecondPipe(msg1,
3                                                            (msg2) => FirstPipe(msg2, FirstMiddleware)));
4 pipe("Hello World"); //Call

Dynamically create middleware pipeline model

From the creation of middleware call chain with parameters, we find that we can't make this pipeline dynamic, so how can we dynamically transfer the middleware and dynamically change the call order?
Through analysis, I found that the most basic unit of a pipeline execution can be defined as follows

 1        public abstract class DynamicPipeBase
 2        {
 3            protected readonly Action<string> _action;
 4
 5            public DynamicPipeBase(Action<string> action)
 6            {
 7                this._action = action;
 8            }
 9            public abstract void Handle(string msg); //Call and execute middleware function body
10        }

Then, three functions are defined to inherit {DynamicPipeBase()

 1        /// <summary>
 2        ///The third Middleware
 3        /// </summary>
 4        public class ThirdSelfPipe : DynamicPipeBase
 5        {
 6            public ThirdSelfPipe(Action<string> action):base(action)
 7            {
 8
 9            }
10            public override void Handle(string msg)
11            {
12                Console.WriteLine("Third Pipe Begin executing");
13                _action(msg);
14                Console.WriteLine("Third Pipe End executed");
15            }
16        }
17
18        public class SecondSelfPipe : DynamicPipeBase
19        {
20            public SecondSelfPipe(Action<string> action) : base(action)
21            {
22
23            }
24            public override void Handle(string msg)
25            {
26                Console.WriteLine("Second Pipe Begin executing");
27                _action(msg);
28                Console.WriteLine("Second Pipe End executed");
29            }
30        }
31
32        /// <summary>
33        ///First Middleware
34        /// </summary>
35        public class FirstSelfPipe : DynamicPipeBase
36        {
37            public FirstSelfPipe(Action<string> action) : base(action)
38            {
39
40            }
41            public override void Handle(string msg)
42            {
43                Console.WriteLine("First Pipe Begin executing");
44                _action(msg);
45                Console.WriteLine("First Pipe End executed");
46            }
47        }

With middleware, we only need to build the pipeline, as shown below

 1        /// <summary>
 2        ///Storage middleware: List < type >_ types;
 3        ///Generate middleware call chain: GeneratePipe
 4        ///Build callback: build
 5        /// </summary>
 6        public class PipeBuilder
 7        {
 8            protected readonly Action<string> _mainAction;
 9            List<Type> _types;
10            public PipeBuilder(Action<string> mainAction)
11            {
12                _mainAction = mainAction;
13                _types = new List<Type>();
14            }
15            public PipeBuilder AddPipe(Type type)
16            {
17                _types.Add(type);
18                return this;
19            }
20
21            private Action<string> GeneratePipe(int index)
22            {
23                if(index==_types.Count-1)
24                {
25                    var finalType= (DynamicPipeBase)Activator.CreateInstance(_types[index], _mainAction);
26                    return finalType.Handle;
27                }
28                else
29                {
30                    var childHalde= GeneratePipe(index + 1);
31                    return ((DynamicPipeBase)Activator.CreateInstance(_types[index], childHalde)).Handle;
32                } 
33
34            }
35
36            public Action<string> Build()
37            {
38                return GeneratePipe(0);
39            }
40        }

Finally, we call on the upper end:

1            #region dynamically create middleware pipeline model
2            Action<string> pipeChain = new PipeBuilder(MainAction)
3                .AddPipe(typeof(ThirdSelfPipe)) //Add multiple middleware execution units at will
4                .AddPipe(typeof(SecondSelfPipe))
5                .AddPipe(typeof(FirstSelfPipe))
6                .Build();
7            pipeChain("Hello World");
8            Console.WriteLine("-------------");
9            #endregion

In this way, the effect shown at the beginning of the article is realized.

Keywords: C# Middleware

Added by CherryT on Tue, 08 Mar 2022 14:05:55 +0200