讓 .NET 輕鬆構建中間件模式代碼(二) 支持管道的中斷和分支 Intro 上次實現了一個基本的構建中間件模式的中間件構建器,現在來豐富一下功能,讓它支持中斷和分支,分別對應 asp.net core 中的 和 實現管道中斷 實現中間件的中斷其實很簡單,通過上一次的分析我們已經知道,中間件每一個部 ...
讓 .NET 輕鬆構建中間件模式代碼(二)--- 支持管道的中斷和分支
Intro
上次實現了一個基本的構建中間件模式的中間件構建器,現在來豐富一下功能,讓它支持中斷和分支,分別對應 asp.net core 中的 applicationBuilder.Run
和 applicationBuilder.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