zoukankan      html  css  js  c++  java
  • ASP.NET Core 中间件

    什么是中间件(Middleware)?
    中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
    • 选择是否将请求传递到管道中的下一个组件。
    • 可在管道中的下一个组件前后执行工作。
    请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
    使用 RunMap 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件 ,也叫中间件组件 。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件” ,因为它阻止中间件进一步处理请求。
    将 HTTP 处理程序和模块迁移到 ASP.NET Core中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。
    使用 IApplicationBuilder 创建中间件管道
    ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。
    每个委托可以在下一个委托之前和之后执行操作。委托还可以决定不将请求传递给下一个委托,这称为请求管道的短路。短路通常是可取的,因为它避免了不必要的工作。例如,静态文件中间件可以返回一个静态文件的请求,并使管道的其余部分短路。需要在管道早期调用异常处理委托,因此它们可以捕获后面管道的异常。
    最简单的可能是ASP.NET Core应用程序建立一个请求的委托,处理所有的请求。此案例不包含实际的请求管道。相反,针对每个HTTP请求都调用一个匿名方法。
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Hello, World!");
            });
        }
    }
    第一个 app.Run 委托终止管道。
    有如下代码:
    0
    通过浏览器访问,发现确实在第一个app.Run终止了管道。
    0
    您可以将多个请求委托与app.Use连接在一起。 next参数表示管道中的下一个委托。 (请记住,您可以通过不调用下一个参数来结束流水线。)通常可以在下一个委托之前和之后执行操作,如下例所示:
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
                {
                    await context.Response.WriteAsync("进入第一个委托 执行下一个委托之前
    ");
                    //调用管道中的下一个委托
                    await next.Invoke();
                    await context.Response.WriteAsync("结束第一个委托 执行下一个委托之后
    ");
                });
                app.Run(async context =>
                {
                    await context.Response.WriteAsync("进入第二个委托
    ");
                    await context.Response.WriteAsync("Hello from 2nd delegate.
    ");
                    await context.Response.WriteAsync("结束第二个委托
    ");
                });
        }
    }
    使用浏览器访问有如下结果:
    0
    可以看出请求委托的执行顺序是遵循上面的流程图的。
    注意:
    响应发送到客户端后,请勿调用next.Invoke。 响应开始之后,对HttpResponse的更改将抛出异常。 例如,设置响应头,状态代码等更改将会引发异常。在调用next之后写入响应体。
    • 可能导致协议违规。 例如,写入超过content-length所述内容长度。
    • 可能会破坏响应内容格式。 例如,将HTML页脚写入CSS文件。
    HttpResponse.HasStarted是一个有用的提示,指示是否已发送响应头和/或正文已写入。
    中间件顺序
    向 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.UseEndpoints(endpoints => { endpoints.MapRazorPages(); endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
    在上述代码中:
    • 在使用单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
    • 并非所有中间件都需要准确按照此顺序运行,但许多中间件必须遵循这个顺序。 例如,UseCors、UseAuthentication 和 UseAuthorization 必须按照上述顺序运行。
    以下 Startup.Configure 方法将为常见应用方案添加中间件组件:
    1.异常/错误处理
    • 当应用在开发环境中运行时:
    • 开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
    • 数据库错误页中间件报告数据库运行时错误。
      • 当应用在生产环境中运行时:
    • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
    • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
    2.HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
    3.静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
    4.Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
    5.用于路由请求的路由中间件 (UseRouting)。
    1. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
    2. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
    3. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
    4. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPages 的 UseEndpoints)。
     
    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 页或 MVC 控制器和操作后,才发生授权(和拒绝)。
    以下示例演示中间件排序,其中静态文件的请求在响应压缩中间件前由静态文件中间件进行处理。 使用此中间件顺序不压缩静态文件。 可以压缩 Razor Pages 响应。
    public void Configure(IApplicationBuilder app) { // Static files aren't compressed by Static File Middleware. app.UseStaticFiles(); app.UseResponseCompression(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); }
    对于单页应用程序,SPA 中间件 UseSpaStaticFiles 通常是中间件管道中的最后一个。 SPA 中间件处于最后的作用是:
    • 允许所有其他中间件首先响应匹配的请求。
    • 允许具有客户端侧路由的 SPA 针对服务器应用无法识别的所有路由运行。
    有关单页应用程序的详细信息,请参阅 React 和 Angular 项目模板的指南。
    对中间管道进行分支
    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>");
            });
        }
    }
    0
    下表显示了使用以前代码的 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"
               //...
           });
           level1App.Map("/level2b", level2BApp => {
               // "/level1/level2b"
               //...
           });
       });
    此外,Map 还可同时匹配多个段:
    app.Map("/level1/level2", HandleMultiSeg);
    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. <p>");
            });
        }
    }
    0
    以下下表显示了使用上面代码 http://localhost:19219 的请求和响应:
    请求
    响应
    localhost:19219
    Hello from non-Map delegate.
    localhost:19219/?branch=1
    Branch used = master
    内置中间件
    ASP.NET Core附带以下中间件组件:
    中间件
    描述
    顺序
    提供身份验证支持
    在需要 HttpContext.User 之前。 OAuth 回叫的终端。
    提供身份验证支持。跟踪用户是否同意存储个人信息,并强制实施
    紧接在身份验证中间件之后。
    cookie 字段(如 secure 和 SameSite)的最低标准。
    在发出 cookie 的中间件之前。 示例:身份验证、会话、MVC (TempData)。
    CORS
    配置跨域资源共享
     
     
    在使用 CORS 的组件之前。
     
    提供新应用的开发人员异常页、异常处理、状态代码页和默认网页的几个单独的中间件。
    在生成错误的组件之前。 异常终端或为新应用提供默认网页的终端。
    将代理标头转发到当前请求。
     
    在使用已更新字段的组件之前。 示例:方案、主机、客户端 IP、方法。
    检查 ASP.NET Core 应用及其依赖项的运行状况,如检查数据库可用性。
    检查数据库可用性。如果请求与运行状况检查终结点匹配,则为终端。
    允许传入 POST 请求重写方法。
    在使用已更新方法的组件之前。
    将所有 HTTP 请求重定向到 HTTPS。
    在使用 URL 的组件之前。
    添加特殊响应标头的安全增强中间件。
    在发送响应之前,修改请求的组件之后。 示例:转接头、URL 重写。
     
    用 MVC/Razor Pages 处理请求。
    如果请求与路由匹配,则为终端。
     
    与基于 OWIN 的应用、服务器和中间件进行互操作。
    如果 OWIN 中间件处理完请求,则为终端。
    提供缓存响应支持
     
    在需要缓存的组件之前。
    提供响应压缩支持
    在需要压缩的组件之前。
    提供本地化支持。
     
    在对本地化敏感的组件之前。
    定义和约束请求路由。
    用于匹配路由的终端。
    提供对管理用户会话的支持。
    在需要会话的组件之前
    为静态文件和目录浏览提供服务提供支持
    如果请求与文件匹配,则为终端。
    用于重写 Url,并将请求重定向的支持
     
    在使用 URL 的组件之前。
     
    启用 WebSockets 协议。
     
    在接受 WebSocket 请求所需的组件之前。

    编写中间件
    中间件通常封装在一个类中,并使用扩展方法进行暴露。 查看以下中间件,它从查询字符串设置当前请求的Culture:
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Use((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
                return next();
            });
    
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    $"Hello {CultureInfo.CurrentCulture.DisplayName}");
            });
    
        }
    }
    您可以通过传递Culture来测试中间件,例如 http://localhost:19219/?culture=zh-CN
    以下代码将中间件委托移动到一个类:
    using Microsoft.AspNetCore.Http;
    using System.Globalization;
    using System.Threading.Tasks;
    
    namespace Culture
    {
        public class RequestCultureMiddleware
        {
            private readonly RequestDelegate _next;
    
            public RequestCultureMiddleware(RequestDelegate next)
            {
                _next = next;
            }
    
            public Task Invoke(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
                return this._next(context);
            }
        }
    }
    以下通过IApplicationBuilder的扩展方法暴露中间件:
    using Microsoft.AspNetCore.Builder;
    
    namespace Culture
    {
        public static class RequestCultureMiddlewareExtensions
        {
            public static IApplicationBuilder UseRequestCulture(
                this IApplicationBuilder builder)
            {
                return builder.UseMiddleware<RequestCultureMiddleware>();
            }
        }
    }
    以下代码从Configure调用中间件:
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseRequestCulture();
    
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    $"Hello {CultureInfo.CurrentCulture.DisplayName}");
            });
    
        }
    }
    中间件应该遵循显式依赖原则,通过在其构造函数中暴露其依赖关系。 中间件在应用程序生命周期构建一次。 如果您需要在请求中与中间件共享服务,请参阅以下请求相关性。
    中间件组件可以通过构造方法参数来解析依赖注入的依赖关系。 UseMiddleware也可以直接接受其他参数。
    每个请求的依赖关系
    因为中间件是在应用程序启动时构建的,而不是每个请求,所以在每个请求期间,中间件构造函数使用的作用域生命周期服务不会与其他依赖注入类型共享。 如果您必须在中间件和其他类型之间共享作用域服务,请将这些服务添加到Invoke方法的签名中。 Invoke方法可以接受由依赖注入填充的其他参数。 例如:
    public class MyMiddleware
    {
        private readonly RequestDelegate _next;
    
        public MyMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
        {
            svc.MyProperty = 1000;
            await _next(httpContext);
        }
    }
  • 相关阅读:
    ajax简单案例
    jquery中的数据传输
    java-Reflect
    Factory Method 和AbstractFactory
    Singleton
    英语六级口语备考指南
    ACM信息汇总
    jquery练习
    char可不可以存汉字
    信息安全
  • 原文地址:https://www.cnblogs.com/hobelee/p/13972638.html
Copyright © 2011-2022 走看看