• [ASP.NET Core开发实战]基础篇03 中间件


    什么是中间件

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

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

    ASP.NET Core请求管道包含一系列请求委托,依次调用。工作原理:

    image

    PS:类似于ASP.NET里的Handler(处理程序)和Module(模块)。

    HTTP模块和处理程序的工作原理:

    image

    创建中间件管道

    Run委托不会收到next参数。第一个Run委托始终为终端,用于终止管道。Run是一种约定。

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

    警告:在向客户端发送Response后,不要再调用next.Invoke。Response启动后,针对HttpResponse的修改会引起异常。调用next后写入Response正文:

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

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

    中间件顺序

    中间件是通过请求管道逐一执行的,通过Startup.Configure方法里添加中间件的顺序来定义了请求管道的顺序。

    image

    如上图所示,自定义的中间件放在Authorization中间件和Endpoint之间。

    以下是ASP.NET Core项目默认配置的中间件顺序:

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

    中间件分支

    当我们需要对某些请求进行特定处理时,可以通过创建管道分支来实现。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. <p>");
            });
        }
    }
    

    MapWhen是用于对特定谓词的结果来创建请求管道分支。相比于Map(只能对针对特定的Url)更加高级。

    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不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道。

    public class Startup
    {
        private readonly ILogger<Startup> _logger;
    
        public Startup(ILogger<Startup> logger)
        {
            _logger = logger;
        }
    
        private void HandleBranchAndRejoin(IApplicationBuilder app)
        {
            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)
        {
            app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                                   HandleBranchAndRejoin);
    
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Hello from main pipeline.");
            });
        }
    }
    

    自定义中间件

    下面以多语言版本功能为例,介绍如何编写自定义中间件。

    多语言版本功能指根据所在区域,以所在区域的语言显示网站内容。例如在中国以汉字显示、在美国以英文显示、在俄罗斯以俄文显示。

    下面代码是直接在Startup.Configure编写,通过参数culture手动指定语言版本,例如请求https://localhost:5001/?culture=zh-cn

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Use(async (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
                await next();
            });
    
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    $"Hello {CultureInfo.CurrentCulture.DisplayName}");
            });
    
        }
    }
    

    一般来说,编写自定义中间件使用类,既符合单一职责原则,也更方便管理。

    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 async Task InvokeAsync(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
                await _next(context);
            }
        }
    }
    

    中间件类必须满足:

    • RequestDelegate类型参数的公共构造函数。
    • 有名字为InvokeInvokeAsync的方法,并且方法要满足:
      • 返回值是Task
      • 方法参数第一个是HttpContext类型。

    在实际开发中,中间件会用到其他服务,由于中间件是在应用启动时创建的,生命周期是Singleton的,不与其他依赖注入的服务共享生命周期。如果需要使用依赖注入,可以通过InvokeInvokeAsync方法注入。

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

    最后,我们习惯性在Startup.Configure中,通过扩展方法来注册中间件。

    using Microsoft.AspNetCore.Builder;
    
    namespace Culture
    {
        public static class RequestCultureMiddlewareExtensions
        {
            public static IApplicationBuilder UseRequestCulture(
                this IApplicationBuilder builder)
            {
                return builder.UseMiddleware<RequestCultureMiddleware>();
            }
        }
    }
    
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseRequestCulture();
    
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    $"Hello {CultureInfo.CurrentCulture.DisplayName}");
            });
        }
    }
    

    参考资料

  • 相关阅读:
    51Nod 1239 欧拉函数之和
    51Nod 1244 莫比乌斯函数之和
    BZOJ 4805: 欧拉函数求和
    BZOJ 3944: Sum
    3.25阅读摘抄
    生活整洁之道
    1064. 朋友数(20)
    1063. 计算谱半径(20)
    1061. 判断题(15)
    1062. 最简分数(20)
  • 原文地址:https://www.cnblogs.com/liang24/p/13303536.html
Copyright © 2020-2023  润新知