ASP.NET Core 中间件

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

作者:Rick AndersonSteve Smith

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMapUse 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。

中间件代码分析

ASP.NET Core 包括许多编译器平台分析器,用于检查应用程序代码的质量。 有关详细信息,请参阅 ASP.NET Core 应用中的代码分析

使用 WebApplication 创建中间件管道

ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。

请求处理模式显示请求到达、通过三个中间件进行处理以及响应离开应用。每个中间件运行其逻辑,并在 next() 语句处将请求传递到下一个中间件。在第三个中间件处理请求之后,请求按相反顺序返回通过前两个中间件,以进行离开应用前并在其 next() 语句后的其他处理,作为对客户端的响应。

每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。 通常可在 next 委托前后执行操作,如以下示例所示:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

对请求管道进行短路

当委托不将请求传递给下一个委托时,它被称为“让请求管道短路”。 通常需要短路,因为这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件的作用。 如果中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理 next.Invoke 语句后面的代码。 不过,请参阅下面有关尝试对已发送的响应执行写入操作的警告。

警告

在向客户端发送响应期间和之后,请勿调用 next.InvokeHttpResponse 启动后,更改会导致异常。 例如,在响应启动后,设置标头和状态代码更改将引发异常。 调用 next 后写入响应正文:

  • 可能会导致协议冲突,例如写入超过所述 Content-Length
  • 可能会损坏正文格式,例如将 HTML 页脚写入 CSS 文件。

HasStarted 是一个有用的提示,指示是否已发送标头或已写入正文。

有关详细信息,请参阅路由后对中间件进行短路

Run 委托

Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。

在前面的示例中,Run 委托将 "Hello from 2nd delegate." 写入响应,然后终止管道。 如果在 Run 委托之后添加了另一个 UseRun 委托,则不会调用该委托。

首选需要将上下文传递给 next 的 app.Use 重载

非分配 app.Use 扩展方法:

  • 需要将上下文传递给 next
  • 保存使用其他重载时所需的两个内部每请求分配。

有关详细信息,请参阅此 GitHub 问题

中间件顺序

下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

ASP.NET Core 中间件管道

上图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。

上一个图中的路由中间件显示在以下静态文件中。 这是通过显式调用 app.UseRouting 实现项目模板的顺序。 如果不调用 app.UseRouting,路由中间件将默认在管道开头运行。 有关详细信息,请参阅路由

ASP.NET Core 筛选器管道

Program.cs 文件中添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

Program.cs 中的以下突出显示的代码按照典型的建议顺序增加与安全相关的中间件组件:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

在上述代码中:

  • 在使用单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
  • 并非所有中间件都完全按照此顺序出现,但许多中间件都会遵循此顺序。 例如:
    • UseCorsUseAuthenticationUseAuthorization 必须按显示的顺序出现。
    • UseCors 当前必须在 UseResponseCaching 之前出现。 GitHub issue dotnet/aspnetcore #23218 中说明了此要求。
    • UseRequestLocalization 必须在可能检查请求区域性的任何中间件(例如 app.UseStaticFiles())之前出现。
    • 在使用速率限制终结点特定的 API 时,必须在 UseRouting 之后调用 UseRateLimiter。 例如,如果使用 [EnableRateLimiting] 属性,则必须在 UseRouting 之后调用 UseRateLimiter。 当仅调用全局限制器时,可以在 UseRouting 之前调用 UseRateLimiter

在某些场景下,中间件的排序有所不同。 例如,高速缓存和压缩排序是特定于场景的,存在多个有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用前面的代码,可以通过缓存压缩的响应来减少 CPU 使用量,但你可能最终会使用不同的压缩算法(如 Gzip 或 Brotli)来缓存资源的多个表示形式。

以下排序结合了静态文件以允许缓存压缩的静态文件:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

以下 Program.cs 代码将为常见应用场景添加中间件组件:

  1. 异常/错误处理
    • 当应用在开发环境中运行时:
    • 当应用在生产环境中运行时:
      • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
      • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
  2. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
  3. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
  4. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
  5. 用于路由请求的路由中间件 (UseRouting)。
  6. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
  7. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
  8. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
  9. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPagesUseEndpoints)。
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();

在前面的示例代码中,每个中间件扩展方法都通过 Microsoft.AspNetCore.Builder 命名空间在 WebApplicationBuilder 上公开。

UseExceptionHandler 是添加到管道的第一个中间件组件。 因此,异常处理程序中间件可捕获稍后调用中发生的任何异常。

尽早在管道中调用静态文件中间件,以便它可以处理请求并使其短路,而无需通过剩余组件。 静态文件中间件不提供授权检查。 可公开访问由静态文件中间件服务的任何文件,包括 wwwroot 下的文件。 有关保护静态文件的方法,请参阅 ASP.NET Core 中的静态文件

如果静态文件中间件未处理请求,则请求将被传递给执行身份验证的身份验证中间件 (UseAuthentication)。 身份验证不使未经身份验证的请求短路。 虽然身份验证中间件对请求进行身份验证,但仅在 MVC 选择特定 Razor Page 或 MVC 控制器和操作后,才发生授权(和拒绝)。

以下示例演示中间件排序,其中静态文件的请求在响应压缩中间件前由静态文件中间件进行处理。 使用此中间件顺序不压缩静态文件。 可以压缩 Razor Pages 响应。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

有关单页应用程序的信息,请参阅 ASP.NET Core 中的单页应用 (SPA) 概述

UseCors 和 UseStaticFiles 顺序

调用 UseCorsUseStaticFiles 的顺序取决于应用。 有关详细信息,请参阅 UseCors 和 UseStaticFiles 顺序

转接头中间件顺序

转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。 若要在诊断和错误处理中间件后运行转接头中间件,请参阅转接头中间件顺序

对中间件管道进行分支

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");
    });
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应。

请求 响应
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.

使用 Map 时,将从 HttpRequest.Path 中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase

Map 支持嵌套,例如:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

此外,Map 还可同时匹配多个段:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen 基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量 branch 是否存在:

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}");
    });
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应:

请求 响应
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

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.
    });
}

在前面的示例中,为所有请求写入 Hello from non-Map delegate. 响应。 如果请求中包含查询字符串变量 branch,则在重新加入主管道之前会记录其值。

内置中间件

ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。 若要详细了解短路,请参阅使用 WebApplication 创建中间件管道部分。

中间件 描述 顺序
身份验证 提供身份验证支持。 在需要 HttpContext.User 之前。 OAuth 回叫的终端。
授权 提供身份验证支持。 紧接在身份验证中间件之后。
Cookie 策略 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secureSameSite)的最低标准。 在发出 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 中间件处理完请求,则为终端。
输出缓存 基于配置提供对缓存响应的支持。 在需要缓存的组件之前。 UseRouting 必须在 UseOutputCaching 之前。 UseCORS 必须在 UseOutputCaching 之前。
响应缓存 提供对缓存响应的支持。 这需要客户端参与才能正常工作。 使用输出缓存实现完整的服务器控制。 在需要缓存的组件之前。 UseCORS 必须在 UseResponseCaching 之前。 通过对 Razor Pages 等 UI 应用没有好处,因为浏览器通常会设置阻止缓存的请求头。 输出缓存有利于 UI 应用。
请求解压缩 提供对解压缩请求的支持。 在读取请求正文的组件之前。
响应压缩 提供对压缩响应的支持。 在需要压缩的组件之前。
请求本地化 提供本地化支持。 在对本地化敏感的组件之前。 使用 RouteDataRequestCultureProvider 时,必须在路由中间件之后显示。
请求超时 支持配置请求超时、全局和每个终结点。 UseRequestTimeouts 必须在 UseExceptionHandlerUseDeveloperExceptionPageUseRouting 后。
终结点路由 定义和约束请求路由。 用于匹配路由的终端。
SPA 通过返回单页应用程序 (SPA) 的默认页面,在中间件链中处理来自这个点的所有请求 在链中处于靠后位置,因此其他服务于静态文件、MVC 操作等内容的中间件占据优先位置。
会话 提供对管理用户会话的支持。 在需要会话的组件之前。
静态文件 为提供静态文件和目录浏览提供支持。 如果请求与文件匹配,则为终端。
URL 重写 提供对重写 URL 和重定向请求的支持。 在使用 URL 的组件之前。
W3CLogging W3C 扩展日志文件格式生成服务器访问日志。 中间件管道的开头。
WebSockets 启用 WebSockets 协议。 在接受 WebSocket 请求所需的组件之前。

其他资源

作者:Rick AndersonSteve Smith

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMapUse 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。

中间件代码分析

ASP.NET Core 包括许多编译器平台分析器,用于检查应用程序代码的质量。 有关详细信息,请参阅 ASP.NET Core 应用中的代码分析

使用 WebApplication 创建中间件管道

ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。

请求处理模式显示请求到达、通过三个中间件进行处理以及响应离开应用。每个中间件运行其逻辑,并在 next() 语句处将请求传递到下一个中间件。在第三个中间件处理请求之后,请求按相反顺序返回通过前两个中间件,以进行离开应用前并在其 next() 语句后的其他处理,作为对客户端的响应。

每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。 通常可在 next 委托前后执行操作,如以下示例所示:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

当委托不将请求传递给下一个委托时,它被称为“让请求管道短路”。 通常需要短路,因为这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件的作用。 如果中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理 next.Invoke 语句后面的代码。 不过,请参阅下面有关尝试对已发送的响应执行写入操作的警告。

警告

在向客户端发送响应后,请勿调用 next.Invoke。 响应启动后,针对 HttpResponse 的更改将引发异常。 例如,设置标头和状态代码更改将引发异常。 调用 next 后写入响应正文:

  • 可能导致违反协议。 例如,写入的长度超过规定的 Content-Length
  • 可能损坏正文格式。 例如,向 CSS 文件中写入 HTML 页脚。

HasStarted 是一个有用的提示,指示是否已发送标头或已写入正文。

Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。

在前面的示例中,Run 委托将 "Hello from 2nd delegate." 写入响应,然后终止管道。 如果在 Run 委托之后添加了另一个 UseRun 委托,则不会调用该委托。

首选需要将上下文传递给 next 的 app.Use 重载

非分配 app.Use 扩展方法:

  • 需要将上下文传递给 next
  • 保存使用其他重载时所需的两个内部每请求分配。

有关详细信息,请参阅此 GitHub 问题

中间件顺序

下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

ASP.NET Core 中间件管道

上图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。

上一个图中的路由中间件显示在以下静态文件中。 这是通过显式调用 app.UseRouting 实现项目模板的顺序。 如果不调用 app.UseRouting,路由中间件将默认在管道开头运行。 有关详细信息,请参阅路由

ASP.NET Core 筛选器管道

Program.cs 文件中添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

Program.cs 中的以下突出显示的代码按照典型的建议顺序增加与安全相关的中间件组件:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

在上述代码中:

  • 在使用单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
  • 并非所有中间件都完全按照此顺序出现,但许多中间件都会遵循此顺序。 例如:
    • UseCorsUseAuthenticationUseAuthorization 必须按显示的顺序出现。
    • UseCors 当前必须在 UseResponseCaching 之前出现。 GitHub issue dotnet/aspnetcore #23218 中说明了此要求。
    • UseRequestLocalization 必须在可能检查请求区域性的任何中间件(例如 app.UseStaticFiles())之前出现。
    • 在使用速率限制终结点特定的 API 时,必须在 UseRouting 之后调用 UseRateLimiter。 例如,如果使用 [EnableRateLimiting] 属性,则必须在 UseRouting 之后调用 UseRateLimiter。 当仅调用全局限制器时,可以在 UseRouting 之前调用 UseRateLimiter

在某些场景下,中间件的排序有所不同。 例如,高速缓存和压缩排序是特定于场景的,存在多个有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用前面的代码,可以通过缓存压缩的响应来减少 CPU 使用量,但你可能最终会使用不同的压缩算法(如 Gzip 或 Brotli)来缓存资源的多个表示形式。

以下排序结合了静态文件以允许缓存压缩的静态文件:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

以下 Program.cs 代码将为常见应用场景添加中间件组件:

  1. 异常/错误处理
    • 当应用在开发环境中运行时:
    • 当应用在生产环境中运行时:
      • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
      • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
  2. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
  3. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
  4. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
  5. 用于路由请求的路由中间件 (UseRouting)。
  6. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
  7. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
  8. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
  9. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPagesUseEndpoints)。
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();

在前面的示例代码中,每个中间件扩展方法都通过 Microsoft.AspNetCore.Builder 命名空间在 WebApplicationBuilder 上公开。

UseExceptionHandler 是添加到管道的第一个中间件组件。 因此,异常处理程序中间件可捕获稍后调用中发生的任何异常。

尽早在管道中调用静态文件中间件,以便它可以处理请求并使其短路,而无需通过剩余组件。 静态文件中间件不提供授权检查。 可公开访问由静态文件中间件服务的任何文件,包括 wwwroot 下的文件。 有关保护静态文件的方法,请参阅 ASP.NET Core 中的静态文件

如果静态文件中间件未处理请求,则请求将被传递给执行身份验证的身份验证中间件 (UseAuthentication)。 身份验证不使未经身份验证的请求短路。 虽然身份验证中间件对请求进行身份验证,但仅在 MVC 选择特定 Razor Page 或 MVC 控制器和操作后,才发生授权(和拒绝)。

以下示例演示中间件排序,其中静态文件的请求在响应压缩中间件前由静态文件中间件进行处理。 使用此中间件顺序不压缩静态文件。 可以压缩 Razor Pages 响应。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

有关单页应用程序的信息,请参阅 ReactAngular 项目模板的指南。

UseCors 和 UseStaticFiles 顺序

调用 UseCorsUseStaticFiles 的顺序取决于应用。 有关详细信息,请参阅 UseCors 和 UseStaticFiles 顺序

转接头中间件顺序

转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。 若要在诊断和错误处理中间件后运行转接头中间件,请参阅转接头中间件顺序

对中间件管道进行分支

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");
    });
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应。

请求 响应
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.

使用 Map 时,将从 HttpRequest.Path 中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase

Map 支持嵌套,例如:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

此外,Map 还可同时匹配多个段:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen 基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量 branch 是否存在:

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}");
    });
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应:

请求 响应
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

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.
    });
}

在前面的示例中,为所有请求写入 Hello from non-Map delegate. 响应。 如果请求中包含查询字符串变量 branch,则在重新加入主管道之前会记录其值。

内置中间件

ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。 若要详细了解短路,请参阅使用 WebApplication 创建中间件管道部分。

中间件 描述 顺序
身份验证 提供身份验证支持。 在需要 HttpContext.User 之前。 OAuth 回叫的终端。
授权 提供身份验证支持。 紧接在身份验证中间件之后。
Cookie 策略 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secureSameSite)的最低标准。 在发出 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 中间件处理完请求,则为终端。
输出缓存 基于配置提供对缓存响应的支持。 在需要缓存的组件之前。 UseRouting 必须在 UseOutputCaching 之前。 UseCORS 必须在 UseOutputCaching 之前。
响应缓存 提供对缓存响应的支持。 这需要客户端参与才能正常工作。 使用输出缓存实现完整的服务器控制。 在需要缓存的组件之前。 UseCORS 必须在 UseResponseCaching 之前。 通过对 Razor Pages 等 UI 应用没有好处,因为浏览器通常会设置阻止缓存的请求头。 输出缓存有利于 UI 应用。
请求解压缩 提供对解压缩请求的支持。 在读取请求正文的组件之前。
响应压缩 提供对压缩响应的支持。 在需要压缩的组件之前。
请求本地化 提供本地化支持。 在对本地化敏感的组件之前。 使用 RouteDataRequestCultureProvider 时,必须在路由中间件之后显示。
终结点路由 定义和约束请求路由。 用于匹配路由的终端。
SPA 通过返回单页应用程序 (SPA) 的默认页面,在中间件链中处理来自这个点的所有请求 在链中处于靠后位置,因此其他服务于静态文件、MVC 操作等内容的中间件占据优先位置。
会话 提供对管理用户会话的支持。 在需要会话的组件之前。
静态文件 为提供静态文件和目录浏览提供支持。 如果请求与文件匹配,则为终端。
URL 重写 提供对重写 URL 和重定向请求的支持。 在使用 URL 的组件之前。
W3CLogging W3C 扩展日志文件格式生成服务器访问日志。 中间件管道的开头。
WebSockets 启用 WebSockets 协议。 在接受 WebSocket 请求所需的组件之前。

其他资源

作者:Rick AndersonSteve Smith

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMapUse 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。

中间件代码分析

ASP.NET Core 包括许多编译器平台分析器,用于检查应用程序代码的质量。 有关详细信息,请参阅 ASP.NET Core 应用中的代码分析

使用 WebApplication 创建中间件管道

ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。

请求处理模式显示请求到达、通过三个中间件进行处理以及响应离开应用。每个中间件运行其逻辑,并在 next() 语句处将请求传递到下一个中间件。在第三个中间件处理请求之后,请求按相反顺序返回通过前两个中间件,以进行离开应用前并在其 next() 语句后的其他处理,作为对客户端的响应。

每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。 通常可在 next 委托前后执行操作,如以下示例所示:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

当委托不将请求传递给下一个委托时,它被称为“让请求管道短路”。 通常需要短路,因为这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件的作用。 如果中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理 next.Invoke 语句后面的代码。 不过,请参阅下面有关尝试对已发送的响应执行写入操作的警告。

警告

在向客户端发送响应后,请勿调用 next.Invoke。 响应启动后,针对 HttpResponse 的更改将引发异常。 例如,设置标头和状态代码更改将引发异常。 调用 next 后写入响应正文:

  • 可能导致违反协议。 例如,写入的长度超过规定的 Content-Length
  • 可能损坏正文格式。 例如,向 CSS 文件中写入 HTML 页脚。

HasStarted 是一个有用的提示,指示是否已发送标头或已写入正文。

Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。

在前面的示例中,Run 委托将 "Hello from 2nd delegate." 写入响应,然后终止管道。 如果在 Run 委托之后添加了另一个 UseRun 委托,则不会调用该委托。

首选需要将上下文传递给 next 的 app.Use 重载

非分配 app.Use 扩展方法:

  • 需要将上下文传递给 next
  • 保存使用其他重载时所需的两个内部每请求分配。

有关详细信息,请参阅此 GitHub 问题

中间件顺序

下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

ASP.NET Core 中间件管道

上图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。

上一个图中的路由中间件显示在以下静态文件中。 这是通过显式调用 app.UseRouting 实现项目模板的顺序。 如果不调用 app.UseRouting,路由中间件将默认在管道开头运行。 有关详细信息,请参阅路由

ASP.NET Core 筛选器管道

Program.cs 文件中添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

Program.cs 中的以下突出显示的代码按照典型的建议顺序增加与安全相关的中间件组件:

using IndividualAccountsExample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/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.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

在上述代码中:

  • 在使用单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
  • 并非所有中间件都完全按照此顺序出现,但许多中间件都会遵循此顺序。 例如:
    • UseCorsUseAuthenticationUseAuthorization 必须按显示的顺序出现。
    • UseCors 当前必须在 UseResponseCaching 之前出现。 GitHub issue dotnet/aspnetcore #23218 中说明了此要求。
    • UseRequestLocalization 必须在可能检查请求区域性的任何中间件(例如 app.UseMvcWithDefaultRoute())之前出现。

在某些场景下,中间件的排序有所不同。 例如,高速缓存和压缩排序是特定于场景的,存在多个有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用前面的代码,可以通过缓存压缩的响应来减少 CPU 使用量,但你可能最终会使用不同的压缩算法(如 Gzip 或 Brotli)来缓存资源的多个表示形式。

以下排序结合了静态文件以允许缓存压缩的静态文件:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

以下 Program.cs 代码将为常见应用场景添加中间件组件:

  1. 异常/错误处理
    • 当应用在开发环境中运行时:
    • 当应用在生产环境中运行时:
      • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
      • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
  2. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
  3. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
  4. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
  5. 用于路由请求的路由中间件 (UseRouting)。
  6. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
  7. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
  8. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
  9. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPagesUseEndpoints)。
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();

在前面的示例代码中,每个中间件扩展方法都通过 Microsoft.AspNetCore.Builder 命名空间在 WebApplicationBuilder 上公开。

UseExceptionHandler 是添加到管道的第一个中间件组件。 因此,异常处理程序中间件可捕获稍后调用中发生的任何异常。

尽早在管道中调用静态文件中间件,以便它可以处理请求并使其短路,而无需通过剩余组件。 静态文件中间件不提供授权检查。 可公开访问由静态文件中间件服务的任何文件,包括 wwwroot 下的文件。 有关保护静态文件的方法,请参阅 ASP.NET Core 中的静态文件

如果静态文件中间件未处理请求,则请求将被传递给执行身份验证的身份验证中间件 (UseAuthentication)。 身份验证不使未经身份验证的请求短路。 虽然身份验证中间件对请求进行身份验证,但仅在 MVC 选择特定 Razor Page 或 MVC 控制器和操作后,才发生授权(和拒绝)。

以下示例演示中间件排序,其中静态文件的请求在响应压缩中间件前由静态文件中间件进行处理。 使用此中间件顺序不压缩静态文件。 可以压缩 Razor Pages 响应。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

有关单页应用程序的信息,请参阅 ReactAngular 项目模板的指南。

UseCors 和 UseStaticFiles 顺序

调用 UseCorsUseStaticFiles 的顺序取决于应用。 有关详细信息,请参阅 UseCors 和 UseStaticFiles 顺序

转接头中间件顺序

转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。 若要在诊断和错误处理中间件后运行转接头中间件,请参阅转接头中间件顺序

对中间件管道进行分支

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");
    });
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应。

请求 响应
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.

使用 Map 时,将从 HttpRequest.Path 中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase

Map 支持嵌套,例如:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

此外,Map 还可同时匹配多个段:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen 基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量 branch 是否存在:

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}");
    });
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应:

请求 响应
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

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.
    });
}

在前面的示例中,为所有请求写入 Hello from non-Map delegate. 响应。 如果请求中包含查询字符串变量 branch,则在重新加入主管道之前会记录其值。

内置中间件

ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。 若要详细了解短路,请参阅使用 WebApplication 创建中间件管道部分。

中间件 描述 顺序
身份验证 提供身份验证支持。 在需要 HttpContext.User 之前。 OAuth 回叫的终端。
授权 提供身份验证支持。 紧接在身份验证中间件之后。
Cookie 策略 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secureSameSite)的最低标准。 在发出 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 请求所需的组件之前。

其他资源

作者:Rick AndersonSteve Smith

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMapUse 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。

使用 IApplicationBuilder 创建中间件管道

ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。

请求处理模式显示请求到达、通过三个中间件进行处理以及响应离开应用。每个中间件运行其逻辑,并在 next() 语句处将请求传递到下一个中间件。在第三个中间件处理请求之后,请求按相反顺序返回通过前两个中间件,以进行离开应用前并在其 next() 语句后的其他处理,作为对客户端的响应。

每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。 通常可在下一个委托前后执行操作,如以下示例所示:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

当委托不将请求传递给下一个委托时,它被称为“让请求管道短路”。 通常需要短路,因为这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件的作用。 如果中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理 next.Invoke 语句后面的代码。 不过,请参阅下面有关尝试对已发送的响应执行写入操作的警告。

警告

在向客户端发送响应后,请勿调用 next.Invoke。 响应启动后,针对 HttpResponse 的更改将引发异常。 例如,设置标头和状态代码更改将引发异常。 调用 next 后写入响应正文:

  • 可能导致违反协议。 例如,写入的长度超过规定的 Content-Length
  • 可能损坏正文格式。 例如,向 CSS 文件中写入 HTML 页脚。

HasStarted 是一个有用的提示,指示是否已发送标头或已写入正文。

Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。

在前面的示例中,Run 委托将 "Hello from 2nd delegate." 写入响应,然后终止管道。 如果在 Run 委托之后添加了另一个 UseRun 委托,则不会调用该委托。

中间件顺序

下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

ASP.NET Core 中间件管道

上图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。

ASP.NET Core 筛选器管道

Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

下面的 Startup.Configure 方法按照典型的建议顺序增加与安全相关的中间件组件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();

    app.UseRouting();
    // app.UseRequestLocalization();
    // app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
    // app.UseResponseCompression();
    // app.UseResponseCaching();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

在上述代码中:

  • 在使用单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
  • 并非所有中间件都完全按照此顺序出现,但许多中间件都会遵循此顺序。 例如:
    • UseCorsUseAuthenticationUseAuthorization 必须按显示的顺序出现。
    • 由于这个 bugUseCors 当前必须在 UseResponseCaching 之前出现。
    • UseRequestLocalization 必须在可能检查请求区域性的任何中间件(例如 app.UseMvcWithDefaultRoute())之前出现。

在某些场景下,中间件的排序有所不同。 例如,高速缓存和压缩排序是特定于场景的,存在多个有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用前面的代码,可以通过缓存压缩的响应来节省 CPU,但你可能最终会使用不同的压缩算法(如 Gzip 或 Brotli)来缓存资源的多个表示形式。

以下排序结合了静态文件以允许缓存压缩的静态文件:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

以下 Startup.Configure 方法将为常见应用方案添加中间件组件:

  1. 异常/错误处理
    • 当应用在开发环境中运行时:
      • 开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
      • 数据库错误页中间件报告数据库运行时错误。
    • 当应用在生产环境中运行时:
      • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
      • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
  2. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
  3. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
  4. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
  5. 用于路由请求的路由中间件 (UseRouting)。
  6. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
  7. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
  8. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
  9. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPagesUseEndpoints)。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    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.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

在前面的示例代码中,每个中间件扩展方法都通过 Microsoft.AspNetCore.Builder 命名空间在 IApplicationBuilder 上公开。

UseExceptionHandler 是添加到管道的第一个中间件组件。 因此,异常处理程序中间件可捕获稍后调用中发生的任何异常。

尽早在管道中调用静态文件中间件,以便它可以处理请求并使其短路,而无需通过剩余组件。 静态文件中间件不提供授权检查。 可公开访问由静态文件中间件服务的任何文件,包括 wwwroot 下的文件。 有关保护静态文件的方法,请参阅 ASP.NET Core 中的静态文件

如果静态文件中间件未处理请求,则请求将被传递给执行身份验证的身份验证中间件 (UseAuthentication)。 身份验证不使未经身份验证的请求短路。 虽然身份验证中间件对请求进行身份验证,但仅在 MVC 选择特定 Razor Page 或 MVC 控制器和操作后,才发生授权(和拒绝)。

以下示例演示中间件排序,其中静态文件的请求在响应压缩中间件前由静态文件中间件进行处理。 使用此中间件顺序不压缩静态文件。 可以压缩 Razor Pages 响应。

public void Configure(IApplicationBuilder app)
{
    // Static files aren't compressed by Static File Middleware.
    app.UseStaticFiles();

    app.UseRouting();

    app.UseResponseCompression();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

对于单页应用程序 (SPA),SPA 中间件 UseSpaStaticFiles 通常是中间件管道中的最后一个。 SPA 中间件处于最后的作用是:

  • 允许所有其他中间件首先响应匹配的请求。
  • 允许具有客户端侧路由的 SPA 针对服务器应用无法识别的所有路由运行。

若要详细了解 SPA,请参阅 ReactAngular 项目模板的指南。

转接头中间件顺序

转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。 若要在诊断和错误处理中间件后运行转接头中间件,请参阅转接头中间件顺序

对中间件管道进行分支

Map 扩展用作约定来创建管道分支。 Map 基于给定请求路径的匹配项来创建请求管道分支。 如果请求路径以给定路径开头,则执行分支。

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应。

请求 响应
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.

使用 Map 时,将从 HttpRequest.Path 中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase

Map 支持嵌套,例如:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

此外,Map 还可同时匹配多个段:

public class Startup
{
    private static void HandleMultiSeg(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map multiple segments.");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1/seg1", HandleMultiSeg);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

MapWhen 基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量 branch 是否存在:

public class Startup
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               HandleBranch);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应:

请求 响应
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen 也基于给定谓词的结果创建请求管道分支。 与 MapWhen 不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道:

public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        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.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
}

在前面的示例中,为所有请求写入“Hello from main pipeline.”响应。 如果请求中包含查询字符串变量 branch,则在重新加入主管道之前会记录其值。

内置中间件

ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。 若要详细了解短路,请参阅使用 IApplicationBuilder 创建中间件管道部分。

中间件 描述 顺序
身份验证 提供身份验证支持。 在需要 HttpContext.User 之前。 OAuth 回叫的终端。
授权 提供身份验证支持。 紧接在身份验证中间件之后。
Cookie 策略 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secureSameSite)的最低标准。 在发出 cookie 的中间件之前。 示例:身份验证、会话、MVC (TempData)。
CORS 配置跨域资源共享。 在使用 CORS 的组件之前。 由于此错误UseCors 当前必须在 UseResponseCaching 之前运行。
诊断 提供新应用的开发人员异常页、异常处理、状态代码页和默认网页的几个单独的中间件。 在生成错误的组件之前。 异常终端或为新应用提供默认网页的终端。
转接头 将代理标头转发到当前请求。 在使用已更新字段的组件之前。 示例:方案、主机、客户端 IP、方法。
运行状况检查 检查 ASP.NET Core 应用及其依赖项的运行状况,如检查数据库可用性。 如果请求与运行状况检查终结点匹配,则为终端。
标头传播 将 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 的组件之前。
WebSockets 启用 WebSocket 协议。 在接受 WebSocket 请求所需的组件之前。

其他资源