Let .NET easily build middleware pattern code (2) --- support pipeline interruption and branching
Intro#
Last implements a basic building middleware middleware model builder, now look rich functionality, it supports interrupt and branches, respectively in asp.net core applicationBuilder.Run
and applicationBuilder.MapWhen
Implement pipeline interruption #
Realizing the interruption of middleware is actually very simple. Through the last analysis, we already know that each part of the middleware is actually a context and next
a delegate. It only needs to be ignored next
and not executed next
, and the execution of the middleware can be interrupted.
Define an Run
extension method to implement the middleware interrupt conveniently:
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); }
Implement branch #
The implementation of the branch is mainly based on the practice of asp.net core applicationBuilder.Map
/ applicationBuilder.MapWhen
implementation of branch routing. In asp.net core, it MapWhen
is an extension method, and its implementation is one MapWhenMiddleware
. If you are interested, you can see the source code of asp.net core.
The implementation principle is also quite simple. In fact, a brand new middleware pipeline is created when the condition of the branch is met. When the condition is met, the branch middleware pipeline is executed, otherwise the branch is skipped and the next middleware is skipped.
First PipelineBuilder
, a New
method was added to the interface definition to create a brand new middleware pipeline, which is defined as follows:
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(); }
The realization is to directly create a new PipelineBuilder<TContext>
object, examples are as follows:
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); }
Asynchronous is similar to synchronous, so I wo n’t go into details here. If you have any questions, you can directly see the source link at the end of the article.
Then we can define our branch extension
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(); } }); }
Example of use #
We can use branches and interrupts to transform yesterday's example. The modified example is as follows:
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("----------------------------"); }
The output is as follows:
Looking at the output, we can see that Run
the middleware registered later will not be executed, and Run
the middleware registered before will execute normally
Then the defined When
branch is also executed correctly ~~