一、什么是中间件

我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中。而中间件就是用于组成应用程序管道来处理请求和响应的组件。管道内的每一个组件都可以选择是否将请求转交给下一个组件,并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个HTTP请求。

中间件可以认为有两个基本的职责:

  1. 选择是否将请求传递给管道中的下一个中间件。
  2. 可以在管道中的下一个中间件前后执行一些工作。

请求委托通过使用IApplicationBuilder类型的Run、Map以及Use扩展方法来配置,并在Startup类中传给Configure方法。每个单独的请求委托都可以被指定为一个内嵌匿名方法,或其定义在一个可重用的类中。这些可以重用的类被称作“中间件”或“中间件组件”。每个位于请求管道内的中间件组件负责调用管道中下一个组件,或适时短路调用链。中间件是一个典型的AOP应用。

ASP.NET Core请求管道由一系列的请求委托所构成,它们一个接着一个的被调用,看下面一张微软官方的中间件请求管道图(图中执行线程按黑色箭头的顺序执行):

中间件短路:每一个委托在下一个委托之前和之后都有机会执行操作。任何委托都能选择停止传递到下一个委托,而是结束请求并开始响应,这就是请求管道的短路,这是一种有意义的设计,因为它可以避免一些不必要的工作。比如说,一个授权(authorization)中间件只有在通过身份验证之后才能调用下一个委托,否则它就会被短路,并返回“Not Authorized”的响应。异常处理委托需要在管道的早期被调用,这样它们就能够捕捉到发生在管道内更深层次出现的异常了。短路可以用下面这张图来表示:

在上图中,我们可以把中间件1认为是身份认证的中间件,HTTP请求发送过来,首先经过身份认证中间件,如果身份认证失败,那么就直接给出响应并返回,不会再把请求传递给下面的中间件2和中间件3.

中间件的执行跟调用的顺序有关,然后在响应时则以相反的顺序返回。

请求在每一步都可能被短路,所以我们要以正确的顺序添加中间件,如异常处理中间件,我们要添加在最开始的地方,这样就能第一时间捕获异常,以及后续中间可能发生的异常,然后最终做处理返回。

我们来看看Configure方法里面提供了哪些中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
// 异常中间件
app.UseDeveloperExceptionPage();
} // 路由中间件
app.UseRouting();
// 授权中间件
app.UseAuthorization();
// 终结点中间件
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

1、中间件和过滤器的区别

中间件和过滤器都是一种AOP的思想,他们的功能类似,那么他们有什么区别呢?

  1. 过滤器更加贴合业务,它关注于应用程序本身,关注的是如何实现业务,比如对输出结果进行格式化,对请求的ViewModel进行数据校验,这时就肯定要使用过滤器了。过滤器是MVC的一部分,它可以拦截到你Action上下文的一些信息,而中间件是没有这个能力的。可以认为过滤器是附加性的一种功能,它只是中间件附带表现出来的特征。
  2. 中间件是管道模型里重要的组成部分,不可或缺,而过滤器可以没有。

二、中间件常用方法

中间件中定义了Run、Use、Map、MapWhen几种方法,我们下面一一讲解这几种方法。

1、Run方法

我们先来看到Run()方法的定义:

中定义中可以看出:Run()方法中只有一个RequestDelegate委托类型的参数,没有Next参数,所以Run()方法也叫终端中间件,不会将请求传递给下一个中间件,也就是发生了“短路”。看下面的代码:

// Run方法向应用程序的请求管道中添加一个RequestDelegate委托
// 放在管道最后面,终端中间件
app.Run(handler: async context =>
{
await context.Response.WriteAsync(text: "Hello World1\r\n");
});
app.Run(handler: async context =>
{
await context.Response.WriteAsync(text: "Hello World2\r\n");
});

程序运行结果:

可以看到:只输出了中间件1的信息,没有输出中间件2的信息,说明发生了短路。

注意:Run()方法被称为终端中间件,要放在所有中间件的最后面,否则在Run()方法后面的中间件将不会被执行。

2、Use方法

我们先来看看Use()方法的定义:

可以看出:Use方法的参数是一个Func委托,输入参数是一个RequestDelegate类型的委托,返回参数也是一个RequestDelegate类型的委托,这里表示调用下一个中间件,我们在来看看RequestDelegate委托的定义:

可以看出:RequestDelegate是一个委托,有一个HttpContext类型的参数,HttPContext表示Http请求上下文,可以获取请求信息,返回值是Task类型,明白了Use()方法的参数以后,我们写一个自定义的Use()方法:

// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。
// context参数是HttpContext,表示HTTP请求的上下文对象
// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路
// 用Use可以将多个中间件链接在一起
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(text: "hello Use1\r\n");
// 调用下一个委托
await next();
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(text: "hello Use2\r\n");
// 调用下一个委托
await next();
});

程序运行结果:

我们在上面说过,可以在调用中间件之前和之后做一些工作,看下面的代码:

// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。
// context参数是HttpContext,表示HTTP请求的上下文对象
// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路
// 用Use可以将多个中间件链接在一起
app.Use(async (context, next) =>
{
// 解决中文乱码问题
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync(text: "中间件1:传入请求\r\n");
// 调用下一个委托
await next();
await context.Response.WriteAsync(text: "中间件1:传出响应\r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(text: "中间件2:传入请求\r\n");
// 调用下一个委托
await next();
await context.Response.WriteAsync(text: "中间件2:传出响应\r\n");
});
app.Run(handler:async context =>
{
await context.Response.WriteAsync(text: "中间件3:处理请求并生成响应\r\n");
});

程序运行结果:

我们可以总结上面代码的执行顺序:

  1. 请求先到达中间件1,然后输出(中间件1:传入请求)
  2. 然后中间件1调用next()。next()会调用管道中的中间件2。
  3. 中间件2输出(中间件2:传入请求)。
  4. 然后中间件2会调用next()。next()在调用管道中的中间件3。
  5. 中间件3处理请求并生成响应,不在调用下一个中间件,所以我们看到输出(中间件3:处理请求并生成响应)。
  6. 这时管理开始发生逆转。
  7. 此时控制器将交回到中间件2,并将中间件3生成的响应传递给它。中间件2输出(中间件2:传出响应)。
  8. 最后,中间件2在将控制权交给中间件1。
  9. 中间件1最后输出(中间件1:传出响应),这就是我们最后看的的结果。

我们知道:Use()方法中有两个参数,next参数表示调用管道中的下一个中间件,如果不调用next,那么也会使管道发生短路,相当于Run()方法,看下面的代码:

// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。
// context参数是HttpContext,表示HTTP请求的上下文对象
// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路
// 用Use可以将多个中间件链接在一起
app.Use(async (context, next) =>
{
// 解决中文乱码问题
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync(text: "中间件1:传入请求\r\n");
// 调用下一个委托
await next();
await context.Response.WriteAsync(text: "中间件1:传出响应\r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(text: "中间件2:传入请求\r\n");
// 调用下一个委托
await next();
await context.Response.WriteAsync(text: "中间件2:传出响应\r\n");
});
//app.Run(handler:async context =>
//{
// await context.Response.WriteAsync(text: "中间件3:处理请求并生成响应\r\n");
//});
// Use方法也可以不调用next,表示发生短路
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(text: "中间件3:处理请求并生成响应\r\n");
});

程序运行结果:

可以看出:如果使用Use()方法,不调用next,实现的效果跟使用Run()方法一样,都会使管道发生短路。

3、Map方法

Map作为惯例,将管道分流。Map根据给定请求路径匹配将请求管道分流。如果请求路径以指定路径开始,则执行分支。看一下Map()方法的定义:

可以看到Map方法有两个参数:第一个参数是匹配规则,第二个参数是Action泛型委托,泛型委托参数是IApplicationBuilder类型,和Configure方法的第一个参数类型相同 。这就表示可以把实现了Action泛型委托的方法添加到中间件管道中执行。

我们首先定义一个方法,该方法的参数是IApplicationBuilder类型:

/// <summary>
/// 自定义方法
/// </summary>
/// <param name="app">IApplicationBuilder</param>
private void HandleMap1(IApplicationBuilder app)
{
app.Run(handler: async context =>
{
await context.Response.WriteAsync(text: "Hello Map1");
});
} /// <summary>
/// 自定义方法
/// </summary>
/// <param name="app">IApplicationBuilder</param>
private void HandleMap2(IApplicationBuilder app)
{
app.Run(handler: async context =>
{
await context.Response.WriteAsync(text: "Hello Map2");
});
}

然后看一下使用Map方法的代码:

// Map可以根据匹配的URL来选择执行,简单来说就是根据URL进行分支选择执行
// 有点类似于MVC中的路由
// 匹配的URL:http://localhost:5000/Map1
app.Map(pathMatch: "/Map1", configuration: HandleMap1);
// 匹配的URL:http://localhost:5000/Map2
app.Map(pathMatch: "/Map2", configuration: HandleMap2);

运行程序,然后在浏览器地址栏里面输入:http://localhost:5000/Map1,输出结果:

在地址栏里面在输入:http://localhost:5000/Map2,输出结果:

Map还支持嵌套,看下面的代码:

// 嵌套Map
app.Map(pathMatch: "/Map1", configuration: App1 =>
{
//
App1.Map("/Map2",action=>
{
action.Run(async context =>
{
await context.Response.WriteAsync("This is /Map1/Map2");
});
});
App1.Run(async context =>
{
await context.Response.WriteAsync("This is no-map");
});
});

访问http://localhost:5000/Map1/123输出结果:

访问http://localhost:5000/Map1输出结果:

访问http://localhost:5000/Map1/Map2输出结果:

Map也可以同时匹配多个段,看下面的代码:

运行程序,输出结果:

访问http://localhost:5000/Map1/Map2输出结果:

4、Mapwhen方法

MapWhen是基于给定的谓词分支请求管道。任何使Func<HttpContext,bool>返回true的谓词的请求都被映射到新的管道分支。

我们先来看看Mapwhen方法的定义:

可以看出:MapWhen方法有两个参数:第一个参数是Func类型的委托,输入参数是HttpContext,输出参数是bool类型。第二个参数是Action委托,参数是IApplicationBuilder类型,表示也可以把实现Action委托的方法添加到中间件管道中执行。

看下面的例子,如果url中包括name查询参数,则执行HandleName方法,如果包含age查询参数,则执行HandleAge方法,否则执行Run()方法。

HandleName和HandleAge方法定义如下:

private void HandleName(IApplicationBuilder app)
{
app.Run(handler: async context =>
{
await context.Response.WriteAsync(text: $"This name is: {context.Request.Query["name"]}");
});
} private void HandleAge(IApplicationBuilder app)
{
app.Run(handler: async context =>
{
await context.Response.WriteAsync(text: $"This age is: {context.Request.Query["age"]}");
});
}

对应的MapWhen方法定义如下:

// 如果访问的url参数中包含name,则执行HandleName
app.MapWhen(
// Func委托,输入参数是HttpContext,返回bool值
predicate: context =>
{
// 判断url参数中是否包含name
return context.Request.Query.ContainsKey("name");
}, configuration: HandleName); // 如果访问的url参数中包含name,则执行HandleAge
app.MapWhen(
// Func委托,输入参数是HttpContext,返回bool值
predicate: context =>
{
// 判断url参数中是否包含age
return context.Request.Query.ContainsKey("age");
}, configuration: HandleAge); app.Run(async context =>
{
await context.Response.WriteAsync("There is non-Map delegate \r\n");
});

运行程序,输出结果:

在url里面添加name查询参数输出结果:

在url里面添加age查询参数输出结果:

三、自定义中间件

在上面的例子中,我们都是使用的官方中间件自动的方法,其实我们也可以自己编写一个中间件。

中间件遵循显示依赖原则,并在其构造函数中暴露所有依赖项。中间件能够利用UseMiddleware<T>扩展方法的优势,直接通过它们的构造函数注入服务。依赖注入服务是自动完成填充的。

ASP.NET Core约定中间件类必须包括以下内容:

  1. 具有类型为RequestDelegate参数的公共构造函数。
  2. 必须有名为Invoke或InvokeAsync的公共方法,此方法必须满足两个条件:方法返回类型是Task、方法的第一个参数必须是HttpContext类型。

我们自定义一个记录IP的中间件,新建一个类RequestIPMiddleware,代码如下:

using Microsoft.AspNetCore.Http;
using System.Threading.Tasks; namespace MiddlewareDemo.Middleware
{
/// <summary>
/// 记录IP地址的中间件
/// </summary>
public class RequestIPMiddleware
{
// 私有字段
private readonly RequestDelegate _next; /// <summary>
/// 公共构造函数,参数是RequestDelegate类型
/// 通过构造函数进行注入,依赖注入服务会自动完成注入
/// </summary>
/// <param name="next"></param>
public RequestIPMiddleware(RequestDelegate next)
{
_next = next;
} /// <summary>
/// Invoke方法
/// 返回值是Task,参数类型是HttpContext
/// </summary>
/// <param name="context">Http上下文</param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync($"User IP:{context.Connection.RemoteIpAddress.ToString()}\r\n");
// 调用管道中的下一个委托
await _next.Invoke(context);
}
}
}

然后创建一个扩展方法,对IApplicationBuilder进行扩展:

using Microsoft.AspNetCore.Builder;

namespace MiddlewareDemo.Middleware
{
public static class RequestIPExtensions
{
/// <summary>
/// 扩展方法,对IApplicationBuilder进行扩展
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder)
{
// UseMiddleware<T>
return builder.UseMiddleware<RequestIPMiddleware>();
}
}
}

最后在Startup类的Configure方法中使用自定义中间件:

// 使用自定义中间件
app.UseRequestIP();

运行程序,查看结果:

这样就完成了一个自定义中间件。

四、官方常用中间件

1、异常处理中间件

当应用程序在开发环境中运行时,开发人员异常页中间件( UseDeveloperExceptionPage )报告应用程序运行时的错误。

当应用程序在生产环境中运行时,异常处理中间件( UseExceptionHandler )捕获下面中间件中引发的异常。

2、HTTPS重定向中间件

HTTPS重定向中间件( UseHttpsRedirection )会将HTTP请求重定向到HTTPS。

3、静态文件中间件

静态文件中间件( UseStaticFiles )返回静态文件,并简化进一步请求处理。

4、Cookie中间件

Cookie策略中间件( UseCookiePolicy )使应用符合欧盟一般数据保护条例的规定。

5、路由中间件

路由中间件( UseRouting )用于路由的请求。

6、身份认证中间件

身份认证中间件( UseAuthentication )尝试对用户进行身份验证,验证通过之后才会允许用户访问安全资源。

7、授权中间件

授权中间件( UseAuthorization )用于授权验证通过的用户可以访问哪些资源。

8、会话中间件

会话中间件( UseSession )建立和维护会话状态。如果应用程序使用会话状态,请在Cookie策略中间件之后和MVC中间件之前调用会话中间件。

9、终结点路由中间件

终结点路由中间件( UseEndpoints )用于将 Razor Pages 终结点添加到请求管道。

更多中间件组件可以到aspnet 的GitHub仓库中查看:https://github.com/aspnet

示例代码GitHub地址:git@github.com:jxl1024/Middleware.git

ASP.NET Core:中间件的更多相关文章

  1. ASP.NET Core 中间件Diagnostics使用

    ASP.NET Core 中间件(Middleware)Diagnostics使用.对于中间件的介绍可以查看之前的文章ASP.NET Core 开发-中间件(Middleware). Diagnost ...

  2. ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析

    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...

  3. [转]ASP.NET Core 中间件详解及项目实战

    本文转自:http://www.cnblogs.com/savorboard/p/5586229.html 前言 在上篇文章主要介绍了DotNetCore项目状况,本篇文章是我们在开发自己的项目中实际 ...

  4. 如何一秒钟从头构建一个 ASP.NET Core 中间件

    前言 其实地上本没有路,走的人多了,也便成了路. -- 鲁迅 就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最 ...

  5. ASP.NET Core中间件实现分布式 Session

    1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件的配置 1.2. 依赖注入中间件 1.3. Cookies ...

  6. ASP.NET Core 入门教程 9、ASP.NET Core 中间件(Middleware)入门

    一.前言 1.本教程主要内容 ASP.NET Core 中间件介绍 通过自定义 ASP.NET Core 中间件实现请求验签 2.本教程环境信息 软件/环境 说明 操作系统 Windows 10 SD ...

  7. ASP.NETCore学习记录(二) —— ASP.NET Core 中间件

    ASP.NET Core 中间件 目录: 什么是中间件 ? IApplicationBuilder 使用 IApplicationBuilder 创建中间件 Run.Map 与 Use 方法 实战中间 ...

  8. ASP.NET Core 中间件基本用法

    ASP.NET Core 中间件 ASP.NET Core的处理流程是一个管道,而中间件是装配到管道中的用于处理请求和响应的组件.中间件按照装配的先后顺序执行,并决定是否进入下一个组件.中间件管道的处 ...

  9. (4)ASP.NET Core 中间件

    1.前言 整个HTTP Request请求跟HTTP Response返回结果之间的处理流程是一个请求管道(request pipeline).而中间件(middleware)则是一种装配到请求管道以 ...

  10. ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 中间件 上一章节中,我们我们有讲到 Startup 类中的 Confi ...

随机推荐

  1. C++ 标准模板库(STL)——算法(Algorithms)的用法及理解

    C++ STL中的算法(Algorithms)作用于容器.它们提供了执行各种操作的方式,包括对容器内容执行初始化.排序.搜索和转换等操作.按照对容器内容的操作可将STL 中的算法大致分为四类: (1) ...

  2. 从 Vue 中 parseHTML 方法来看前端 html 词法分析

    先前我们在 从 Vue parseHTML 所用正则来学习常用正则语法 这篇文章中分析了 parseHTML 方法用到的正则表达式,在这个基础上我们可以继续分析 parseHTML 方法. 先来看该方 ...

  3. AI 预测蛋白质结构「GitHub 热点速览 v.21.29」

    作者:HelloGitHub-小鱼干 虽然 AI 领域藏龙卧虎,但是本周预测蛋白质结构的 alphafold 一开源出来就刷爆了朋友圈,虽然项目与我无关,但是看着科技进步能探寻到生命机理,吃瓜群众也有 ...

  4. ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放

    ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放 前言 加锁逻辑已经介绍完毕,那当一个线程重复加锁是如何处理的呢? 锁重入 在上一小节中,可以看到加锁的过程,再回头看 ...

  5. 【LeetCode】560. 和为K的子数组

    560. 和为K的子数组 知识点:数组:前缀和: 题目描述 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 输入:nums = [1,1,1], k = 2 ...

  6. Hive——元数据表含义

    Hive--元数据表含义 1.VERSION   -- 查询版本信息   Field Type Comment   VER_ID bigint(20) ID主键   SCHEMA_VERSION va ...

  7. 前端开发入门到进阶第三集【Jsonp】

    /* $.ajax({ type : "get", url : "${loginInfo.SSO_BASE_URL }/user/token/" + token ...

  8. 高版本(8以上)tomcat不支持rest中的delete和put方式请求怎么办

    出现问题 当我们去访问delete方式和put方式: 后来才知道tomcat8以上是不支持delete方式和put方式 解决方法: 在跳转目标的jsp头文件上改为(加上了isErrorPage=&qu ...

  9. Linux下Apache(HTTP)基础知识梳理-运维笔记

    HTTP介绍: HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传 ...

  10. 痞子衡嵌入式:i.MXRT1010, 1170型号上不一样的SNVS GPR寄存器读写控制设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1010, 1170型号上不一样的SNVS GPR寄存器读写控制设计. 痞子衡之前两篇文章 <在SBL项目实战中妙用i ...