中间件的工作原理

ASP.NET Core 请求管道包含一系列请求委托,依次调用。
21 | 中间件:掌控请求处理过程的关键 - 图1
每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

核心对象

  • IApplicationBuilder
  • RequestDelegate
    • RequestDelegate就是我们处理整个请求的委托

看一下**IApplicationBuilder****RequestDelegate**的定义

  1. namespace Microsoft.AspNetCore.Builder
  2. {
  3. //定义提供配置应用程序请求的机制的类管道。
  4. public interface IApplicationBuilder
  5. {
  6. IServiceProvider ApplicationServices { get; set; }
  7. IDictionary<string, object> Properties { get; }
  8. IFeatureCollection ServerFeatures { get; }
  9. RequestDelegate Build();
  10. IApplicationBuilder New();
  11. // IApplicationBuilder可以让我们去注册我们的中间件
  12. IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
  13. }
  14. }

IApplicationBuilderUse可以让我们去注册我们的中间件,每一个委托的入参也是一个委托

RequestDelegate这个委托的入参就是一个HttpContext

  1. namespace Microsoft.AspNetCore.Http
  2. {
  3. public delegate Task RequestDelegate(HttpContext context);
  4. }

所有注册中间件的委托实际上都是对HttpContext的处理。

之前我们记录到过lStartup类,Configure方法是用来注册我们的中间件的。
根据上面流程图,发现中间件的执行顺序是跟我们的注册顺序是有关系的
最早注册的中间件它的权力最大,可以越早发生作用。

我们不仅仅可以使用内置的中间件,也可以使用注册委托的方式来注册我们的逻辑。

新建Web程序👉选择API模板👉Startup类,Configure方法

  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. app.Use(async (context, next) =>
  8. {
  9. // 不对next进行任何操作
  10. await context.Response.WriteAsync("Hello World!");
  11. });
  12. app.UseRouting();
  13. app.UseAuthorization();
  14. app.UseEndpoints(endpoints =>
  15. {
  16. endpoints.MapControllers();
  17. });
  18. }

我们不对next进行任何操作
执行程序:image.png
可以发现,页面只输出了Hello World!如果需要后续的中间件操作,那么要在最后执行**next**委托

  1. app.Use(async (context, next) =>
  2. {
  3. await context.Response.WriteAsync("Hello World!");
  4. await next();
  5. });

执行输出:image.png
发现页面是一片空白,这是因为,我们response已经启动。一旦我们的应用程序已经对**response**输出内容,我们就不能对**header**进行操作了,但是可以在response后续继续写出信息
我们将context.Response.WriteAsync("Hello World!")移动到next后面

  1. app.Use(async (context, next) =>
  2. {
  3. await next();
  4. await context.Response.WriteAsync("Hello World!");
  5. });

执行输出:image.png 可以发现,在Json后面添加了我们输出的HelloWorld
如果我们对response输出内容,还继续对header进行操作的话就会触发异常。可以通过 Context.Response.HasStarted来判断是否进行过操作。

除了Use这种方式之外,还有Map方式。
Map这个函数,它的作用是我们对特殊的路径,比如hi进行处理

  1. app.Map("/hi", hiBuilder =>
  2. {
  3. hiBuilder.Use(async (context, next) =>
  4. {
  5. await next();
  6. await context.Response.WriteAsync("Hello World!");
  7. });
  8. });

启动之后,将路径修改为hiimage.png

如果我们在Map的时候,逻辑复杂一点,不仅仅是判断它的URL地址,而且还要做特殊的判断的话,可以使用MapWhen
比如说,我们请求的地址中条件包含hi的时候
方法参数:image.png

  1. app.MapWhen(context => context.Request.Query.Keys.Contains("hi"), builder =>
  2. {
  3. builder.Run(async context => await context.Response.WriteAsync("hi!"));
  4. });

执行输出:image.png
这里我们用到了不同的方法Run
**Use**是指我们可以向注册一个完整的中间件一样,将我们的**next**也注入进来,我们可以去决定是否执行后续的中间件。
**Run**的含义就表示我们这里就是中间件执行的末端,也就不再执行后面的中间件了。

我们如何像UseRoutingUseEndpoints一样来设置我们自己的中间件呢
右键项目👉新建文件夹Middlewares👉新建类MyMiddlewareMyBuilderExtensions
定义中间件我们使用了一个约定的方式:我们的中间件的类包含了**Invoke**或者**InvokeAsync**这样一个方法,入参是**HttpContext**,返回是一个**Task**

  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.Extensions.Logging;
  3. using System.Threading.Tasks;
  4. namespace LoggingSerilogDemo.Middlewares
  5. {
  6. class MyMiddleware
  7. {
  8. private readonly ILogger<MyMiddleware> _logger;
  9. private readonly RequestDelegate _next;
  10. public MyMiddleware(ILogger<MyMiddleware> logger, RequestDelegate next)
  11. {
  12. _logger = logger;
  13. _next = next;
  14. }
  15. public async Task InvokeAsync(HttpContext context)
  16. {
  17. _logger.LogInformation("Begin!");
  18. await _next(context);
  19. _logger.LogInformation("End!");
  20. }
  21. }
  22. }

MyBuilderExtensions类定义扩展方法方便直接注册

  1. using LoggingSerilogDemo.Middlewares;
  2. namespace Microsoft.AspNetCore.Builder
  3. {
  4. public static class MyBuilderExtensions
  5. {
  6. public static IApplicationBuilder AddMyMiddleware(this IApplicationBuilder app)
  7. {
  8. return app.UseMiddleware<MyMiddleware>();
  9. }
  10. }
  11. }

Configure方法里注册app.AddMyMiddleware()
执行程序:image.png