ASP.NET Core中的中间件(Middleware)是指位于客户端和服务器之间的软件。在应用管道中处理请求和响应,可以选择是否将请求传递到管道的下一个中间件中,也可以配置其在某个中间件前后执行。
请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
使用 Run、Map 和Use扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。
中间件管道
ASP.NET Core中的请求管道包含了一系列的请求委托,依次调用。如图:
Run委托
Run委托中不能执行next,可以直接让请求管道短路(直接终止管道)。详情请见代码示例:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(async context => { await context.Response.WriteAsync("Hello World!"); });
//Hello End将不会打印,因为Run委托使管道短路了
app.Run(async context => { await context.Response.WriteAsync("Hello End!"); });
app.Run();
如果需要管道中执行下一个委托的话,需要使用Use。详情请见代码示例:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello World!");
await next.Invoke();
});
//Hello End打印
app.Run(async context => { await context.Response.WriteAsync("Hello End!"); });
app.Run();
中间件顺序
ASP.NET Core中的中间件的自定义执行应顺序如下图所示:
如果未调用Routing,则Routing在请求管道开始时运行。
管道分支
ASP.NET Core通过使用Map、MapWhen和UseWhen关键字对管道进行分支处理。
1.Map
Map会根据请求路径来进行分支匹配,如果请求路径以给定路径开头,则命中分支,参考代码如下:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
请求响应结果如下:
请求 | 响应 |
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/map1 | Map Test 1 |
localhost:1234/map2 | Map Test 2 |
localhost:1234/map3 | Hello from non-Map delegate. |
2.MapWhen
MapWhen可以使用条件判断来命中管道分支,参考代码如下:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}
请求响应结果如下:
请求 | 响应 |
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/?branch=main | Branch used = main |
3.UseWhen
UseWhen效果等同于MapWhen,但是如果分支不包含短路或者终端中间件,将会在执行完管道分支之后重新加入主管道,参考代码如下:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
appBuilder => HandleBranchAndRejoin(appBuilder));
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
void HandleBranchAndRejoin(IApplicationBuilder app)
{
var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var branchVer = context.Request.Query["branch"];
logger.LogInformation("Branch used = {branchVer}", branchVer);
// Do work that doesn't write to the Response.
await next();
// Do other work that doesn't write to the Response.
});
}
自定义中间件
ASP.NET Core中,自定中间件步骤如下:
1.声明使用约定激活的中间件
public class ConventionalMiddleware
{
private readonly RequestDelegate _next;
public ConventionalMiddleware(RequestDelegate next)
=> _next = next;
public async Task InvokeAsync(HttpContext context, SampleDbContext dbContext)
{
var keyValue = context.Request.Query["key"];
if (!string.IsNullOrWhiteSpace(keyValue))
{
await context.Response.WriteAsync($"Output: key—{keyValue}");
}
await _next(context);
}
}
2.声明使用 MiddlewareFactory 激活的中间件(必须继承自IMiddleware)
public class FactoryActivatedMiddleware : IMiddleware
{
public FactoryActivatedMiddleware(SampleDbContext dbContext)
=> _dbContext = dbContext;
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var keyValue = context.Request.Query["key"];
if (!string.IsNullOrWhiteSpace(keyValue))
{
await context.Response.WriteAsync($"Output: key—{keyValue}");
}
await next(context);
}
}
3.为中间件添加扩展
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseConventionalMiddleware(
this IApplicationBuilder app)
=> app.UseMiddleware<ConventionalMiddleware>();
public static IApplicationBuilder UseFactoryActivatedMiddleware(
this IApplicationBuilder app)
=> app.UseMiddleware<FactoryActivatedMiddleware>();
}
4.在启动类中进行注册
using Middleware;
var builder = WebApplication.CreateBuilder(args);
//使用 MiddlewareFactory 激活的中间件需要在此注入
builder.Services.AddTransient<FactoryActivatedMiddleware>();
var app = builder.Build();
app.UseConventionalMiddleware();
app.UseFactoryActivatedMiddleware();
app.Run();
5.在url中加入key参数查看结果
内置中间件
ASP.NET Core中常用内置中间件如下:
中间件 | 描述 | 顺序 |
提供身份验证支持。 | 在需要HttpContext.User之前。 OAuth 回叫的终端。 | |
提供身份验证支持。 | 紧接在身份验证中间件之后。 | |
跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如secure和SameSite)的最低标准。 | 在发出 cookie 的中间件之前。 示例:身份验证、会话、MVC (TempData)。 | |
配置跨域资源共享。 | 在使用 CORS 的组件之前。 由于此错误,UseCors当前必须在UseResponseCaching之前运行。 | |
生成一个页面,其中包含的错误信息仅适用于开发环境。 | 在生成错误的组件之前。 对于开发环境,项目模板会自动将此中间件注册为管道中的第一个中间件。 | |
提供新应用的开发人员异常页、异常处理、状态代码页和默认网页的几个单独的中间件。 | 在生成错误的组件之前。 异常终端或为新应用提供默认网页的终端。 | |
将代理标头转发到当前请求。 | 在使用已更新字段的组件之前。 示例:方案、主机、客户端 IP、方法。 | |
检查 ASP.NET Core 应用及其依赖项的运行状况,如检查数据库可用性。 | 如果请求与运行状况检查终结点匹配,则为终端。 | |
将 HTTP 标头从传入的请求传播到传出的 HTTP 客户端请求中。 | ||
记录 HTTP 请求和响应。 | 中间件管道的开头。 | |
允许传入 POST 请求重写方法。 | 在使用已更新方法的组件之前。 | |
将所有 HTTP 请求重定向到 HTTPS。 | 在使用 URL 的组件之前。 | |
添加特殊响应标头的安全增强中间件。 | 在发送响应之前,修改请求的组件之后。 示例:转接头、URL 重写。 | |
用 MVC/Razor Pages 处理请求。 | 如果请求与路由匹配,则为终端。 | |
与基于 OWIN 的应用、服务器和中间件进行互操作。 | 如果 OWIN 中间件处理完请求,则为终端。 | |
基于配置提供对缓存响应的支持。 | 在需要缓存的组件之前。UseRouting必须在UseOutputCaching之前。UseCORS必须在UseOutputCaching之前。 | |
提供对缓存响应的支持。 这需要客户端参与才能正常工作。 使用输出缓存实现完整的服务器控制。 | 在需要缓存的组件之前。UseCORS必须在UseResponseCaching之前。 通过对 Razor Pages 等 UI 应用没有好处,因为浏览器通常会设置阻止缓存的请求头。输出缓存有利于 UI 应用。 | |
提供对解压缩请求的支持。 | 在读取请求正文的组件之前。 | |
提供对压缩响应的支持。 | 在需要压缩的组件之前。 | |
提供本地化支持。 | 在对本地化敏感的组件之前。 使用RouteDataRequestCultureProvider时,必须在路由中间件之后显示。 | |
支持配置请求超时、全局和每个终结点。 | UseRequestTimeouts必须在UseExceptionHandler、UseDeveloperExceptionPage和UseRouting后。 | |
定义和约束请求路由。 | 用于匹配路由的终端。 | |
通过返回单页应用程序 (SPA) 的默认页面,在中间件链中处理来自这个点的所有请求 | 在链中处于靠后位置,因此其他服务于静态文件、MVC 操作等内容的中间件占据优先位置。 | |
提供对管理用户会话的支持。 | 在需要会话的组件之前。 | |
为提供静态文件和目录浏览提供支持。 | 如果请求与文件匹配,则为终端。 | |
提供对重写 URL 和重定向请求的支持。 | 在使用 URL 的组件之前。 | |
以W3C 扩展日志文件格式生成服务器访问日志。 | 中间件管道的开头。 | |
启用 WebSockets 协议。 | 在接受 WebSocket 请求所需的组件之前。 |