更新记录
转载请注明出处:
2022年11月17日 发布。
2022年11月16日 从笔记迁移到博客。
Middleware(中间件)
中间件介绍
中间件可以处理 请求(Request) 和 响应(Response)。中间件可以处理传入请求,并将请求传递给下一个中间件。ASP.NET Core引入了中间件(Middleware)设计模式。多个中间件堆叠在一起形成了对 HTTP请求(HTTP Request) 和 HTTP响应(HTTP Response)进行处理的软件管道。ASP.NET Core 框架的请求处理管道由服务器和中间件组成,管道利用服务器来监听和接收请求,并完成最终对请求的响应,应用针对请求的处理则体现在有序排列的中间件上。
ASP.NET Core 框架利用一个消息处理管道完成对HTTP请求的监听、接收、处理和最终的响应。ASP.NET Core 管道由一个服务器(Server)和若干中间件(Middleware)构成,当宿主(Host)程序启动之后,管道被构建出来,作为管道“龙头”的服务器就开始监听来自客户端的HTTP请求。
比如:验证用户权限的中间件、处理程序错误的中间件、处理静态文件的中间件。
预定义中间件
ASP.NET Core中内置了中间件,叫做预定义中间件
主要包含MVC、认证、错误、静态文件、HTTPS重定向、跨域资源共享(CORS)等
ASP.NET Core也允许向管件添加自定义中间件
默认加载的中间件
默认情况下会有三个中间件会自动加载:
UseDeveloperExceptionPage
使用该方法后,会加载开发时异常处理页中间件,显示未处理异常信息
UseRouting
使用该方法后,将会引入路由中间件,通常与MVC框架一起使用
UseEndpoints
provides the configuration for the endpoint routing middleware
added by the UseRouting method
中间件的工作原理
说明
中间件(Middleware)被安排在队列中,也叫做请求管道(request pipeline),请求管道由Startup.cs文件中的Startup类中的Configure方法进行配置,该方法是应用程序启动的重要组成部分。
默认模板中间件
默认模板中的Configure方法中的代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
//测试读取
Debug.WriteLine(Configuration["PandaKey"]);
}
我们可以把Configure方法中的内容进行修改,从而修改管道的流程
比如下面配置管道只返回指定的字符串
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Run(async (content) =>
{
await content.Response.WriteAsync("Panda666");
});
}
中间件工作原理
中间件本质
ASP.NET Core的目的是接收HTTP的请求并向发送响应
ASP.NET Core通过使用中间件来处理这个过程
多个中间件按顺序关系使之形成了 管件(Pipeline)或 请求管件
每个 HTTP 请求都会进入管件,请求从一端进入,按照顺序由每个中间件处理,最后从另一端出来
中间件本质上是一个类,类中包含一段用来处理HTTP请求与HTTP响应的代码
中间件的返回
HTTP请求(Request)进入请求管件(Request Pipeline),将会创建一个对象
用于描述请求(Request)信息,也用于返回最终HTTP响应(Response)
该对象将会通过各个中间件(Middleware),按照顺序一个接着一个
并最终返回到HTTP Response
注意:如果中间件最终没有返回HTTP Response, 将会提示HTTP 404 Not Found
这意味着在next方法前的代码会在管道进入的阶段执行,next后的代码会在管道返回阶段进行执行
中间件之间的关系
每个中间件可以对传入的请求进行一些操作然后 传入下一个中间件 或 直接返回
中间件安卓添加到管道的顺序进行执行
而对于返回响应也会遍历进来时所经过的中间件,顺序与请求时的正好相反
终端中间件(terminal middleware)
如果一个中间件不继续调用下一个中间件,造成管道短路,该中间件叫做终端中间件
终端中间件可以避免继续执行不需要的中间件
比如:
如果请求的是静态资源,比如图片或者CSS文件等静态文件,就会管道短路
中间件的管理
在大型软件开发团队中,中间件一般以NuGet包的方式进行管理
这样可以将中间件拆分成小中间件,便于更新和管理
在微服务中,中间件的数量更多,模块化中间件更加便于维护代码
配置请求管道(使用中间件(Configuring Middleware))
说明
需要在Startup.cs文件中的Startup类中的Configure方法中设置请求管道
我们可以把Configure方法中的内容进行修改,从而修改管道的流程
比如下面配置管道只返回指定的字符串
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Run(async (content, next) =>
{
await content.Response.WriteAsync("Panda666");
});
}
我们可以根据需要来配置管道
比如需要支持静态文件,就配置引入支持静态文件中间件
比如需要验证用户身份,就配置引入身份验证中间件
注册中间件
ASP.NET Core框架提供了多种方法来实现不同方式的注册:
- Use:以委托的方式注册中间件。
- UseMiddleware
:以类型的方式注册中间件,T表示中间件的类型。 - Map:将特定的请求地址(path)与中间件绑定,即path匹配时执行该中间件。
- MapWhen:定义一个逻辑判断委托,当判断为true时执行指定的中间件。
- Run:表示一个断路的中间件,是执行并返回给上游的中间件,无后续中间件。
使用预定义中间件(Built-in Middleware)
说明
预定义中间件一般以Use开头
预定义中间件配置参数类型
预定义中间件对应的配置参数类型一般以中间件名称+Options命名
比如:
UseDeveloperExceptionPage对应的配置参数类型DevelopExceptionPageOptions
UseDefaultFiles对应的配置参数类型DefaultFilesOptions
UseStaticFiles对应的配置参数类型StaticFilesOptions
UseFileServer对应的配置参数类型FileServerOptions
使用UseDefaultFiles()中间件
基本使用
Web程序一般需要启动默认的index.html、default.html页面
为了在ASP.NET Core中支持Web默认文件,需要使用UseDefaultFiles()中间件
注意:需要在UseStaticFiles()中间件之前注册UseDefaultFiles()中间件
UseDefaultFiles()中间件只是一个URL重写,并不提供默认文件,需要自己定义
UseDefaultFiles()中间件默认查找的地址顺序:
Index.htm
Index.html
default.htm
default.html
只需要将这些文件放在wwwroot中即可
注册中间件代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//使用Web默认文件页面
app.UseDefaultFiles();
//引入支持静态文件中间件
app.UseStaticFiles();
}
自定义默认文件
如果需要自定义默认文件,可以使用DefaultFilesOptions类型
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//自定义默认文件
DefaultFilesOptions defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("panda.html");
//使用Web默认文件页面
app.UseDefaultFiles(defaultFilesOptions);
//引入支持静态文件中间件
app.UseStaticFiles();
}
项目结构:
使用UseDirectoryBrowser()中间件
使用UseDirectoryBrowser()中间件即可支持静态目录浏览功能
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDirectoryBrowser();
}
使用UseFileServer()中间件
UseFileServer()中间件结合了UseDefaultFiles()、UseStaticFiles()、UseDirectoryBrowser()
可以使用UseFileServer()中间件替换UseDefaultFiles()、UseStaticFiles()中间件
注册中间件实例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
FileServerOptions fileServerOptions = new FileServerOptions();
fileServerOptions.DefaultFilesOptions.DefaultFileNames.Clear();
fileServerOptions.DefaultFilesOptions.DefaultFileNames.Add("panda.html");
app.UseFileServer(fileServerOptions);
}
使用UseDeveloperExceptionPage()中间件
基本使用
UseDeveloperExceptionPage()中间件用于处理程序异常,显示开发异常页面
UseDeveloperExceptionPage()中间件应尽量在其他中间件前注册
便于捕获其他中间的异常问题
代码注册实例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
}
自定义UseDeveloperExceptionPage()中间件
使用DeveloperExceptionPageOptions类型参数
代码实例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
//自定义开发异常页面
DeveloperExceptionPageOptions options = new DeveloperExceptionPageOptions();
//显示异常出处代码的上下3行
options.SourceCodeLineCount = 3;
app.UseDeveloperExceptionPage(options);
}
}
使用UseMvc()中间件
启动MVC功能
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMvc();
}
使用UseMvcWithDefaultRoute()中间件
启用默认MVC路由功能
这会给ASP.NET Core添加:{controller }/{action}/{id?}这样的默认路由规则
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMvcWithDefaultRoute();
}
使用UseRouting()中间件
使用UseHttpsRedirection()中间件
使用UseEndpoints()中间件
自定义映射规则
实例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMvc();
app.UseMvcWithDefaultRoute();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
使用UseAuthorization()中间件
使用UseExceptionHandler()中间件
使用UseHsts()中间件
待合并-使用预定义中间件-使用预定义扩展方法
可以直接在Startup类的Configure方法中直接添加预定义的中间件
在 Configure 方法中
通过调用 IApplicationBuilder 接口中以 Use 开头的扩展方法
即可添加系统内置的中间件
注意:需要预先安装好已经定义的中间件包
public void Configure(IApplicationBuilder app)
{
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
每一个以Use开头的方法都会逐一并顺序地向管道添加相应的中间件
注意:
中间件的添加顺序将决定HTTP请求以及HTTP响应遍历它们的顺序
因此对于上面的代码,传入的请求首先会经过异常处理中间件
再到静态文件中间件,接着是认证中间件,最后则是MVC中间件
每一个中间件都可以终止请求管道,例如,如果认证失败,则不再继续向后执行
以Use开头的方法都是扩展方法,它们封装细节
在每一个扩展方法的内部实现中
每个中间件都是通过调用IApplicationBuilder接口的Use和Run方法
添加到请求管道中
自定义中间件(Creating Custom Middleware)
说明
创建自定义中间件需要:构造函数、名为Invoke的方法
构造函数有一个RequestDelegate类型的参数,表示在管道中的下一个中间件
Invoke方法有一个HttpContext类型的参数并返回Task类型
创建自定义中间件可以很灵活地控制HTTP请求的处理流程
比如要让应用程序仅接受GET和HEAD方法,就可以创建如下的中间件
public class HttpMethodCheckMiddleware
{
private readonly RequestDelegate _next;
public HttpMethodCheckMiddleware(RequestDelegate requestDelegate, IHostingEnvironment environment)
{
this._next = requestDelegate;
}
public Task Invoke(HttpContext context)
{
var requestMethod = context.Request.Method.ToUpper();
if (requestMethod == HttpMethods.Get || requestMethod == HttpMethods.Head)
{
return _next(context);
}
else
{
context.Response.StatusCode = 400;
context.Response.Headers.Add("X-AllowHTTPVerb", new[] { "GET,HEAD" });
context.Response.WriteAsync("只支持GET、HEAD方法");
return Task.CompletedTask;
}
}
}
在中间件的构造函数中,可以得到下一个中间件,并且还可以注入需要的服务
如上例中的IhostingEnvironment参数
在Invoke方法中,对HTTP请求方法进行判断
如果符合条件,则继续执行下一个中间件
否则返回400 Bad Request错误
并在响应中添加了自定义消息头,用于说明错误原因
接下来,在Configure方法中添加中间件:
app.UseMiddleware<T>();
添加时应注意其顺序
为了更方便地使用自定义中间件,还可以为它创建一个扩展方法
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseHttpMethodCheck (this Iapplication Builder builder)
{
return builder.UseMiddleware<HttpMethodCheckMiddleware>();
}
}
使用时,只要调用该扩展方法即可:
app.UseHttpMethodCheck();
创建自定义中间件-使用Use方法-Lambda
如果希望中间件能够调用下一个中间件,应使用app.Use()方法注册中间件。
提示:建议使用使用自定义中间件类型的方式来实现Use方法中的功能,方便日后维护。
app.Use(async (context, next) => {
//进入中间件执行代码1
if (context.Request.Method == HttpMethods.Get && context.Request.Query["custom"] == "true") {
await context.Response.WriteAsync("Custom Middleware \n");
}
//进入下一个中间件
await next();
//进入中间件执行代码2
//......
});
说明:
context
是HttpContext
类型,封装所有单个HTTP请求到响应的所有信息。next表示下一个中间件的委托,用于执行下一个中间件。
注意:调用下一个中间件组件时,无需参数,ASP.NET自动给参数。
HttpContext类型关键成员:
Connection 此属性返回一个ConnectionInfo对象
提供有关HTTP请求下载网络连接的信息
包括本地和远程IP地址和端口的详细信息
Request 此属性返回一个HTTPRequest对象
该对象描述正在处理的HTTP请求
RequestServices 此属性提供对请求可用的服务访问
Response 此属性返回一个HTTPResponse对象
该对象用于创建对HTTP请求的响应
Session 此属性返回与请求有关的会话数据
User 此属性返回与请求关联的用户的详细信息
Features 此属性提供对请求特性的访问请求
特性允许访问请求处理的低级方面
HTTPRequest对象主要成员:
Body 返回可用于读取请求主体的流
ContentLength 返回Content-Length标题的值
ContentType 返回Content-Type标题的值
Cookies 返回请求的Cookie
Form 返回请求主体的表单值
Headers 返回请求标头
IsHttps 如果使用https发出请求,则属性为true
Method 返回HTTP请求的方法
Path 返回请求URL的路径部分
Query 以键值对的形式,返回请求URL的查询字符串部分
HTTPResponse对象对象主要成员:
ContentLength 返回Content-Length标题的值
ContentType 返回Content-Type标题的值
Cookies 返回Cookie
HasStarted 如果asp.net core开始向用户客户端发送响应头之后就不可能更改了,则返回true
Headers 返回请求标头
StatusCode 设置响应的状态代码
WriteAsync() 这个异步方法向响应体写入一个数据字符串
Redirect() 这个方法发送从定向响应
实例:简单实例
app.Use(async (context, next) =>{
Console.WriteLine("中间件A:开始");
await next();
Console.WriteLine("中间件A:结束");
});
创建自定义中间件-使用Use方法-短路操作(Short-Circuiting the Request Pipeline)
生成完整响应的组件,可以选择不调用下一个函数以便不传递请求,不传递请求的组件会导致短路
注意:短路只会影响后面的中间件,前面的中间件仍会返回执行next后的代码
实例:如果path中带short,则提前返回
app.Use(async (context, next) => {
//如果满足条件,则直接短路返回了,不会去执行下一个中间件。
if (context.Request.Path == "/short") {
await context.Response.WriteAsync($"Request Short Circuited");
} else {
await next();
}
});
创建自定义中间件-使用Map方法-创建管道分支(Creating Pipeline Branches)
Map方法会根据是否匹配指定的请求路径来决定是否在一个新的分支上继续执行后续的中间件,并且在新分支上执行完后不再回到原来的管道上。管道分支是用于处理指定的URL的中间件,只对指定的URL有效
public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration);
实例:
app.Map("/branch", branch => {
//如果匹配了Path路径则注入以下2个中间件
branch.UseMiddleware<QueryStringMiddleWare>();
branch.Use(async (context, next) => {
await context.Response.WriteAsync($"Branch Middleware");
});
});
示意图:
注意:匹配成功指定的URL之后,在Map方法后的中间件将不会被执行。
实例:简单使用
app.Map(new PathString("/maptest"),a => a.Use(async (context, next) =>{
Console.WriteLine("中间件B:开始");
await next();
Console.WriteLine("中间件B:结束");}
));
创建自定义中间件-使用MapWhen方法-带条件的管道分支
MapWhen可以满足更复杂的条件,使用Func类型的作为参数。并以该参数作为判断条件,因此,它会对HttpContext对象进行更细致的判断,比如:如是否包含指定的请求消息头等。然后决定是否进入新的分支继续执行指定的中间件。
public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration);
在Startup.cs文件中,Startup类中的Configure方法中添加:
app.MapWhen(context => context.Request.Query.Keys.Contains("branch"),
branch => {
branch.UseMiddleware<QueryStringMiddleWare>();
branch.Use(async (context, next) => {
await context.Response.WriteAsync($"Branch Middleware");
});
});
创建自定义中间件-使用Run方法-Lambda
Run方法和Use方法的区别在于Run会直接返回(短路),而Use会继续到下一个中间件。
意味着Run方法是短路操作,也叫终端中间件。本质上Run方法在后台也是使用Use实现的。
Run方法的参数是一个RequestDelegate委托类型。
app.Run(async (httpContext) => {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("===========Request Header===========" + Environment.NewLine);
foreach (var item in httpContext.Request.Headers)
{
stringBuilder.Append($"{item.Key} = {item.Value}" + Environment.NewLine);
}
await httpContext.Response.WriteAsync(stringBuilder.ToString());
});
实例:Lambda
app.Run(async context =>
{
Console.WriteLine("中间件C");
await context.Response.WriteAsync("Hello world");
});
也可以定义中间件类来实现终端中间件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace PandaTest.Middleware
{
public class QueryStringMiddleWare
{
//指向下一个中间件
private RequestDelegate next;
//无参数的构造函数,必须
public QueryStringMiddleWare()
{
}
//构造函数,注入下一个中间件的引用
public QueryStringMiddleWare(RequestDelegate nextDelegate)
{
next = nextDelegate;
}
//调用中间件的Invoke方法
//参数是HttpContext类型
//注意是一个异步方法
public async Task Invoke(HttpContext context)
{
if (context.Request.Method == HttpMethods.Get
&& context.Request.Query["custom"] == "true")
{
await context.Response.WriteAsync("Class-based Middleware \n");
}
if(next != null)
{
//调用下一个中间件
await next(context);
}
}
}
}
注入中间件到管件
app.Map("/branch", branch => {
branch.Run((new QueryStringMiddleWare()).Invoke);
});
创建自定义中间件-使用UseWhen方法
UseWhen与MapWhen接受的参数完全一致
但它和Map和MapWhen区别是由它创建的分支在执行完后会继续回到原来的管道上。
public static IApplicationBuilder UseWhen(this IApplicationBuilder app, Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration);
实例:
app.UseWhen(context => context.Request.Path.Value == "/maptest",a => a.Use(async (context, next) =>{
Console.WriteLine("中间件B:开始");
await next();
Console.WriteLine("中间件B:结束");})
);
创建自定义中间件-使用Map方法-命令行中间件(Terminal Middleware)
命令行中间件是指不转发Request请求的中间件
使用Run方法
app.Map("/branch", branch => {
branch.UseMiddleware<QueryStringMiddleWare>();
branch.Run(async (context) => {
await context.Response.WriteAsync($"Branch Middleware");
});
});
说明:Run方法只是一个语法糖,本质上还是执行的Use方法,等价于
branch.Run(async (context) => {
await context.Response.WriteAsync($"Branch Middleware");
});
也可以使用类来实现命令行中间件,只需要不执行next方法即可:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Platform
{
public class QueryStringMiddleWare
{
private RequestDelegate next;
public QueryStringMiddleWare(RequestDelegate nextDelegate)
{
//do nothing
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Method == HttpMethods.Get && context.Request.Query["custom"] == "true")
{
await context.Response.WriteAsync("Class-based Middleware \n");
}
if(next != null)
{
await next(context);
}
}
}
}
使用时使用以下方式:
app.Map("/branch", branch => {
branch.Run(new QueryStringMiddleWare().Invoke);
});
创建自定义中间件-自定义中间件类(Class-based Middleware)
使用Lambda函数定义中间件很方便,但是它会导致startup类中的Configure方法很长很复杂。并且很难在不同项目中复用中间件,所以也可以使用类来定义中间件。
将中间件放入类文件中有这些好处:reusability、testability、maintainability。
实例:定义中间件文件SampleMiddleware.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Demo.WebAPI
{
public class SampleMiddleware
{
//下一个中间件的引用
private readonly RequestDelegate _next;
//注入下一个中间件
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
//执行中间件
public async Task InvokeAsync(HttpContext context)
{
//Doing
//......
//调用下一个中间件
await _next(context);
}
}
}
实例:定义中间件文件QueryStringMiddleWare.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace PandaTest.Middleware
{
public class QueryStringMiddleWare
{
//指向下一个中间件
//注意是RequestDelegate类型
private RequestDelegate next;
//构造函数,注入下一个中间件的引用
public QueryStringMiddleWare(RequestDelegate nextDelegate)
{
next = nextDelegate;
}
//调用中间件的Invoke方法
//参数是HttpContext类型
//注意是一个异步方法
//注意:Invoke方法中的代码必须是线程安全的
public async Task Invoke(HttpContext context)
{
if (context.Request.Method == HttpMethods.Get
&& context.Request.Query["custom"] == "true")
{
await context.Response.WriteAsync("Class-based Middleware \n");
}
//调用下一个中间件,注意需要传入context参数
await next(context);
}
}
}
然后在管道中直接注入中间件
app.UseMiddleware<QueryStringMiddleWare>();
或者定义IApplicationBuilder类型的扩展方法,使其可以优雅的调用中间件。
public static class SampleMiddlewareExtensions
{
public static IApplicationBuilder UseSampleMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SampleMiddleware>();
}
}
然后优雅的配置中间件。
public class Startup
{
//配置中间件
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//优雅的使用中间件
app.UseSampleMiddleware();
}
}
实例:自定义中间件并注册到管道中
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//第一个自定义的中间件
app.Use(async (content, next) =>
{
await content.Response.WriteAsync("Task1");
await next();
});
//第二个自定义的中间件
app.Use(async (content, next) =>
{
await content.Response.WriteAsync("Task2");
await next();
});
//第三个自定义的中间件
app.Use(async (content, next) =>
{
await content.Response.WriteAsync("Task3");
await next();
});
//第四个自定义的中间件
app.Run(async (content) =>
{
await content.Response.WriteAsync("Task4");
});
}
标签:Core,ASP,请求,app,中间件,next,context,public
From: https://www.cnblogs.com/cqpanda/p/16894814.html