zoukankan      html  css  js  c++  java
  • 让 .NET 轻松构建中间件模式代码(二)

    让 .NET 轻松构建中间件模式代码(二)--- 支持管道的中断和分支

    Intro

    上次实现了一个基本的构建中间件模式的中间件构建器,现在来丰富一下功能,让它支持中断和分支,分别对应 asp.net core 中的 applicationBuilder.RunapplicationBuilder.MapWhen

    实现管道中断

    实现中间件的中断其实很简单,通过上一次的分析我们已经知道,中间件每一个部分其实是一个上下文和 next 的委托,只需要忽略 next,不执行 next 就可以了,就可以中断后面中间件的执行。

    定义一个 Run 扩展方法来实现方便的实现中间件中断:

    public static IPipelineBuilder<TContext> Run<TContext>(this IPipelineBuilder<TContext> builder, Action<TContext> handler)
    {
        return builder.Use(_ => handler);
    }
    
    public static IAsyncPipelineBuilder<TContext> Run<TContext>(this IAsyncPipelineBuilder<TContext> builder, Func<TContext, Task> handler)
    {
        return builder.Use(_ => handler);
    }
    

    实现分支

    分支的实现主要是参考 asp.net core 里 applicationBuilder.Map/applicationBuilder.MapWhen 实现分支路由的做法,在 asp.net core 里,MapWhen 是一个扩展方法,其实现是一个 MapWhenMiddleware,有兴趣可以看 asp.net core 的源码。

    实现原理也挺简单的,其实就是满足分支的条件时创建一个全新的中间件管道,当满足条件的时候就就执行这个分支中间件管道,否则就跳过这个分支进入下一个中间件。

    首先在 PipelineBuilder 的接口定义中增加了一个 New 方法用来创建一个全新的中间件管道,定义如下:

    public interface IPipelineBuilder<TContext>
    {
        IPipelineBuilder<TContext> Use(Func<Action<TContext>, Action<TContext>> middleware);
    
        Action<TContext> Build();
    
        IPipelineBuilder<TContext> New();
    }
    
    //
    public interface IAsyncPipelineBuilder<TContext>
    {
        IAsyncPipelineBuilder<TContext> Use(Func<Func<TContext, Task>, Func<TContext, Task>> middleware);
    
        Func<TContext, Task> Build();
    
        IAsyncPipelineBuilder<TContext> New();
    }
    

    实现就是直接创建了一个新的 PipelineBuilder<TContext> 对象,示例如下:

    internal class PipelineBuilder<TContext> : IPipelineBuilder<TContext>
    {
        private readonly Action<TContext> _completeFunc;
        private readonly List<Func<Action<TContext>, Action<TContext>>> _pipelines = new List<Func<Action<TContext>, Action<TContext>>>();
    
        public PipelineBuilder(Action<TContext> completeFunc)
        {
            _completeFunc = completeFunc;
        }
    
        public IPipelineBuilder<TContext> Use(Func<Action<TContext>, Action<TContext>> middleware)
        {
            _pipelines.Add(middleware);
            return this;
        }
    
        public Action<TContext> Build()
        {
            var request = _completeFunc;
    
            for (var i = _pipelines.Count - 1; i >= 0; i--)
            {
                var pipeline = _pipelines[i];
                request = pipeline(request);
            }
    
            return request;
        }
    
        public IPipelineBuilder<TContext> New() => new PipelineBuilder<TContext>(_completeFunc);
    }
    

    异步的和同步类似,这里就不再赘述,有疑问可以直接看文末的源码链接

    接着就可以定义我们的分支扩展了

    public static IPipelineBuilder<TContext> When<TContext>(this IPipelineBuilder<TContext> builder, Func<TContext, bool> predict, Action<IPipelineBuilder<TContext>> configureAction)
    {
        return builder.Use((context, next) =>
        {
            if (predict.Invoke(context))
            {
                var branchPipelineBuilder = builder.New();
                configureAction(branchPipelineBuilder);
                var branchPipeline = branchPipelineBuilder.Build();
                branchPipeline.Invoke(context);
            }
            else
            {
                next();
            }
        });
    }
    
    

    使用示例

    我们可以使用分支和中断来改造一下昨天的示例,改造完的示例如下:

    var requestContext = new RequestContext()
    {
        RequesterName = "Kangkang",
        Hour = 12,
    };
    
    var builder = PipelineBuilder.Create<RequestContext>(context =>
            {
                Console.WriteLine($"{context.RequesterName} {context.Hour}h apply failed");
            })
            .When(context => context.Hour <= 2, pipeline =>
                    {
                        pipeline.Use((context, next) =>
                        {
                            Console.WriteLine("This should be invoked");
                            next();
                        });
                        pipeline.Run(context => Console.WriteLine("pass 1"));
                        pipeline.Use((context, next) =>
                        {
                            Console.WriteLine("This should not be invoked");
                            next();
                            Console.WriteLine("will this invoke?");
                        });
                    })
            .When(context => context.Hour <= 4, pipeline =>
                {
                    pipeline.Run(context => Console.WriteLine("pass 2"));
                })
            .When(context => context.Hour <= 6, pipeline =>
                {
                    pipeline.Run(context => Console.WriteLine("pass 3"));
                })
    
        ;
    var requestPipeline = builder.Build();
    Console.WriteLine();
    foreach (var i in Enumerable.Range(1, 8))
    {
        Console.WriteLine($"--------- h:{i} apply Pipeline------------------");
        requestContext.Hour = i;
        requestPipeline.Invoke(requestContext);
        Console.WriteLine("----------------------------");
    }
    

    输出结果如下:

    看输出结果我们可以看到 Run 后面注册的中间件是不会执行的,Run 前面注册的中间件正常执行

    然后定义的 When 分支也是正确执行的~~

    Reference

  • 相关阅读:
    【Scheme归纳】3 比较do, let, loop
    【Scheme归纳】2 算数运算
    【Scheme归纳】1 使用Edwin
    【SICP练习】106 练习3.7
    【SICP练习】105 练习3.5-3.6
    【SICP练习】104 练习3.1-3.4
    【SICP练习】103 练习2.81-2.97
    【SICP练习】102 练习2.79-2.80
    【SICP练习】101 练习2.77-2.78
    【SICP练习】100 练习2.76
  • 原文地址:https://www.cnblogs.com/weihanli/p/12709603.html
Copyright © 2011-2022 走看看