美文网首页
asp.net core 系列 15 中间件

asp.net core 系列 15 中间件

作者: 懒懒的程序员一枚 | 来源:发表于2019-06-09 10:57 被阅读0次

    一.概述

    中间件(也叫中间件组件)是一种装配到应用管道以处理请求和响应的软件。 每个组件:(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");
                });
            }
    
    image

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

    二. 编写中间件

    上面演示在请求管道中使用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 中间件

    相关文章

      网友评论

          本文标题:asp.net core 系列 15 中间件

          本文链接:https://www.haomeiwen.com/subject/femgxctx.html