中间件基础:

在.net6.0在请求在响应给请求者之前会通过请求管道再处理服务端的逻辑然后再响应给请求者,而请求管道则是由一系列中间件组成的有点类似于过滤器,为了更直观的了解,我们请看下图:

 它可以决定是否将请求传递给请求管道中下一个中间件,也可以处理下一个中间件之前的逻辑也可以处理下一个中间件之后的逻辑.

此为中间件的注册是有顺序的,在定义时一定要注意.

中间件与过滤器的区别:

Filter是延续ASP.NET MVC的产物,同样保留了五种的Filter,分别是Authorization Filter、Resource Filter、Action Filter、Exception Filter及Result Filter。具体你可以去查找一下关于ASP.NET CORE Filter的相关文章

根据描述,可以看出中间件和过滤器的功能类似,那么他们有什么区别?为什么又要搞一个中间件呢?其实,过滤器和中间件他们的关注点是不一样的,也就是说职责不一样,干的事情就不一样。

同作为两个AOP利器,Filter(过滤器)更贴合业务,它关注于应用程序本身,比如你看ActionFilter 和 ResultFilter,它都直接和你的Action,ActionResult交互了,是不是离你很近的感觉,那我有一些比如对我的输出结果进行格式化,对我的请求的ViewModel进行数据验证啦,肯定就是用Filter无疑了。它是MVC的一部分,它可以拦截到你Action上下文的一些信息,而中间件是没有这个能力的。

可以看到,每一个中间件都都可以在请求之前和之后进行操作。请求处理完成之后传递给下一个请求

使用场景:

那么,何时使用中间件呢?我的理解是在我们的应用程序当中和业务关系不大的一些需要在管道中做的事情可以使用,比如身份验证,Session存储,日志记录等。其实我们的 Asp.net core项目中本身已经包含了很多个中间件。比如 身份认证中间件 UseAuthorization()等系列

中间件管道:

Run: Run称为终端中间件,也就是说该中间件就是管道的末尾,该中间件注册之后,后面的中间件将不再执行,基本上放在末尾就行了,如下图:

 Use:通过该方法可以快速注册一个匿名中间件,如下图:

 需要注意的是:

1.如果要将请求发送给管道中下一个中间件时,一定记得调用next(),否则会导致管道短路,后面的中间件不再被执行

2.在中间件中如有respance的操作,千万不要调用next(),也不要对respanse进行任何更改,否则会抛出异常.

UseWhen:

该方法可以针对不同逻辑创建管道分支,拥有子级中间件,,支持嵌套,其实这里和if else 同理,如下图:

 最后输出:

UseWhen:Use
Use
Run

Map: 

针对不同请求路径创建管道分支,需要注意的是:一旦进入管道分支则不再回到主管道,如下图:

当你访问 /get/user 时,输出如下:

Map get: Use
Request Path: /user
Request PathBase: /get
Map get: Run

当你访问 /post/user/student/1 时,则只会进入下面的map 分支 其他不进入,具体场景交给你们自己尝试哦

MapWhen:

与map类似,也是一旦进入分支则不会再回到主管道,只不过MapWhen不是基于路径而是基于条件,也支持嵌套如下图:

 当你访问 /get/user 时,输出如下:

MapWhen get user: Use

基于约定的中间件:

"约定大于配置",有以下几点:

1.拥有public构造函数,且该构造函数中至少包含一个类型为RequestDeleGet的参数

2.拥有名为Invoke或InvokeAsync的public方法,该方法必须包含类型为HttpContext 的参数,且该参数必须位于第一个位置,另外返回值必须为Task类型

3.在构造函数中的其他参数可以通过依赖注入(DI)填充,也可以通过UseMiddleware进行传参填充

,通过DI填充时,只能接收 Transient 和 Singleton 的DI参数。这是由于中间件是在应用启动时构造的(而不是按请求构造),所以当出现 Scoped 参数时,构造函数内的DI参数生命周期与其他不共享,如果想要共享,则必须将Scoped DI参数添加到Invoke/InvokeAsync来进行使用。通过UseMiddleware传参时,构造函数内的DI参数和非DI参数顺序没有要求,传入UseMiddleware内的参数顺序也没有要求,但是我建议将非DI参数放到前面,DI参数放到后面。(这一块感觉微软做的好牛皮)Invoke/InvokeAsync的其他参数也能够通过依赖注入(DI)填充,可以接收 Transient、Scoped 和 Singleton 的DI参数。

示例如下:

 然后,可以通过UseMiddleware方法将其添加到管道中

 基于工厂的中间件:

优势:

1.上面基于约定的中间件是单例的,但是基于工厂的中间件,可以再依赖注入时设置中间件实例生命周期.

2.使中间件强化了类型,因为实现了IMiddleware接口

该方式的实现基于IMiddlewareFactoryIMiddleware。先来看一下接口定义:

 你有没有想过当我们调用UseMiddleware时,它是如何工作的,事实上UseMiddleware扩展方法会先检查中间件是否实现了IMiddleware接口。

如果实现了,则使用容器中注册的IMiddlewareFactory实例来解析IMiddleware的实例("这下你应该知道为什么称为工厂中间件了吧"),如果没实现

则使用基于约定的中间件逻辑来激活

注意,基于工厂的中间件,在应用的服务容器中一般注册为 Scoped 或 Transient 服务

这样的话,咱们就可以放心的将 Scoped 服务注入到中间件的构造函数中了。

接下来,咱们就来实现一个基于工厂的中间件:

 接下来,注入服务并注册使用中间件:

基于约定的中间件 VS 基于工厂的中间件

  • 基于约定的中间件实例都是 Singleton;而基于工厂的中间件实例可以是 Singleton、Scoped 和 Transient(当然,不建议注册为 Singleton)
  • 基于约定的中间件实例构造函数中可以通过依赖注入传参,也可以用过UseMiddleware传参;而基于工厂的中间件只能通过依赖注入传参
  • 基于约定的中间件实例可以在Invoke/InvokeAsync中添加更多的依赖注入参数;而基于工厂的中间件只能按照IMiddleware的接口定义进行实现。