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

    前言

      由于是第一次写博客,如果您看到此文章,希望大家抱着找错误、批判的心态来看。 sky!

    何为中间件?

    在 ASP.NET Framework 中应该都知道请求管道。可参考:浅谈 ASP.NET 的内部机制 系列,个人感觉超详细。

    题外话:
    说到请求管道,就想以前还是超菜鸟时有次面试被问到这个问题,一脸懵逼只说了 Controller→Action→View。脸红啊!!

    ASP.NET Core 中的中间件就是.net framework 请求管道的实现。下图演示了 Middlerware 的概念。 沿黑色箭头执行。

    每一个中间件(Middleware1、Middleware2...)都是一个委托,这一系列委托就组成了整个管道。

    中间件的写法

    1. 直接在Startup.cs类的Configure方法里写

      app.Use(async (context, next) =>
      {
          logger.LogInformation("中间件开始...");
          await next.Invoke(); //执行下一个中间件
          logger.LogInformation("中间件完成...");
      });
      

      结合上图:

      //logic对应logger.LogInformation("中间件开始...");

      next();对应await next.Invoke();

      //more logic对应logger.LogInformation("中间件完成...");

      其中//logic(即请求)是顺序执行。即:Middleware1→Middleware2→...→Middlewaren

      //more logic(即响应)是倒序执行。即:Middlewaren→...→Middleware2→Middleware1

    2. 同 1,只是不用 Use 而是用 Run:

       app.Run(async context =>
       {
           await context.Response.WriteAsync("请求终止了,下一步将会执行已执行过的Middleware的 //more logic");
       });
      

      Run 会终止请求,即管道中最后一个中间件,后面详细剖析!

    3. 下面这种写法应该是比较合理的,也是比较优雅的

      新建一个类如下(该类是有强制规范的,详细见下文):

      public class RequestTestMiddleware
      {
          private readonly RequestDelegate _next;
          public RequestTestMiddleware(RequestDelegate next)
          {
              _next = next;
          }
          public async Task InvokeAsync(HttpContext context)
          {
              //中间件开始 logic
              await _next(context);//执行下一个中间件
              //中间件完成 more logic
          }
      }
      

      Startup.cs类的Configure方法里添加如下代码,效果和 1 相同:

      app.UseMiddleware<RequestTestMiddleware>();
      //app.UseMiddleware<RequestTestMiddleware>(params object[] parameters);//参数说明见下面
      

      不知发现了没,上面的InvokeAsync方法不是用的打印日志,而是用的注释。
      因为我们没有引用logger对象,了解过 ASP.NET Core 的肯定知道依赖注入,我们只需要把ILogger注入进来就行了,改造如下:

      public class RequestTestMiddleware
       {
           private readonly RequestDelegate _next;
           public RequestTestMiddleware(RequestDelegate next)
           {
               _next = next;
           }
           public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
           {
               logger.LogInformation("中间件开始 logic");
               await _next(context);
               logger.LogInformation("中间件完成 more logic");
           }
       }
      
    4. 通过依赖注入方法添加中间件:
      新建类 TestMiddleware.cs 注意依赖注入的位置和 3 不同

      public class TestMiddleware : IMiddleware
       {
           private readonly ILogger _logger;
           public TestMiddleware(ILogger<TestMiddleware> logger)
           {
               _logger = logger;
           }
           public async Task InvokeAsync(HttpContext context, RequestDelegate next)
           {
               _logger.LogInformation("中间件开始");
               await next(context);
               _logger.LogInformation("中间件完成");
           }
       }
      

      Startup.cs类的ConfigureServices方法里添加如下代码:

      services.AddTransient<TestMiddleware>();
      

      Startup.cs类的Configure方法里添加如下代码:

      app.UseMiddleware<TestMiddleware>();
      
    5. 还有一种第三方容器激活中间件

    源代码分析(部分)

    1. RunUse的实现

      直接放出源代码:

       public static void Run(this IApplicationBuilder app, RequestDelegate handler)
       {
           if (app == null)
           {
               throw new ArgumentNullException(nameof(app));
           }
           if (handler == null)
           {
               throw new ArgumentNullException(nameof(handler));
           }
           app.Use(_ => handler);
       }
      
       public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
       {
           return app.Use(next =>
           {
               return context =>
               {
                   Func<Task> simpleNext = () => next(context);
                   return middleware(context, simpleNext);
               };
           });
       }
      

      2 个方法最终调用的都是app.Use(),我们看下代码:

      public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
      {
          _components.Add(middleware);
          return this;
      }
      

      _componentsIList<Func<RequestDelegate, RequestDelegate>>类型,其实就是把我们的Middleware添加到 _components 中,继续看代码:

      public RequestDelegate Build()
      {
          RequestDelegate app = context =>
          {
              context.Response.StatusCode = 404;
              return Task.CompletedTask;
          };
          foreach (var component in _components.Reverse())
          {
              app = component(app);
          }
          return app;
      }
      

      该方法会在Program.csMain方法的 CreateWebHostBuilder(args).Build().Run();Run() 方法执行。

      此方法把我们所有的Middleware再次组装成 1 个新的RequestDelegate,最终的顺序将会是:

      Middleware1()
      {
          next()=>Middleware2()
                  {
                      next()=>Middleware3()
                              {
                                  next()=>最后的那个返回404的委托
                              }
                  }
      }
      

      不知道写清楚了没( ╯□╰ ). 其中next()=>Middleware2()的意思为:next()就是 Middleware2()

    2. 继承 IMiddleware 和没继承 IMiddleware(根据规范必须要有 InvokeAsync 或 Invoke 方法等)的区别:

      按功能实现方面来说是没区别的,但按性能方面应该是继承了 IMiddleware 的方式要好很多,因为没继承 IMiddleware 的方式会用到反射。(未测试,由于继承 IMiddleware 还需要用依赖注入这里只是猜测)

      代码见:
      Microsoft.AspNetCore.Http.AbstractionsExtensionsUseMiddlewareExtensions.cs 的 UseMiddleware 方法。

    3. 未继承 IMiddleware 时的约定,直接看代码吧:

      //1.在middleware中必须存在public且有返回值的方法
      var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
      
      //2.必须有‘Invoke’或‘InvokeAsync’方法
      var invokeMethods = methods.Where(m =>
          string.Equals(m.Name, "Invoke", StringComparison.Ordinal)
          || string.Equals(m.Name, "InvokeAsync", StringComparison.Ordinal)
          ).ToArray();
      
      //3.‘Invoke’和‘InvokeAsync’只能有1个
      if (invokeMethods.Length > 1) {}
      
      //4.‘Invoke’和‘InvokeAsync’必须要存在
      if (invokeMethods.Length == 0) {}
      var methodInfo = invokeMethods[0];
      
      //5.返回结果类型必须为Task
      if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){}
      
    4. 中间件传参
      直接上代码:

      public class RequestTestMiddleware
      {
          private readonly RequestDelegate _next;
          private int _i;
          public RequestTestMiddleware(RequestDelegate next, int i)
          {
              _next = next;
              _i = i;
          }
          public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
          {
              logger.LogInformation($"通过参数传递i值:{_i}");
              logger.LogInformation("中间件开始");
              await _next(context);
              logger.LogInformation("中间件完成");
          }
      }
      

      Startup.cs类的Configure方法里:

      //参数类型为: params object[] args
      app.UseMiddleware<RequestTestMiddleware>(1);
      

      具体实现方式同样在 Microsoft.AspNetCore.Http.AbstractionsExtensionsUseMiddlewareExtensions.cs 的 UseMiddleware 方法中

    高级用法 Map MapWhen

    1. Map

      app.Map("/map", _app =>
      {
          _app.Run(async context =>
          {
              await context.Response.WriteAsync("Test Map!");
          });
      });
      

      当访问https://localhost:5001/map时将返回 Test Map!

      这里说一下,代码中并没有 MapController....

    2. MapWhen

      app.MapWhen(context => context.Request.Query.ContainsKey("branch"), _app =>
      {
          _app.Run(async context =>
          {
              await context.Response.WriteAsync("Test Map!");
          });
      });
      

      看源代码会发现,MapWhen 的第二个参数(委托)并不是上面Use()next(),而是存在MapOptionsBranch属性中,也是RequestDelegate委托

    其他说明

    1. Middleware 的执行的有顺序的,在合适的 Middleware 返回请求可时管道更短,速度更快。
      比如 UseStaticFiles(),静态资源不必走验证、MVC 中间件,所以该方法在中间件的前面执行。

    2. 我们看到有很多内置的中间件的用法是*Use**,其实是加了个扩展:

      public static class RequestCultureMiddlewareExtensions
      {
          public static IApplicationBuilder UseRequestCulture(
              this IApplicationBuilder builder)
          {
              return builder.UseMiddleware<RequestCultureMiddleware>();
          }
      }
      

    总结

      第一次写博客,最大的感触就是,然后就是思维逻辑有点混乱,总想用最简单的语言来表达,就是掌握不好。最后看起来还是太啰嗦了点。最后说明,以上很可能有错误的说法,希望大家以批判的角度来看,有任何问题可在留言区留言!Thanks!

  • 相关阅读:
    bzoj 1086 [SCOI2005]王室联邦——思路
    bzoj 3809 Gty的二逼妹子序列——莫队+分块
    bzoj 3781 小B的询问——分块
    bzoj 3309 DZY Loves Math——反演+线性筛
    洛谷 1079 Vigenère 密码——模拟水题
    洛谷 1082 同余方程——exgcd(水题)
    bzoj2662 [BeiJing wc2012]冻结 ——分层图
    bzoj2242 [SDOI2011]计算器——BSGS
    bzoj1008 [HNOI2008]越狱——快速幂
    bzoj1607 [Usaco2008 Dec]Patting Heads 轻拍牛头——暴力
  • 原文地址:https://www.cnblogs.com/dudd/p/9670028.html
Copyright © 2011-2022 走看看