为了将多个管道中间件串联起来,每个中间件需要接收下一个中间件的处理请求的函数作为参数,中间件本身返回一个处理请求的 RequestDelegate 委托对象。所以,中间件实际上是一个生成器函数。
使用 C# 的委托表示出来,就是下面的一个类型。所以,在 ASP.NET Core 中,中间件的类型就是这个 Func<T, TResult>。
Func<RequestDelegate, RequestDelegate>
这个概念比较抽象,与我们所熟悉的面向对象编程方式完全不同,下面我们使用一个示例进行说明。
我们通过一个中间件来演示它的模拟实现代码。下面的代码定义了一个中间件,该中间件接收一个表示后继处理的函数,中间件的返回结果是创建的另外一个 RequestDelegate 对象。它的内部通过调用下一个处理函数来完成中间件之间的级联。
// 定义中间件 Func<RequestDelegate, RequestDelegate> middleware1 = next => { // 中间件返回一个 RequestDelegate 对象 return (HttpContextSample context) => { // 中间件 1 的处理内容 context.Output.AppendLine("Middleware 1 Processing."); // 调用后继的处理函数 return next(context); }; };
把它和我们前面定义的 app 委托结合起来如下所示,注意调用中间件的结果是返回一个新的委托函数对象,它就是我们的处理管道。
// 最终的处理函数 RequestDelegate app = context => { context.Output.AppendLine("End of output."); return Task.CompletedTask; }; // 定义中间件 1 Func<RequestDelegate, RequestDelegate> middleware1 = next => { return (HttpContextSample context) => { // 中间件 1 的处理内容 context.Output.AppendLine("Middleware 1 Processing."); // 调用后继的处理函数 return next(context); }; }; // 得到一个有一个处理步骤的管道 var pipeline1 = middleware1(app); // 准备一个表示当前请求的对象 var context2 = new HttpContextSample(); // 通过管道处理当前请求 pipeline1(context2); // 输出请求的处理结果 Console.WriteLine(context2.Output.ToString());
可以得到如下的输出
Middleware 1 Processing.
End of output.
继续增加第二个中间件来演示多个中间件的级联处理。
RequestDelegate app = context => { context.Output.AppendLine("End of output."); return Task.CompletedTask; }; // 定义中间件 1 Func<RequestDelegate, RequestDelegate> middleware1 = next => { return (HttpContextSample context) => { // 中间件 1 的处理内容 context.Output.AppendLine("Middleware 1 Processing."); // 调用后继的处理函数 return next(context); }; }; // 定义中间件 2 Func<RequestDelegate, RequestDelegate> middleware2 = next => { return (HttpContextSample context) => { // 中间件 2 的处理 context.Output.AppendLine("Middleware 2 Processing."); // 调用后继的处理函数 return next(context); }; }; // 构建处理管道 var step1 = middleware1(app); var pipeline2 = middleware2(step1); // 准备当前的请求对象 var context3 = new HttpContextSample(); // 处理请求 pipeline2(context3); // 输出处理结果 Console.WriteLine(context3.Output.ToString());
当前的输出
Middleware 2 Processing.
Middleware 1 Processing.
End of output.
如果我们把这些中间件保存到几个列表中,就可以通过循环来构建处理管道。下面的示例重复使用了前面定义的 app 变量。
List<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); _components.Add(middleware1); _components.Add(middleware2); // 构建处理管道 foreach (var component in _components) { app = component(app); } // 构建请求上下文对象 var context4 = new HttpContextSample(); // 使用处理管道处理请求 app(context4); // 输出处理结果 Console.WriteLine(context4.Output.ToString());
输出结果与上一示例完全相同
Middleware 2 Processing.
Middleware 1 Processing.
End of output.
但是,有一个问题,我们后加入到列表中的中间件 2 是先执行的,而先加入到列表中的中间件 1 是后执行的。如果希望实际的执行顺序与加入的顺序一致,只需要将这个列表再反转一下即可。
// 反转此列表 _components.Reverse(); foreach (var component in _components) { app = component(app); } var context5 = new HttpContextSample(); app(context5); Console.WriteLine(context5.Output.ToString());
输出结果如下
Middleware 1 Processing.
Middleware 2 Processing.
End of output.
现在,我们可以回到实际的 ASP.NET Core 代码中,把 ASP.NET Core 中 ApplicationBuilder 的核心代码 Build() 方法抽象之后,可以得到如下的关键代码。