一. 概述
中间件 (也叫中间件组件) 是一种装配到应用管道以处理请求和响应的软件. 每个组件:(1)选择是否将请求传递到管道中的下一个组件;(2)可以在管道中的下一个组件之前和之后执行工作.
请求委托用于生成请求管道. 请求委托会处理每个 HTTP 请求. 使用以下方法配置请求委托: Run, Map, Use 扩展方法. 可以将单个请求委托作为匿名方法(称为内联中间件 in-line middleware) 或者可以在可重用类中定义. 这些可重用的类和内联匿名方法是中间件, 也称为中间件组件. 请求管道中的每个中间件组件负责调用管道中的下一个组件, 或使管道短路.
- (1) Run
- // 将终端中间件委托添加到应用程序的请求管道中.
- public static class RunExtensions
- {
- public static void Run(this IApplicationBuilder App, RequestDelegate handler);
- }
- (2) Map
- // 根据给定请求路径的匹配对请求管道进行分支.
- public static class MapExtensions
- {
- public static IApplicationBuilder Map(this IApplicationBuilder App, PathString pathMatch, Action<IApplicationBuilder> configuration);
- }
- (3) Use
- // 提供配置应用程序请求的机制
- public interface IApplicationBuilder
- {
- //....
- // 将中间件委托添加到应用程序的请求管道中.
- IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
- }
1.1 使用 IApplicationBuilder 创建中间件管道
在 Startup. Configure 方法中, 使用 IApplicationBuilder 来创建中间件管理. 每一个 use 开头的扩展方法将一个中间件添加到 IApplicationBuilder 请求管道中. 使用 Use 扩展方法来配置请求委托. 每个 use 的中间件类似如下声明:
- public static IApplicationBuilder Use[Middleware] (this IApplicationBuilder App )
- public static IApplicationBuilder Use[Middleware] (this IApplicationBuilder App , Action<T>)
ASP.NET Core 请求管道包含一系列请求委托, 依次调用. 下图演示了这一概念. 沿黑色箭头执行.
在 Startup. Configure 代码中, 一系列 use 请求委托中间件如下所示:
- App.UseHttpsRedirection();
- App.UseStaticFiles();
- App.UseCookiePolicy();
- App.UseMvc();
委托可以决定不将请求传递给下一个委托(中间件), 这就是对请求管道进行短路. 通常需要短路, 因为这样可以避免不必要的工作.
下面示例 是一个最简单的 ASP.NET core 应用程序, 用 run 方法配置请求委托, 设置单个委托处理处理所有请求. 此案例不包括实际的请求管道. 相反, 调用单个匿名函数以响应每个 HTTP 请求. 并用委托终止了管道.
- public class Startup
- {
- public void Configure(IApplicationBuilder App)
- {
- App.Run(async context =>
- {
- await context.Response.WriteAsync("Hello, World!");
- });
- }
- }
下面示例用 Use 方法将多个请求委托链接在一起, next 参数表示管道中的下一个委托. 可通过不调用 next 参数使管道短路.
- App.Use(async (context, next) =>
- {
- // 调用下一个委托(App.run)
- await next.Invoke();
- });
- App.Run(async context =>
- {
- await context.Response.WriteAsync("Hello, World!");
- });
1.2 中间件顺序
向 Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序, 以及响应的相反顺序. 此排序对于安全性, 性能和功能至关重要. 以下 Startup.Configure 方法将为常见应用方案添加中间件组件:
(1) 异常 / 错误处理
(2) HTTP 严格传输安全协议
(3) HTTPS 重定向
(4) 静态文件服务器
(5) Cookie 策略实施
(6) 身份验证
(7) 会话
- (8) MVC
- public void Configure(IApplicationBuilder App)
- {
- if (env.IsDevelopment())
- {
- // When the App runs in the Development environment:
- // Use the Developer Exception Page to report App runtime errors.
- // Use the Database Error Page to report database runtime errors.
- App.UseDeveloperExceptionPage();
- App.UseDatabaseErrorPage();
- }
- else
- {
- // When the App doesn't run in the Development environment:
- // Enable the Exception Handler Middleware to catch exceptions
- // thrown in the following middlewares.
- // Use the HTTP Strict Transport Security Protocol (HSTS)
- // Middleware.
- App.UseExceptionHandler("/Error");
- App.UseHsts();
- }
- // Use HTTPS Redirection Middleware to redirect HTTP requests to HTTPS.
- App.UseHttpsRedirection();
- // Return static files and end the pipeline.
- App.UseStaticFiles();
- // Use Cookie Policy Middleware to conform to EU General Data
- // Protection Regulation (GDPR) regulations.
- App.UseCookiePolicy();
- // Authenticate before the user accesses secure resources.
- App.UseAuthentication();
- // If the App uses session state, call Session Middleware after Cookie
- // Policy Middleware and before MVC Middleware.
- App.UseSession();
- // Add MVC to the request pipeline.
- App.UseMvc();
- }
(1) UseExceptionHandler 是添加到管道的第一个中间件组件. 该异常处理程序中间件可捕获稍后调用中发生的任何异常.
(2) UseStaticFiles 静态文件中间件, 应该在管道的早期调用. 这样它就可以处理请求和短路, 而不需要遍历其余组件. 静态文件中间件不提供授权检查. 它提供的任何文件, 包括 wwwroot 下的文件, 都是公开可访问的.
(3) UseAuthentication 身份验证中间件. 未经身份验证的请求不会短路, 但只有在特定的 Razor 页面或 MVC 控制器操作之后, 才会发生授权(和拒绝).
1.3 Use,Run 和 Map
配置 HTTP 管道可以使用 Use,Run 和 Map, 但各方法针对构建的中间件作用不同:
(1) Use[Middleware]中间件负责调用管道中的下一个中间件, 也可使管道短路(即不调用 next 请求委托).
(2) Run[Middleware]是一种约定, 一些中间件组件可能会公开在管道末端运行的 Run[Middleware]方法.
(3) Map 扩展用作约定来创建管道分支, Map * 创建请求管道分支是基于给定请求路径的匹配项.
- public void Configure(IApplicationBuilder App, IHostingEnvironment env)
- {
- App.Map("/Map1",HandleMapTest1);
- App.Map("/Map2", HandleMapTest2);
- // 其它请求地址
- App.Run(async context =>
- {
- await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
- });
- }
- 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");
- });
- }
Map 还支持嵌套, 下面的示例中, 请求访问 / level1/level2a 和 /level1/level2b 时进行不同逻辑处理:
- App.Map("/level1", level1App => {
- level1App.Map("/level2a", level2AApp => {
- // "/level1/level2a" processing
- });
- level1App.Map("/level2b", level2BApp => {
- // "/level1/level2b" processing
- });
- });
- 1.4 MapWhen
MapWhen 基于 url 给定谓词的结果创建请求管道分支. Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支. 在以下示例中, 谓词用于检测查询字符串变量 branch 是否存在, 如果存在使用新分支(HandleBranch).
- public void Configure(IApplicationBuilder App, IHostingEnvironment env)
- {
- //Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration
- App.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
- // 非匹配 branch 其它请求地址
- App.Run(async context =>
- {
- await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
- });
- }
- private static void HandleBranch(IApplicationBuilder App)
- {
- App.Run(async context =>
- {
- var branchVer = context.Request.Query["branch"];
- await context.Response.WriteAsync("Map Test 1");
- });
- }
二. 编写中间件
上面演示在请求管道中使用 use,map,run 方法, 来委托处理每个 HTTP 请求就是中间件. 通常中间件会封装在类中, 并且通过扩展方法公开. 下面示例是如何编写一个中间件组件. 处理逻辑是该中间件通过查询字符串设置当前请求的区域性.
- /// <summary>
- /// 自定义中间件实现类
- /// </summary>
- public class RequestCultureMiddleware
- {
- //using Microsoft.AspNetCore.Http
- private readonly RequestDelegate _next;
- /// <summary>
- /// 程序启动时调用
- /// </summary>
- /// <param name="next"></param>
- public RequestCultureMiddleware(RequestDelegate next)
- {
- this._next = next;
- }
- /// <summary>
- /// 每个页面请求时自动调用, 方法按约定命名, 必需是 Invoke 或 InvokeAsync
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public async Task InvokeAsync(HttpContext context)
- {
- var cultureQuery = context.Request.Query["culture"];
- if (!string.IsNullOrWhiteSpace(cultureQuery))
- {
- //using System.Globalization;
- var culture = new CultureInfo(cultureQuery);
- CultureInfo.CurrentCulture = culture;
- CultureInfo.CurrentUICulture = culture;
- }
- // Call the next delegate/middleware in the pipeline
- await _next(context);
- }
- }
- /// <summary>
- /// 通过扩展方法公开中间件
- /// </summary>
- public static class RequestCultureMiddlewareExtensions
- {
- public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
- {
- // 在管道中添加一个 use 的中间件
- return builder.UseMiddleware<RequestCultureMiddleware>();
- }
- }
- public void Configure(IApplicationBuilder App)
- {
- // 调用中间件
- App.UseRequestCulture();
- App.Run(async (context) =>
- {
- await ResponseAsync(context);
- });
- }
- private async Task ResponseAsync(HttpContext context)
- {
- context.Response.ContentType = "text/html; charset=utf-8";
- await context.Response.WriteAsync(
- // 打印当前显示的语言
- $"Hello { CultureInfo.CurrentCulture.DisplayName }"
- );
- }
2.1 请求依赖项
由于中间件是在应用启动时构造的(实例), 而不是在每个请求时的, 因此在每个请求过程中, 中间件构造函数使用的作用域生命周期服务, 不会在每个请求期间与其他依赖注入类型共享. 如果必须在中间件和其他类型之间共享一个范围服务, 请将这些服务添加到 Invoke 方法的签名. Invoke 方法可接受由 DI 填充的参数:
- public class CustomMiddleware
- {
- private readonly RequestDelegate _next;
- public CustomMiddleware(RequestDelegate next)
- {
- _next = next;
- }
- // IMyScopedService is injected into Invoke
- public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
- {
- svc.MyProperty = 1000;
- await _next(httpContext);
- }
- }
参考文献:
官方文档: ASP.NET Core 中间件
来源: https://www.cnblogs.com/MrHSR/p/10307795.html