• 让 .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

  • 相关阅读:
    POJ 2923 Relocation (状态压缩,01背包)
    HDU 2126 Buy the souvenirs (01背包,输出方案数)
    hdu 2639 Bone Collector II (01背包,求第k优解)
    UVA 562 Dividing coins (01背包)
    POJ 3437 Tree Grafting
    Light OJ 1095 Arrange the Numbers(容斥)
    BZOJ 1560 火星藏宝图(DP)
    POJ 3675 Telescope
    POJ 2986 A Triangle and a Circle
    BZOJ 1040 骑士
  • 原文地址:https://www.cnblogs.com/weihanli/p/12709603.html
Copyright © 2020-2023  润新知