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 代码将为常见应用场景添加中间件组件:

  1. 异常/错误处理
  • 当应用在开发环境中运行时:
    开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
    数据库错误页中间件 (UseDatabaseErrorPage) 报告数据库运行时错误。
  • 当应用在生产环境中运行时:
    异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
    HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
  1. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
  2. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
  3. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
  4. 用于路由请求的路由中间件 (UseRouting)。
  5. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
  6. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
  7. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
  8. 用于将 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 的公共方法。 此方法必须:
  1. 返回 Task。
    
  2. 接受类型 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();
posted @ 2022-09-06 17:42  BLJCharles  阅读(57)  评论(0编辑  收藏  举报