ASP.NET Core 配置系列三

6 ASP.NET Core中间件

ASP.NET Core 中间件是位于请求处理管道的一系列组件,用来处理请求和响应,在请求管道线上可以有一个或者多个中间件

当一个新的HTTP请求到达,第一个中间件将执行,这个中间件将执行下面两个过程中的一个.

1 生成响应并且发送响应到客户端

2 将请求传递到下一个中间件

ASP.NET Core 配置系列三

注意中间件执行的顺序按照他们在应用程序中注册的顺序执行,响应的顺序则按照相反的方向执行,假设我们有4个中间件-M1,M2,M3和M4,他们在应用程序中定义的顺序M1>M2>M3>M4,针对一个请求

1 中间件处理的顺序是M1>M2>M3>M4.

2 中间件响应返回到客户端的顺序为M4>M3>M2>M1

ASP.NET Core为什么要使用中间件?中间件处理应用程序发出的请求,可以增强和构建应用程序的高级功能,如身份验证、授权、日志记录等,ASP.NET Core 中有大量的内置中间件,我们可以在我们应用程序中使用这些中间件,删除不需要的中间件,从而优化我们应用程序的速度

6.1 ASP.NET Core客户自定义中间件

在ASP.NET Core中我们可以自定义中间件,客户自定义的中间件有下列四种形式:

1 内容生成中间件(Content-Generating middleware)

2 短路中间件(Short-Circuiting middleware)

3 请求编辑中间件(Request-Editing middleware)

4 响应编辑中间件(Response-Editing middleware)

每个中间件必须具备两个功能:
1 Microsoft.AspNetCore.Http命名空间下的RequestDeleg-ate,这个对象处理http请求,RequestDelegate代表请求管道中的下一个中间件组件,并通过依赖注入到中间件的构造中
2 当中间件被调用时调用Invoke方法,在这个方法中我们可以写自己代码
我们针对这四种类型的中间件单独构建一个例子来说明

6.1.1 内容生成中间件(Content-Generating middleware)

内容生成中间件生成内容或响应,我们创建这个中间件返回响应到客户端,我们通过一个简单例子来理解如何工作

在根目录一下创建一个Moddlewares文件夹,在这个文件夹下添加一个ContextMiddleware.cs文件,代码如下:

namespace AspNetCore.Configuration.Middlewares{    public class ContentMiddleware    {        private RequestDelegate _nextDelegate;        public ContentMiddleware(RequestDelegate next)        {            _nextDelegate = next;        }        public async Task Invoke(HttpContext httpContext)        {            if (httpContext.Request.Path == "/middleware")            {                await httpContext.Response.WriteAsync("这是Context 中间件");            }            else            {                _nextDelegate(httpContext);            }        }    }}

注意中间件类没有实现任何接口或者继承任何公共基类,它有一个构造函数使用了RequestDelegate类型,构造函数的参数由MVC自动提供,RequestDelegate对象表示下一个执行的中间件

中间件定义了Invoke方法,当.NET 接收到http请求时这个方法被调用,Invoke方法有一个HttpContext的参数类型,该参数包含了Http 请求和返回客户端的响应
在ContentMiddleware.cs类中,Invoke方法检查HTTP请求并且检查请求的URL中是否包含/middleware , 如果有,它将发送简单的文本响应到客户端,如果没有,它将转发请求到下一个中间件
现在在Program.cs类中使用UseMiddleware()方法注册中间件,代码如下:
using AspNetCore.Configuration.Middlewares;using AspNetCore.Configuration.Services;
var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllersWithViews();builder.Services.AddRazorPages();builder.Services.AddSingleton<TotalUsers>();var app = builder.Build();// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){    app.UseExceptionHandler("/Home/Error");    // The default HSTS value is 30 days.    app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseMiddleware<ContentMiddleware>();app.UseRouting();app.UseAuthorization();app.MapControllerRoute(    name: "default",    pattern: "{controller=Home}/{action=Index}/{id?}");app.Run();
运行应用程序然后进入https://localhost:7034/middleware,你会看到中间件输出的内容,如下图所示:

ASP.NET Core 配置系列三

如果请求的URL不是 https://localhost:7034/middleware,ContextMiddleware传递请求到下一个中间件,注意请求管道中已经没有中间件,因此请求直接返回

中间件中使用依赖注入

我们可以使用DI将服务注入到中间件,在前面的我们创建一个TotalUsers,现在我们使用中间件的构造函数注入该服务并在中间件中使用这个服务,因此,我们更新一下
using AspNetCore.Configuration.Services;namespace AspNetCore.Configuration.Middlewares{    public class ContentMiddleware    {        private RequestDelegate _nextDelegate;        private TotalUsers _totalUsers;        public ContentMiddleware(RequestDelegate next, TotalUsers totalUsers)        {            _nextDelegate = next;            _totalUsers = totalUsers;        }        public async Task Invoke(HttpContext httpContext)        {            if (httpContext.Request.Path == "/middleware")            {                await httpContext.Response.WriteAsync("this message come from ContextMiddleware" +" TotalUsers=" + _totalUsers.TUsers());            }            else            {                _nextDelegate(httpContext);            }        }    }}
运行应用程序,进入https://localhost:7034/middleware,现在你将看到总用户人数:

ASP.NET Core 配置系列三

6.1.2 短路中间件(Short-Circuiting middleware)

短路中间件会阻止后面中间件的执行,因为它会使整个请求管道短路,让我们创建一个短路中间件

在Middlewares文件夹下创建一个ShortCircuitMiddleware.cs类,在这个类中添加如下代码:

namespace AspNetCore.Configuration.Middlewares{    public class ShortCircuitMiddleware    {        private RequestDelegate _next;        public ShortCircuitMiddleware(RequestDelegate requestDelegate)        {            _next = requestDelegate;        }        public async Task Invoke(HttpContext context)        {            if (context.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox")))            {                context.Response.StatusCode = StatusCodes.Status401Unauthorized;            }            else            {                await _next(context);            }        }    }}
如果客户端浏览器使用的是firefox, ASP.NET Core中间件返回401 Unauthorized 
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
现在进入Program.cs类注册这个短路中间件,确保在Context-Middleware.cs中间件之前注册该中间件
app.UseMiddleware<ShortCircuitMiddleware>();app.UseMiddleware<ContentMiddleware>();
记住中间件执行的顺序是按照在Program类中注册的顺序执行的,我们想让ShortCircuitMiddleware中间件在ContentMiddleware中间件之前执行,因此我们把它放在第一个位置
短路中间件检查User-Agent在请求头查找是否是firefox浏览器,如果是它不会将请求转发到下一个中间件,而是直接返回未授权状态码,针对除firefox以外的浏览器会将请求转发到ContextMiddleware.cs

ASP.NET Core 配置系列三

运行程序,在firefox输入https://localhost:7034/middleware

ASP.NET Core 配置系列三

在别的浏览器中输入https://localhost:7034/middleware

ASP.NET Core 配置系列三

6.1.3 请求编辑中间件(Request-Editing middleware)

请求编辑中间件是针对http请求进行编辑,则不会生成响应,我们在Middlewares文件夹内创建一个RequestEditingMiddleware中间件,添加下面代码:
namespace AspNetCore.Configuration.Middlewares{    public class RequestEditingMiddleware    {        private RequestDelegate _next;        public RequestEditingMiddleware(RequestDelegate next) => _next = next;        public async Task Invoke(HttpContext httpContext)        {            httpContext.Items["Firefox"] = httpContext.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox"));            await _next(httpContext);        }    }}
在上面代码中我们看到对HTTP请求进行修改而没有发送任何响应,首先检查请求是否来自于firefox浏览器,如果是我们往Http-Context字典中添加一个key为Firefox值为true的键值对,如下代码:
httpContext.Items["Firefox"] = httpContext.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox"));

现在我们修改一下ShortCircuitMiddleware.cs中间件,删除下面代码:

if (context.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox")))

用下面代码替换

if(context.Items["Firefox"] as bool? == true)
最新的ShortCircuitMiddleware代码如下:
namespace AspNetCore.Configuration.Middlewares{    public class ShortCircuitMiddleware    {        private RequestDelegate _next;        public ShortCircuitMiddleware(RequestDelegate requestDelegate)        {            _next = requestDelegate;        }        public async Task Invoke(HttpContext context)        {            //if (context.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox")))            if(context.Items["Firefox"] as bool? == true)            {                context.Response.StatusCode = StatusCodes.Status401Unauthorized;            }            else            {                await _next(context);            }        }    }}
我们把RequestEditingMiddleware.cs注册到ShortCircuitMid-dleware中间件前面

因此在Program.cs中注册中间件的顺序如下:

app.UseMiddleware<RequestEditingMiddleware>();app.UseMiddleware<ShortCircuitMiddleware>();app.UseMiddleware<ContentMiddleware>();
ASP.NET Core 配置系列三

运行程序,firefox输入https://localhost:7034/middleware ,由于返回未授权的http因此你获取一个空白页

6.1.4 响应编辑中间件(Response-Editing middleware)

响应编辑中间件对响应进行编辑,在上面我们获取一个空白页,空白页针对应用程序是不友好的,我们将显示友好的消息,在这里将使用响应编辑中间件,我们提供文本的输出让用户在浏览器中看到
在Middlewares文件夹下添加一个ResponseEditingMiddle-ware.cs类,代码如下:
namespace AspNetCore.Configuration.Middlewares{    public class ResponseEditingMiddleware    {        private RequestDelegate _next;        public ResponseEditingMiddleware(RequestDelegate next)         {            _next=next;        }        public async Task Invoke(HttpContext httpContext)        {            await _next(httpContext);            if (httpContext.Response.StatusCode==401)            {                await httpContext.Response.WriteAsync("Firefox browser not authorized");            }            else if(httpContext.Response.StatusCode==404)             {                await httpContext.Response.WriteAsync("No Response Generated");            }        }    }}

在上面的代码如果HTTP响应的状态码是401,我们添加文本Firefox browser not authorized到响应中,回想一下我们在Short-Circuiting中间中生成401未授权的响应

响应编辑中间件仅仅编辑其它中间件的响应,因此它必须放到请求管道其它中间件的前面,所以注册的位置很重要

app.UseMiddleware<ResponseEditingMiddleware>();app.UseMiddleware<RequestEditingMiddleware>();app.UseMiddleware<ShortCircuitMiddleware>();app.UseMiddleware<ContentMiddleware>();
ASP.NET Core 配置系列三

现在运行你的应用程序并且在Firefox浏览器中输入URL-https://localhost:7034/middleware,你将获取到响应文本-Firefox browser not unthorized , 图片显示如下

ASP.NET Core 配置系列三

接收404状态码
在响应编辑中间件中,有else if 代码块:
else if(httpContext.Response.StatusCode==404) {    await httpContext.Response.WriteAsync("No Response Generated");}

当状态码是404时执行该代码,当请求的资源在应用程序中没有发现时该状态码会自动生成

在浏览器输入一个url-https://localhost:7034/tutorials,你会在浏览器中看到No Response Generated 消息

ASP.NET Core 配置系列三

7 路由中间件
路由中间件的主要任务是把请求赋值给各自的资源,我们在program.cs类中创建路由来执行这些操作
我们通过使用app.UseRouting()让应用程序来使用路由,我们在应用程序program类底部创建一个默认的路由
app.MapControllerRoute(    name: "default",    pattern: "{controller=Home}/{action=Index}/{id?}");

总结

这节我们主要介绍了中间件的概念以及内容生成中间件(Content-Generating middleware),短路中间件(Short-Circuiting middleware),请求编辑中间件(Request-Editing middleware),响应编辑中间件(Response-Editing middleware)等
源代码地址
https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.Configuration/AspNetCore.Configuration