龙空技术网

ASP.NET Core 中间件

老衲法号不行 337

前言:

现时小伙伴们对“net常用中间件”大致比较重视,我们都需要分析一些“net常用中间件”的相关资讯。那么小编也在网上收集了一些对于“net常用中间件””的相关文章,希望各位老铁们能喜欢,咱们一起来学习一下吧!

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

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

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

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

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

使用 IApplicationBuilder 创建中间件管道

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

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

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

C#复制

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

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

C#

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] 方法:

C#复制

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 委托之后添加了另一个 Use 或 Run 委托,则不会调用该委托。

中间件顺序

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

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

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

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

C#

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 应用时未添加的中间件已被注释掉。并非所有中间件都完全按照此顺序出现,但许多中间件都会遵循此顺序。 例如:UseCors、UseAuthentication 和 UseAuthorization 必须按显示的顺序出现。由于这个 bug,UseCors 当前必须在 UseResponseCaching 之前出现。UseRequestLocalization 必须在可能检查请求区域性的任何中间件(例如 app.UseMvcWithDefaultRoute())之前出现。

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

C#

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

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

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

C#

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

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

异常/错误处理当应用在开发环境中运行时:开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。数据库错误页中间件报告数据库运行时错误。当应用在生产环境中运行时:异常处理程序中间件 (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)。

C#

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 响应。

C#

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,请参阅 React 和 Angular 项目模板的指南。

转接头中间件顺序

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

对中间件管道进行分支

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

C#

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

下表使用前面的代码显示来自 的请求和响应。

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

Map 支持嵌套,例如:

C#

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

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

C#

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 是否存在:

C#

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

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

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

C#复制

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 创建中间件管道部分。

其他资源生存期和注册选项包含范围内、临时性和单一实例生存期服务的中间件的完整示例 。写入自定义 ASP.NET Core 中间件测试 ASP.NET Core 中间件将 HTTP 处理程序和模块迁移到 ASP.NET Core中间件ASP.NET Core 中的应用启动ASP.NET Core 中的请求功能ASP.NET Core 中基于工厂的中间件激活使用 ASP.NET Core 中的第三方容器激活中间件

标签: #net常用中间件