让 .NET 轻松构建中间件模式代码(二)

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

Intro

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

实现管道中断

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

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

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 的源码。app

实现原理也挺简单的,其实就是知足分支的条件时建立一个全新的中间件管道,当知足条件的时候就就执行这个分支中间件管道,不然就跳过这个分支进入下一个中间件。asp.net

首先在 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> 对象,示例以下:ui

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);
}

异步的和同步相似,这里就再也不赘述,有疑问能够直接看文末的源码连接this

接着就能够定义咱们的分支扩展了.net

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();
        }
    });
}

使用示例

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

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

相关文章
相关标签/搜索