ASP.NET Core应用本质上,其实就是由若干个中间件构建成的请求处理管道。管道相当于一个故事的框架,而中间件就相当于故事中的某些情节。同一个故事框架采用不同的情节拼凑,最终会体现出不同风格的故事。而我们的ASP.NET Core应用也正是如此,同一管道采用不同的中间件组合,最终也会呈现出不同的应用形态。

从上述的概念种可以看出,中间件在ASP.NET Core应用有着举足轻重的地位。虽然ASP.NET Core为我们提供了一组丰富的内置中间件,但有些时候我们可能会需要自定义一些中间件,将其穿插到管道中,以便满足我们特定业务场景的需求,所以本文将介绍3种方式来满足自定义中间件的需求。


1.委托形式

在应用程序代码中,我们可以从用于注册中间件的Use方法中看出,所谓管道中的中间件其实就是一种委托类型的对象,这个具体的委托对象体现为Fun<RequestDelegate,RequestDelegate>”。

从Fun<RequestDelegate,RequestDelegate>委托的定义可以看出,该委托类型的入参和返回值都是一个RequestDelegate委托类型的对象。RequestDelegate委托类型其实就是管道在代码中的体现形式,该委托类型承载很多关于请求响应的重要信息,定义如下:

public delegate Task RequestDelegate(HttpContext context);

Fun<RequestDelegate,RequestDelegate>委托中,入参的RequestDelegate对象表示由上一个中间件构建的管道,返回值的RequestDelegate对象表示:将当前中间件基于上一个管道处理后生成的新管道。由于中间件体现为一个Fun<RequestDelegate,RequestDelegate>委托对象,那么这就代表我们可以定义一个与该委托具有一致声明的方法作为自定义中间件的方式。具体的代码实现方式如下:

 1 //创建应用
2 var app = WebApplication.Create(args);
3
4 //转换获得应用建造者
5 IApplicationBuilder appBuilder = app;
6
7 //注册自定义的中间件
8 appBuilder.Use(SayHi);
9
10 //运行应用
11 app.Run();
12
13 //定义为Fun<RequestDelegate,RequestDelegate>类型的方法
14 static RequestDelegate SayHi(RequestDelegate request)
15 => httpContext => httpContext.Response.WriteAsync("Hello");

上面的代码是在一个原始的控制台程序中编写的,并且自行进行了主机应用的构建。在代码中定义了一个和Fun<RequestDelegate,RequestDelegate>委托签名一致的SayHi方法,并以此方法作为中间件进行了引用。虽然这是一个可行的方式,但在实际开发的工作场景中,其实很少会使用委托形式作为自定义中间件的方式。在此处之所以演示这种形式,主要是为了表面中间件本质是一个委托,并且不管通过什么形式去定义中间件,它最终都会体现为一个Fun<RequestDelegate,RequestDelegate>委托对象。


2.强类型中间件

在实际的开发过程中,基本上都会将自定义的中间件定义为一个具体类型,而对于使用强类型的中间件而言,则我们定义的中间件类型必须实现IMiddleware接口。既然通过一个具体类型来定义中间件,类型在使用上则势必会与其他类型产生依赖关联性,那么对于中间件类型中依赖服务的实例化,框架则要求我们使用依赖注入的方式。接下来我们将通过代码示例演示如何定义一个强类型的中间件。

2.1.定义中间件的依赖

下面代码定义的类型是我们预先为中间件类型定义的依赖项,ISeasonTips接口类型的作用主要是,根据不同月份获取对应的季节,并输出对应季节的注意事项,其中SeasonTips类型是接口的默认实现。

    public interface ISeasonTips
{
string Prompt(DateTimeOffset time);
} public class SeasonTips : ISeasonTips
{
//根据不同月份提示季节注意事项
public string Prompt(DateTimeOffset time) => time.Month switch
{
var h when h >= 3 && h <= 5 => "春天到了,早晚温差比较大,要注意别感冒。",
var h when h >= 6 && h <= 8 => "夏天到了,天气炎热,要注意别防嗮。",
var h when h >= 9 && h <= 11 => "秋天到了,天气干燥,要注意多喝水。",
_ => "冬天到了,天气寒冷,要注意防寒保暖。" }; //END Prompt() }

2.2.定义中间件类型

下面的代码中,我们定义了一个名为SeasonMiddleware的中间件类型,并实现IMiddleware接口。该中间件的处理请求的逻辑在InvokeAsync方法中,该方法调用其依赖类型的Prompt方法,根据当前时间获取当前季节的注意事项进行输出。在该调用该方法后,我们还对InvokeAsync的另一个参数:“RequestDelegate类型的委托对象”进行了调用,以便执行管道中的下一个中间件。另外,对于中间件依赖的类型ISeasonTips,我们将其定义在构造函数的参数列表上,以便依赖注入容器提供相应的实例。

 1    /// <summary>
2 /// 强类型中间件
3 /// </summary>
4 public class SeasonMiddleware : IMiddleware
5 {
6 //依赖类型,通过构造函数进行依赖注入
7 private readonly ISeasonTips _seasonTips;
8 public SeasonMiddleware(ISeasonTips seasonTips)
9 {
10 _seasonTips = seasonTips;
11 }
12
13 //调用依赖的“季节提示类型”,根据当前时间获取当前季节的注意事项,并进行响应输出
14 public async Task InvokeAsync(HttpContext context, RequestDelegate next)
15 {
16 await context.Response.WriteAsync(_seasonTips.Prompt(DateTimeOffset.Now));
17
18 //调用管道中的下一个中间件
19 await next(context);
20 } // END InvokeAsync()
21
22 } // END Class

2.3.应用中间件

在下面的代码中我们对自定义的“强类型中间件”进行了应用。由于“强类型中间件”的实例以及依赖都是由依赖注入容器提供的,所以不仅要对依赖的服务进行注册,还要对自身的中间件类型进行服务注册。在服务注册之后,我们使用WebApplication对象的UseMiddleware<SeasonMiddleware>扩展方法,将该中间件添加到应用程序的请求管道中。由于在该中间件后没有其他中间件的处理,所以我们通过调用Run扩展方法注册了管道末端的中间件,以便结束当前请求,将响应输出到客户端。

 1 using dotNet6Demo;
2
3 //创建“应用建造者”
4 var builder = WebApplication.CreateBuilder(args);
5
6 //服务注册
7 builder.Services.AddSingleton<ISeasonTips, SeasonTips>().AddSingleton<SeasonMiddleware>();
8
9 //构建应用
10 var app = builder.Build();
11
12 //引用强类型中间件
13 app.UseMiddleware<SeasonMiddleware>();
14
15 //末端的中间件
16 app.Run(async (context) =>
17 {
18 await context.Response.WriteAsync("请求结束");
19 });
20
21 //运行应用
22 app.Run();

到目前为止,结合本示例以上的3个步骤,启动运行程序就可以验证自定义强类型中间件的效果了。


3.基于约定的中间件

对于ASP.NET的开发者而言,基于约定的编程模式应该不会陌生。例如在ASP.NET MVC框架中,“Action”默认查找视图就有一种基于约定的规则,即“Action”首先会在Views目录中查找与当前“Controller”同名的目录,然后在该目录中查找与“Action”同名的视图文件。这种基于约定的设计方式,在自定义中间件领域也同样使用到了,即基于约定的中间件。

3.1.约定规则

基于约定的中间件它不必像强类型中间件那样,必须实现IMiddleware接口或继承某些基类,它只用按照框架约定的方式定义中间件类型即可,具体的约定规则如下:

  1. 中间件类型必须要定义为一个公共的、可供外界实例化的类型,静态类型无效;
  2. 构造函数的参数中必须包含RequestDelegate类型,如果存在依赖类型则也必须包含在构造函数中;
  3. 必须定义InvokeAsync或Invoke方法,方法签名为:public Task Invoke(HttpContext context);

对以上的约定进行一个补充说明:构造函数的参数列表要包含依赖的类型,是为了依赖注入容器对依赖类型提供实例;RequestDelegate参数具有传递性,表示由后续中间件构建的管道,当前中间件利用它将请求转交给后续管道进行处理。InvokeAsync或Invoke方法主要是代表中间件在管道中处理请求的逻辑。

3.2.应用实现

下面我们在“强类型中间件”示例的基础上,根据约定规则将SeasonMiddleware类型改造为“基于约定的中间件”,代码如下:

 1     /// <summary>
2 /// 基于约定的中间件
3 /// </summary>
4 public class SeasonMiddleware
5 {
6 private readonly ISeasonTips _seasonTips;
7 private readonly RequestDelegate _next;
8
9 public SeasonMiddleware(ISeasonTips seasonTips, RequestDelegate next)
10 {
11 _seasonTips = seasonTips;
12 _next = next;
13 }
14
15 //调用依赖的“季节提示类型”,根据当前时间获取当前季节的注意事项,并进行响应输出
16 public async Task InvokeAsync(HttpContext context)
17 {
18 await context.Response.WriteAsync(_seasonTips.Prompt(DateTimeOffset.Now));
19 //调用管道中的下一个中间件
20 await _next(context);
21
22 } // END InvokeAsync()
23
24 } // END Class

在中间件引用方面,“基于约定的中间件”同样可以使用“app.UseMiddleware<SeasonMiddleware>()”的方式进行引用,但是在此我们介绍一种较为常用的方式,就是将自定义中间件的引用方式进行封装,将其作为IApplicationBuilder类型的扩展方法来使用,扩展方法定义的代码如下:

1     public static class SeasonMiddlewareExtensions
2 {
3 public static IApplicationBuilder UseSeason(this IApplicationBuilder builder)
4 {
5 return builder.UseMiddleware<SeasonMiddleware>();
6 }
7 }

接下来在示例应用方面,将其调整为使用“基于约定中间件”的形式,并使用扩展方法引用中间件。

 1 using dotNet6Demo;
2
3 //创建“应用建造者”
4 var builder = WebApplication.CreateBuilder(args);
5
6 //服务注册
7 builder.Services.AddSingleton<ISeasonTips, SeasonTips>();
8
9 //构建应用
10 var app = builder.Build();
11
12 //通过自定义扩展方法 引用中间件
13 app.UseSeason();
14
15 //末端的中间件
16 app.Run(async (context) =>
17 {
18 await context.Response.WriteAsync("请求结束");
19 });
20
21 //运行应用
22 app.Run();

3.3.提供方式

在对以上中间件应用方面,我们能可以看出“基于约定的中间件”类型并没有进行服务注册,而“强类型中间件”类型却进行了服务注册,这是因为两者在提供实例的方式上有着本质的区别。

“基于约定的中间件”的实例是在应用启动时便可提供的,并且只能指定的一个固定的生命周期模式“Singleton”,所以该类型中间件具有和应用程序一样的生存期,直到应用程序关闭才会释放。

“强类型中间件”的实例并不是在应用启动时提供的,它需要根据服务注册时指定的生命周期,来决定创建提供的时机。例如“强类型中间件”注册的生命周期为“Scoped”,那么依赖注入容器会根据客户端的请求实时创建中间件的实例,请求处理完成后才会被释放。


总结

中间件的使用地位在ASP.NET Core中绝对是毋庸置疑的,那么对于较为复杂的项目而言,自定义中间件的需求绝对是“绕不开的弯”,所以我们必须掌握自定义中间件的方式。

本文介绍了3种可以实现自定义ASP.NET Core中间件的方式。其中第一种并不推崇作为实战运用的手段,其目的是为了让我们明白:中间件最终的体现形式其实就是一个委托对象,该委托对象承载了请求上下信息,并具有传递性。在实际的使用中,我们可以在第二种和第三种中进行选择,也就是“强类型中间件”和“基于约定的中间件”,从两者的特点上来看,“基于约定的中间件”在使用方面会更加的方便,但是其生命周期模式只能局限于Singleton。而“强类型中间件”可以通过服务注册为中间件实例指定任意的生命周期模式,相比更加灵活。

对于具体的选择,我们想我们还是交给我们实际的运用场景。

如果想了解更多关于自定义 ASP.NET Core 中间件的方式,可以访问如下的官方文档:

写入自定义 ASP.NET Core 中间件 | Microsoft Docs

ASP.NET Core自定义中间件的方式的更多相关文章

  1. 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?

    原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...

  2. asp.net core 自定义中间件

    官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1 中间件的定 ...

  3. asp.net core 自定义中间件【以dapper为例】

    在asp.net core开发中.按照国际案例开始.都是先在Nuget安装XXX包.比如我们今天要用到的Dapper nuget里面安装Dapper 1.然后新建一个类文件DapperExtensio ...

  4. asp.net core 自定义中间件和service

    首先新建项目看下main方法: public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel ...

  5. asp.net core 自定义认证方式--请求头认证

    asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...

  6. asp.net core 自定义异常处理中间件

    asp.net core 自定义异常处理中间件 Intro 在 asp.net core 中全局异常处理,有时候可能不能满足我们的需要,可能就需要自己自定义一个中间件处理了,最近遇到一个问题,有一些异 ...

  7. asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密。

    原文:asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密. GitHub demo https://github.com/zhanglilong23/Asp.NetCore. ...

  8. 如何传递参数给ASP.NET Core的中间件(Middleware)

    问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...

  9. asp.net core mvc 中间件之WebpackDevMiddleware

    asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...

随机推荐

  1. Java开发学习(四)----bean的三种实例化方式

    一.环境准备 准备开发环境 创建一个Maven项目 pom.xml添加依赖 resources下添加spring的配置文件applicationContext.xml 最终项目的结构如下:    二. ...

  2. 测试平台系列(97) 完善执行case部分

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节我们讨论了怎么结束一个 ...

  3. react的setState到底是同步还是异步

    在介绍这个问题之前,我们先来看一下一个例子: state = {number:1};componentDidMount(){this.setState({number:3})console.log(t ...

  4. SAP BPC 清除CUBE 中的数据

    原理:先根据模型和查询条件取出数据,然后把金额设置为0,再写回CUBE. 1.获取数据并清空金额 *&--------------------------------------------- ...

  5. UiPath文本操作Get OCR Text的介绍和使用

    一.Get OCR Text操作的介绍 使用OCR屏幕抓取方法从指示的UI元素或图像中提取字符串及其信息.执行屏幕抓取操作时,还可以自动生成此活动以及容器.默认情况下,使用Google OCR引擎. ...

  6. HashMap的实现原理?如何保证HashMap线程安全?

    A:HashMap简单说就是它根据建的hashcode值存储数据的,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历的顺序是不确定的. B:HashMap基于哈希表,底层结构由数组来实 ...

  7. Python实现循环的最快方式,for和while到底谁更强

    写在前面的一些P话: 大家都知道,效率不管是对于工作还是学习都是十分重要的.当然,Python也是需要效率的.众所周知,Python 不是一种执行效率较高的语言.此外在任何语言中,循环都是一种非常消耗 ...

  8. linux配置svn

    1.安装 yum install subversion 2.测试安装是否成功: svnserve --version 3.创建目录并配置 建立版本库目录 mkdir -pv /data/svn/svn ...

  9. idea 查看 类所有方法的快捷键

    idea 查看 类 所有方法的快捷键 Idea:ctrl+F12 Eclipse:Ctrl+O

  10. NC50528 滑动窗口

    NC50528 滑动窗口 题目 题目描述 给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位,如下图: 你的任务是找出窗体在各个位置时的最大值 ...