1. 程式人生 > >讓 .NET 輕鬆構建中介軟體模式程式碼(二)

讓 .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

  • https://www.cnblogs.com/weihanli/p/12700006.html
  • https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/DotNetCoreSample/PipelineTest.cs
  • https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Helpers/Pipelines/PipelineBuilder.cs
  • https://github.com/dotnet/aspnetcore/blob/master/src/Http/Http/src/Builder/ApplicationBuilder.cs
  • https://github.com/dotnet/aspnetcore/blob/master/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs
  • https://github.com/dotnet/aspnetcore/blob/master/src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs