ASP.NET Core 03中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
- 请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
使用 RunMap 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。
中间件代码分析
诊断 ID | 中断修复还是非中断修复 | 说明 |
---|---|---|
ASP0000 | 非中断 | 不要在“ConfigureServices”中调用“IServiceCollection.BuildServiceProvider” |
ASP0001 | 非中断 | 授权中间件配置错误 |
ASP0003 | 非中断 | 不要将模型绑定属性与路由处理程序一起使用 |
ASP0004 | 非中断 | 不要将操作结果与路由处理程序一起使用 |
ASP0005 | 非中断 | 不要将属性置于由路由处理程序 Lambda 调用的方法上 |
ASP0006 | 非中断 | 不要使用非文本序列号 |
ASP0007 | 非中断 | 路由形参和实参可选性不匹配 |
BL0001 | 重大 | 组件参数应具有公共资源库 |
BL0002 | 非中断 | 组件有多个 CaptureUnmatchedValues 参数 |
BL0003 | 重大 | 具有 CaptureUnmatchedValues 的组件参数类型错误 |
BL0004 | 重大 | 组件参数应该是公开参数 |
BL0005 | 非中断 | 不应在组件外部设置组件参数 |
BL0006 | 非中断 | 不要使用 RenderTree 类型 |
MVC1000 | 非中断 | 应避免使用 IHtmlHelper.Partial |
MVC1001 | 非中断 | 筛选器不能应用于页面处理程序方法 |
MVC1002 | 非中断 | 路由属性不能应用于页面处理程序方法 |
MVC1003 | 非中断 | 路由属性不能应用于页面模型 |
MVC1004 | 重大 | 重命名模型绑定参数 |
MVC1005 | 非中断 | 无法将 UseMvc 与终结点路由一起使用 |
MVC1006 | 重大 | 包含 TagHelpers 的方法必须是异步方法并返回 Task |
使用 WebApplication 创建中间件管道
ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。
每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。
尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。
用 Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。 通常可在 next 委托前后执行操作,如以下示例所示:
点击查看代码
app.Use(async (context, next) =>
{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:
点击查看代码
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");//Run 委托将 "Hello from 2nd delegate." 写入响应,然后终止管道
});
首选需要将上下文传递给 next 的 app.Use 重载
非分配 app.Use 扩展方法:
- 需要将上下文传递给 next。
- 保存使用其他重载时所需的两个内部每请求分配。
中间件顺序
下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。
以下 Program.cs 代码将为常见应用场景添加中间件组件:
- 异常/错误处理
- 当应用在开发环境中运行时:
开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
数据库错误页中间件 (UseDatabaseErrorPage) 报告数据库运行时错误。 - 当应用在生产环境中运行时:
异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
- HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
- 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
- Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
- 用于路由请求的路由中间件 (UseRouting)。
- 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
- 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
- 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
- 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPages 的 UseEndpoints)。
点击查看代码
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();
UseCors 和 UseStaticFiles 顺序
调用 UseCors 和 UseStaticFiles 的顺序取决于应用
通常, UseStaticFiles 在 UseCors之前调用 。 使用 JavaScript 检索跨站点的静态文件的应用必须在UseStaticFiles之前调用UseCors。
转接头中间件顺序
转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。
app.UseForwardedHeaders();
对中间件管道进行分支
Map 扩展用作约定来创建管道分支。 Map 基于给定请求路径的匹配项来创建请求管道分支。 如果请求路径以给定路径开头,则执行分支。
使用 Map 时,将从 HttpRequest.Path 中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase。
Map 支持嵌套
Map 还可同时匹配多个段
MapWhen 基于给定谓词的结果创建请求管道分支
UseWhen 也基于给定谓词的结果创建请求管道分支。 与 MapWhen 不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道
内置中间件
中间件 | 描述 | 顺序 |
---|---|---|
身份验证 | 提供身份验证支持。 | 在需要 HttpContext.User 之前。 OAuth 回叫的终端。 |
授权 | 提供身份验证支持。 | 紧接在身份验证中间件之后。 |
Cookie 策略 | 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secure 和 SameSite)的最低标准。 | 在发出 cookie 的中间件之前。 示例:身份验证、会话、MVC (TempData)。 |
CORS | 配置跨域资源共享。 | 在使用 CORS 的组件之前。 由于此错误,UseCors 当前必须在 UseResponseCaching 之前运行。 |
DeveloperExceptionPage | 生成一个页面,其中包含的错误信息仅适用于开发环境。 | 在生成错误的组件之前。 对于开发环境,项目模板会自动将此中间件注册为管道中的第一个中间件。 |
诊断 | 提供新应用的开发人员异常页、异常处理、状态代码页和默认网页的几个单独的中间件。 | 在生成错误的组件之前。 异常终端或为新应用提供默认网页的终端。 |
转接头 | 将代理标头转发到当前请求。 | 在使用已更新字段的组件之前。 示例:方案、主机、客户端 IP、方法。 |
运行状况检查 | 检查 ASP.NET Core 应用及其依赖项的运行状况,如检查数据库可用性。 | 如果请求与运行状况检查终结点匹配,则为终端。 |
标头传播 | 将 HTTP 标头从传入的请求传播到传出的 HTTP 客户端请求中。 | |
HTTP 日志记录 | 记录 HTTP 请求和响应。 | 中间件管道的开头。 |
HTTP 方法重写 | 允许传入 POST 请求重写方法。 | 在使用已更新方法的组件之前。 |
HTTPS 重定向 | 将所有 HTTP 请求重定向到 HTTPS。 | 在使用 URL 的组件之前。 |
HTTP 严格传输安全性 (HSTS) | 添加特殊响应标头的安全增强中间件。 | 在发送响应之前,修改请求的组件之后。 示例:转接头、URL 重写。 |
MVC | 用 MVC/Razor Pages 处理请求。 | 如果请求与路由匹配,则为终端。 |
OWIN | 与基于 OWIN 的应用、服务器和中间件进行互操作。 | 如果 OWIN 中间件处理完请求,则为终端。 |
请求解压缩 | 提供对解压缩请求的支持。 | 在读取请求正文的组件之前。 |
响应缓存 | 提供对缓存响应的支持。 | 在需要缓存的组件之前。 UseCORS 必须在 UseResponseCaching 之前。 |
响应压缩 | 提供对压缩响应的支持。 | 在需要压缩的组件之前。 |
请求本地化 | 提供本地化支持。 | 在对本地化敏感的组件之前。 使用 RouteDataRequestCultureProvider 时,必须在路由中间件之后显示。 |
终结点路由 | 定义和约束请求路由。 | 用于匹配路由的终端。 |
SPA | 通过返回单页应用程序 (SPA) 的默认页面,在中间件链中处理来自这个点的所有请求 | 在链中处于靠后位置,因此其他服务于静态文件、MVC 操作等内容的中间件占据优先位置。 |
会话 | 提供对管理用户会话的支持。 | 在需要会话的组件之前。 |
静态文件 | 为提供静态文件和目录浏览提供支持。 | 如果请求与文件匹配,则为终端。 |
URL 重写 | 提供对重写 URL 和重定向请求的支持。 | 在使用 URL 的组件之前。 |
W3CLogging | 以 W3C 扩展日志文件格式生成服务器访问日志。 | 中间件管道的开头。 |
WebSockets | 启用 WebSockets 协议。 | 在接受 WebSocket 请求所需的组件之前。 |
写入自定义 ASP.NET Core 中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 ASP.NET Core 提供了一组丰富的内置中间件组件,但在某些情况下,你可能需要写入自定义中间件。
介绍如何编写基于约定编写中间件。
内联中间件
通过调用 Microsoft.AspNetCore.Builder.UseExtensions.Use 创建中间件组件
Use 扩展可以使用两个重载:
- 一个重载采用 HttpContext 和 Func
。 不使用任何参数调用 Func 。 - 另一个重载采用 HttpContext 和 RequestDelegate。 通过传递 HttpContext 调用 RequestDelegate。
优先使用后面的重载,因为它省去了使用其他重载时所需的两个内部每请求分配。
点击查看代码
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline.
await next(context);
});
中间件类
中间件类必须包括:
- 具有类型为 RequestDelegate 的参数的公共构造函数。
- 名为 Invoke 或 InvokeAsync 的公共方法。 此方法必须:
-
返回 Task。
-
接受类型 HttpContext 的第一个参数。
构造函数和 Invoke/InvokeAsync 的其他参数由依赖关系注入 (DI) 填充。
点击查看代码
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline.
await _next(context);
}
}
创建扩展方法以通过 IApplicationBuilder 公开中间件
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
Program.cs 调用中间件
app.UseRequestCulture();