您现在的位置是:网站首页> 编程资料编程资料
ASP.NET Core处理管道的深入理解_实用技巧_
2023-05-24
390人已围观
简介 ASP.NET Core处理管道的深入理解_实用技巧_
前言
在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式。这导致代码的逻辑大大简化,但是,对于熟悉面向对象编程,而不是函数式编程思路的开发者来说,是一个比较大的挑战。
处理请求的函数
在 ASP.NET Core 中,一次请求的完整表示是通过一个 HttpContext 对象来完成的,通过其 Request 属性可以获取当前请求的全部信息,通过 Response 可以获取对响应内容进行设置。
对于一次请求的处理可以看成一个函数,函数的处理参数就是这个 HttpContext 对象,处理的结果并不是输出结果,结果是通过 Response 来完成的,从程序调度的角度来看,函数的输出结果是一个任务 Task。
这样的话,具体处理 Http 请求的函数可以使用如下的 RequestDelegate 委托进行定义。
public delegate Task RequestDelegate(HttpContext context);
在函数参数 HttpContext 中则提供了此次请求的所有信息,context 的 Request 属性中提供了所有关于该次请求的信息,而处理的结果则在 context 的 Response 中表示。通常我们会修改 Response 的响应头,或者响应内容来表达处理的结果。
需要注意的是,该函数的返回结果是一个 Task,表示异步处理,而不是真正处理的结果。
参见:在 Doc 中查看 RequestDelegate 定义
我们从 ASP.NET Core 的源代码中选取一段作为参考,这就是在没有我们自定义的处理时,ASP.NET Core 最终的处理方式,返回 404。这里使用函数式定义。
RequestDelegate app = context => { // ...... context.Response.StatusCode = StatusCodes.Status404NotFound; return Task.CompletedTask; }; 来源:在 GitHub 中查看 ApplicationBuilder 源码
把它翻译成熟悉的方法形式,就是下面这个样子:
public Task app(HttpContext context) { // ...... context.Response.StatusCode = StatusCodes.Status404NotFound; return Task.CompletedTask; }; 这段代码只是设置了 Http 的响应状态码为 404,并直接返回了一个已经完成的任务对象。
为了脱离 ASP.NET Core 复杂的环境,可以简单地进行后继的演示,我们自定义一个模拟 HttpContext 的类型 HttpContextSample 和相应的 RequestDelegate 委托类型。
在模拟请求的 HttpContextSample 中,我们内部定义了一个 StringBuilder 来保存处理的结果,以便进行检查。其中的 Output 用来模拟 Response 来处理输出。
而 RequestDelegate 则需要支持现在的 HttpContextSample。
using System.Threading.Tasks; using System.Text; public class HttpContextSample { public StringBuilder Output { get; set; } public HttpContextSample() { Output = new StringBuilder(); } } public delegate Task RequestDelegate(HttpContextSample context); 这样,我们可以定义一个基础的,使用 RequestDelegate 的示例代码。
// 定义一个表示处理请求的委托对象 RequestDelegate app = context => { context.Output.AppendLine("End of output."); return Task.CompletedTask; }; // 创建模拟当前请求的对象 var context1 = new HttpContextSample(); // 处理请求 app(context1); // 输出请求的处理结果 Console.WriteLine(context1.Output.ToString()); 执行之后,可以得到如下的输出
End of output.
处理管道中间件
所谓的处理管道是使用多个中间件串联起来实现的。每个中间件当然需要提供处理请求的 RequestDelegate 支持。在请求处理管道中,通常会有多个中间件串联起来,构成处理管道。

但是,如何将多个中间件串联起来呢?
可以考虑两种实现方式:函数式和方法式。
方法式就是再通过另外的方法将注册的中间件组织起来,构建一个处理管道,以后通过调用该方法来实现管道。而函数式是将整个处理管道看成一个高阶函数,以后通过调用该函数来实现管道。
方法式的问题是在后继中间件处理之前需要一个方法,后继中间件处理之后需要一个方法,这就是为什么 ASP.NET Web Form 有那么多事件的原因。
如果我们只是把后继的中间件中的处理看成一个函数,那么,每个中间件只需要分成 3 步即可:
- 前置处理
- 调用后继的中间件
- 后置处理
在 ASP.NET Core 中是使用函数式来实现请求的处理管道的。
在函数式编程中,函数本身是可以作为一个参数来进行传递的。这样可以实现高阶函数。也就是说函数的组合结果还是一个函数。
对于整个处理管道,我们最终希望得到的形式还是一个 RequestDelegate,也就是一个对当前请求的 HttpContext 进行处理的函数。
本质上来讲,中间件就是一个用来生成 RequestDelegate 对象的生成函数。
为了将多个管道中间件串联起来,每个中间件需要接收下一个中间件的处理请求的函数作为参数,中间件本身返回一个处理请求的 RequestDelegate 委托对象。所以,中间件实际上是一个生成器函数。
使用 C# 的委托表示出来,就是下面的一个类型。所以,在 ASP.NET Core 中,中间件的类型就是这个 Func
Func
这个概念比较抽象,与我们所熟悉的面向对象编程方式完全不同,下面我们使用一个示例进行说明。
我们通过一个中间件来演示它的模拟实现代码。下面的代码定义了一个中间件,该中间件接收一个表示后继处理的函数,中间件的返回结果是创建的另外一个 RequestDelegate 对象。它的内部通过调用下一个处理函数来完成中间件之间的级联。
// 定义中间件 Funcmiddleware1 = 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 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 middleware1 = next => { return (HttpContextSample context) => { // 中间件 1 的处理内容 context.Output.AppendLine("Middleware 1 Processing."); // 调用后继的处理函数 return next(context); }; }; // 定义中间件 2 Func 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> _components = new List >(); _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() 方法抽象之后,可以得到如下的关键代码。
注意 Build() 方法就是构建我们的请求处理管道,它返回了一个 RequestDelegate 对象,该对象实际上是一个委托对象,代表了一个处理当前请求的处理管道函数,它就是我们所谓的处理管道,以后我们将通过该委托来处理请求。
public RequestDelegate Build() { RequestDelegate app = context => { // ...... context.Response.StatusCode = StatusCodes.Status404NotFound; return Task.CompletedTask; }; foreach (var component in _components.Reverse()) { app = component(app); } return app; } 完整的 ApplicationBuilder 代码如下所示:
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Builder { public class ApplicationBuilder : IApplicationBuilder { private const string ServerFeaturesKey = "server.Features"; private const string ApplicationServicesKey = "application.Services"; private readonly IList> _components = new List>(); public ApplicationBuilder(IServiceProvider serviceProvider) { Properties = new Dictionary(StringComparer.Ordinal); ApplicationServices = serviceProvider; } public ApplicationBuilder(IServiceProvider serviceProvider, object server) : this(serviceProvider) { SetProperty(ServerFeaturesKey, server); } private ApplicationBuilder(ApplicationBuilder builder) { Properties = new CopyOnWriteDictionary(builder.Properties, StringComparer.Ordinal); } public IServiceProvider ApplicationServices { get { return GetProperty(ApplicationServicesKey)!; } set { SetProperty(ApplicationServicesKey, value); } } public IFeatureCollection ServerFeatures { get { return GetProperty(ServerFeaturesKey)!; } } public IDictionary Properties { get; } private T? GetProperty(string key) { return Properties.TryGetValue(key, out var value) ? (T)value : default(T); } private void SetProperty(string key, T value) { Properties[key] = value; } public IApplicationBuilder Use(Func middleware) { _components.Add(middleware); return this; } public IApplicationBuilder New() { return new ApplicationBuilder(this); } public RequestDelegate Build() { RequestDelega
相关内容
- .net core中的Authorization过滤器使用_实用技巧_
- asp.net core 使用 TestServer 来做集成测试的方法_实用技巧_
- 详解ASP.NET Core 中基于工厂的中间件激活的实现方法_实用技巧_
- .NET必知的EventCounters性能指标监视器详解_实用技巧_
- 在ASP.NET Core5.0中访问HttpContext的方法步骤_实用技巧_
- ASP.NET Core3.1 Ocelot负载均衡的实现_实用技巧_
- ASP.NET Core3.1 Ocelot认证的实现_实用技巧_
- ASP.NET Core3.1 Ocelot路由的实现_实用技巧_
- .NET Core 源码编译的问题解析_实用技巧_
- 如何在ASP.Net Core中使用Serilog_实用技巧_
点击排行
本栏推荐
