vlambda博客
学习文章列表

如何使用C#在控制台上动态构建中间件管道?


如上图所示:我们将会在下面文章上一步一步变形实现出这样的功能。


一、傻瓜式执行演示

首先建立控制台项目,创建Begin() FirstMiddleware() SecondMiddleware() End() 三个函数

 1        /// <summary>
2        /// 开始执行前
3        /// </summary>
4        public static void Begin()
5        
{
6            Console.WriteLine("begin executing");
7        }
8
9        /// <summary>
10        /// 执行结束
11        /// </summary>
12        public static void End()
13        
{
14            Console.WriteLine("end executed");
15        }
16
17        public static void FirstMiddleware()
18        
{
19            Console.WriteLine("第一个中间件");
20        }
21        public static void SecondMiddleware()
22        
{
23            Console.WriteLine("第二个中间件");
24        }

按照如下顺序执行,并显示结果

 1        static void Main(string[] args)
2        
{
3            //Console.WriteLine("Hello World!");
4            #region 傻瓜式演示中间件执行方法
5            Begin();
6            FirstMiddleware();
7            End();
8            Console.WriteLine("-------------------");
9            Begin();
10            SecondMiddleware();
11            End();
12            #endregion
13        }


如何使用C#在控制台上动态构建中间件管道?


二、封装傻瓜式执行函数

这个Begin() End() 两个方法会执行多次,所以我们可以选择封装一下

 1        /// <summary>
2        /// 封装一个执行方法 减少重复代码
3        /// 入参可以传递一个无参数 无返回值函数 用内置委托接受
4        /// </summary>
5        /// <param name="function"></param>
6        public static void CommonExecute(Action function)
7        
{
8            Begin();
9            function();
10            End();
11        }

如下调用,执行结果和前文一样

1            #region 封装傻瓜式执行函数
2            CommonExecute(FirstMiddleware);
3            Console.WriteLine("-------------------");
4            CommonExecute(SecondMiddleware);
5            #endregion


三、利用lambda变形简化

这里我们需要做个演变,就是将我们要执行的CommonExecute()函数用lambda表达式包裹为另一个函数的参数传递;
于是创建一个ExtraExecute() 函数

 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        }

我们将包裹后的CommonExecute()作为入参传递给ExtraExecute()调用,如下所示:

1            ExtraExecute(() => CommonExecute(FirstMiddleware)); // 执行第一个中间件,先执行ExtraExecute() 再执行CommonExecute()
2            Console.WriteLine("-----------");
3            ExtraExecute(() => CommonExecute(SecondMiddleware)); // 执行第二个中间件


如何使用C#在控制台上动态构建中间件管道?

通过观察发现CommonExecute() 与ExtraExecute()方法签名一样,所以我们可以调换下两者的执行顺序,如下所示:

1            Console.WriteLine("-----------");
2            // 更改中间件调用顺序 先执行CommonExecute() 再执行ExtraExecute()
3            CommonExecute(() => ExtraExecute(FirstMiddleware));




带参创建中间件调用链

我们刚刚演示了无参的中间件调用,现在给中间件执行体增加入参,新增FirstPipe(string msg, Action func) SecondPipe(string msg, Action func) ThirdPipe(string msg, Action func) 三个函数,如下调用,核心原则就是将中间件执行函数包裹成参数传递给下一个执行的函数

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



动态创建中间件管道模型

从带参创建中间件调用链上我们发现,我们无法把这个管道变成动态的,那么如何才能将中间件动态传入以及动态改变调用顺序呢?
通过分析我发现,一个管道执行的最基本单元可以像下面这样定义

 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)// 调用执行中间件函数体
10        }

然后定义三个函数继承 DynamicPipeBase()

 1        /// <summary>
2        /// 第三个中间件
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        /// 第一个中间件
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        }

有了中间件,我们接着只需要构建管道即可,如下所示

 1        /// <summary>
2        /// 存储中间件: List<Type> _types;
3        /// 生成中间件调用链: GeneratePipe
4        /// 构建回调: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<stringGeneratePipe(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<stringBuild()
37            
{
38                return GeneratePipe(0);
39            }
40        }

最终我们在上端调用:

1            #region 动态创建中间件管道模型
2            Action<string> pipeChain = new PipeBuilder(MainAction)
3                .AddPipe(typeof(ThirdSelfPipe)) // 随意添加多个中间件执行单元
4                .AddPipe(typeof(SecondSelfPipe))
5                .AddPipe(typeof(FirstSelfPipe))
6                .Build();
7            pipeChain("Hello World");
8            Console.WriteLine("-------------");
9            #endregion


这样便实现了文章开头展示的效果。