• .NET CORE 中间件


    什么是中间件

    对于中间件我们其实并不陌生,在.NET CORE出现之前中间件的概念在OWIN应用程序中就已经普遍使用了。
    中间件官方定义: 中间件是一种集成到应用管道中间来处理请求和响应的模块,每个中间件可以:

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

    ASP.NETCORE中的中间件本质上是一个请求委托 Func< RequestDelegate, RequestDelegate> middleware
    RequestDelegate本身也是一个委托,定义为 public delegate Task RequestDelegate(HttpContext Context)
    在ASP.NETCORE请求管道中,形成一条委托链。
    委托链

    请求管道短路:当委托不选择将请求传递到下一个委托时,称之为“短路”。

    如何创建中间件

    在ASP.NETCORE中,使用 IApplicationBuilder 来创建/插入中间件管道。提供了 RunUse 两类方式。依赖组件包 Microsoft.AspNetCore.Http.Abstractions
    Run是一种 约定 的终端管道,即短路,不再执行下一个委托

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
    		
            app.Run(async context => { await context.Response.WriteAsync("hello world 1"); });
    		//这里不会执行到!!
    		app.Run(async context => { await context.Response.WriteAsync("hello world 2"); });
    
        }
    

    Use通常以扩展方法提供中间件,很适合处理一些AOP的事务。

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            app.Use(async (context, next) =>
            {
                //可以在invoke之前做一些事
                await next.Invoke();
                //可以在invoke之后做一些事
            });
    
            app.Run(async context => { await context.Response.WriteAsync("hello world"); });
        }
    

    实际开发中我们通常需要自己定义中间件,有两种方式可以实现。

    约定方式

    public class RequestIdInRequestMiddleware
    {
        private readonly RequestDelegate _next;
    
        public RequestIdInRequestMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public Task Invoke(HttpContext httpContext,IService service)
        {
    		service.SayHello();
            //request head 加入requestid
            var requestId = Guid.NewGuid().ToString("n");
            httpContext.Request.Headers.Add("REQUESTID", requestId);
    
            return _next(httpContext);
        }
    }
    

    如上有以下约定:

    • 具有类型为 RequestDelegate 的参数公共构造函数
    • 名为 InvokeInvokeAsync 的公共方法,且此方法必须:
      • 返回 Task
      • 第一个参数为 HttpContext

    目前官方是推荐使用约定方式, 注意:该方式加入管道中的生命周期为单例。也因此如果依赖一些Service,建议从InvokeInvokeAsync的方法参数注入,而不是从构造函数注入。(可以想想为什么?单例构造函数注入对Service的生命周期有要求~~)。

    强类型

    官方也提供了IMiddleware接口,用于扩展创建中间件。这种方式有两个优点:

    • 可以按需(生命周期)注入

    • 中间件强类型话,更易理解

        public class RequestIdInResponseMiddleware:IMiddleware
        {
            private readonly IService _service;
      
            public RequestIdInResponseMiddleware(IService service)
            {
                _service = service;
            }
      
            public Task InvokeAsync(HttpContext context, RequestDelegate next)
            {
                var requestId = Guid.NewGuid().ToString("n");
                context.Response.Headers.Add("REQUESTID", requestId);
      
                return next(context);
            }
        }
      

    中间件加入管道

    中间件一般都是基于IApplicationBuilder扩展方法加入管道。

    public static class RequestIdMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestIdInResponseMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestIdInResponseMiddleware>();
        }
    }
    

    可以在 Configure 方法中调用加入 app.UseRequestIdInResponseMiddleware();
    如果是 强类型 方式创建的Middleware,还需要在 ConfigureServices 中注册 services.AddSingleton<RequestIdInResponseMiddleware>();

    中间件的顺序

    中间件显著受加入的顺序影响,官方提供的默认中间件顺序图
    中间件顺序

    中间件分支Map

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

    根据请求会响应不同结果

    请求 响应
    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.

    另外还可以使用 UseWhen 创建管道分支,只有匹配一定条件才会短路管道。

    public void Configure(IApplicationBuilder app)
    {
    	//只有请求url包含查询字符串变量 branch,才会短路管道
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                    builder => builder.Use(async (context, next) =>
                         {
                             var branchVer = context.Request.Query["branch"];
                             // Do work that doesn't write to the Response.
                             await next();
                             // Do other work that doesn't write to the Response.
                         }));
    
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
    

    中间件的单元测试

    针对中间件的单元测试,可以使用 TestServer 来进行。它有以下几个优点:

    • 请求会发送到内存中,而不是通过网络进行序列化
    • 避免产生额外的问题,例如端口号或Https等
    • 中间件中的异常可以直接流回调用测试
    • 可以直接在测试中自定义服务器数据结构,如 HttpContext

    http请求发送模拟可以使用 HttpClientHttpContext ,分别可以验证Response和Request Context相关功能。下面分别测试RequestIdInRequestMiddleware,RequestIdInResponseMiddleware。
    新建xunit单元测试项目,加入依赖包: Microsoft.AspNetCore.TestHost , Microsoft.Extensions.Hosting
    测试代码如下:

    public class MiddlewareTest
    {
        /// <summary>
        /// HttpContext模拟,验证request header是否成功加入requestId
        /// </summary>
        [Fact]
        public void MiddlewareTest_RequestHeaderExistRequestId()
        {
            var hostBuilder = new HostBuilder()
                .ConfigureWebHost(webBuilder =>
                {
                    webBuilder
                        .UseTestServer()
                        .ConfigureServices((context, services) =>
                        {
                            services.AddTransient<IService, MyService>();
                        })
                        .Configure(app =>
                        {
                            app.UseRequestIdInRequestMiddleware();
                        });
                });
            using (var host = hostBuilder.Start())
            {
                var context = host.GetTestServer().SendAsync(c =>
                        {
                            c.Request.Path = "/map";
                            c.Request.Method = HttpMethods.Get;
                        }).Result;
    
                Assert.True(context.Request.Headers.ContainsKey("REQUESTID"));
            }
        }
        /// <summary>
        /// HttpClient模拟,验证response header是否成功加入requestId
        /// </summary>
        [Fact]
        public void MiddlewareTest_ResponseHeaderExistRequestId()
        {
            var hostBuilder = new HostBuilder()
                .ConfigureWebHost(webBuilder =>
                {
                    webBuilder
                        .UseTestServer()
                        .ConfigureServices((context, services) =>
                        {
                            services.AddSingleton<RequestIdInResponseMiddleware>();
                            services.AddTransient<IService, MyService>();
                        })
                        .Configure(app =>
                        {
                            app.UseRequestIdInResponseMiddleware();
                        });
                });
            using (var host = hostBuilder.Start())
            {
                host.GetTestServer().CreateRequest("/map").GetAsync()
                    .ContinueWith(task =>
                    {
                        var response = task.Result;
                        Assert.True(response.Headers.Contains("REQUESTID"));
                    }).Wait();
            }
        }
    }
  • 相关阅读:
    jquery 菜单1
    Notepad++在vs2008下编译运行
    Linux readelf命令的使用
    Linux下truss、strace或ltrace的使用
    (转载)new和malloc的区别
    windows xp 设置回环网卡
    Linux下没有网线的情况下,从主机使用SSH登录虚拟机
    Linux ELF文件学习(1)
    C++子类继承父类
    (转载)wireshark抓包的时候有黄条、蓝条各代表什么意思?
  • 原文地址:https://www.cnblogs.com/gt1987/p/13042232.html
Copyright © 2020-2023  润新知