中间件的工作原理
ASP.NET Core 请求管道包含一系列请求委托,依次调用。
每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。
核心对象
- IApplicationBuilder
- RequestDelegate
- RequestDelegate就是我们处理整个请求的委托
看一下**IApplicationBuilder**和**RequestDelegate**的定义
namespace Microsoft.AspNetCore.Builder{//定义提供配置应用程序请求的机制的类管道。public interface IApplicationBuilder{IServiceProvider ApplicationServices { get; set; }IDictionary<string, object> Properties { get; }IFeatureCollection ServerFeatures { get; }RequestDelegate Build();IApplicationBuilder New();// IApplicationBuilder可以让我们去注册我们的中间件IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);}}
IApplicationBuilder的Use可以让我们去注册我们的中间件,每一个委托的入参也是一个委托
RequestDelegate这个委托的入参就是一个HttpContext
namespace Microsoft.AspNetCore.Http{public delegate Task RequestDelegate(HttpContext context);}
所有注册中间件的委托实际上都是对HttpContext的处理。
之前我们记录到过lStartup类,Configure方法是用来注册我们的中间件的。
根据上面流程图,发现中间件的执行顺序是跟我们的注册顺序是有关系的,
最早注册的中间件它的权力最大,可以越早发生作用。
我们不仅仅可以使用内置的中间件,也可以使用注册委托的方式来注册我们的逻辑。
新建Web程序👉选择API模板👉Startup类,Configure方法
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.Use(async (context, next) =>{// 不对next进行任何操作await context.Response.WriteAsync("Hello World!");});app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}
我们不对next进行任何操作
执行程序:
可以发现,页面只输出了Hello World!,如果需要后续的中间件操作,那么要在最后执行**next**委托
app.Use(async (context, next) =>{await context.Response.WriteAsync("Hello World!");await next();});
执行输出:
发现页面是一片空白,这是因为,我们response已经启动。一旦我们的应用程序已经对**response**输出内容,我们就不能对**header**进行操作了,但是可以在response后续继续写出信息
我们将context.Response.WriteAsync("Hello World!")移动到next后面
app.Use(async (context, next) =>{await next();await context.Response.WriteAsync("Hello World!");});
执行输出:
可以发现,在Json后面添加了我们输出的HelloWorld
如果我们对response输出内容,还继续对header进行操作的话就会触发异常。可以通过 Context.Response.HasStarted来判断是否进行过操作。
除了Use这种方式之外,还有Map方式。Map这个函数,它的作用是我们对特殊的路径,比如hi进行处理
app.Map("/hi", hiBuilder =>{hiBuilder.Use(async (context, next) =>{await next();await context.Response.WriteAsync("Hello World!");});});
启动之后,将路径修改为hi:
如果我们在Map的时候,逻辑复杂一点,不仅仅是判断它的URL地址,而且还要做特殊的判断的话,可以使用MapWhen
比如说,我们请求的地址中条件包含hi的时候
方法参数:
app.MapWhen(context => context.Request.Query.Keys.Contains("hi"), builder =>{builder.Run(async context => await context.Response.WriteAsync("hi!"));});
执行输出:
这里我们用到了不同的方法Run,**Use**是指我们可以向注册一个完整的中间件一样,将我们的**next**也注入进来,我们可以去决定是否执行后续的中间件。**Run**的含义就表示我们这里就是中间件执行的末端,也就不再执行后面的中间件了。
我们如何像UseRouting,UseEndpoints一样来设置我们自己的中间件呢
右键项目👉新建文件夹Middlewares👉新建类MyMiddleware、MyBuilderExtensions
定义中间件我们使用了一个约定的方式:我们的中间件的类包含了**Invoke**或者**InvokeAsync**这样一个方法,入参是**HttpContext**,返回是一个**Task**
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Logging;using System.Threading.Tasks;namespace LoggingSerilogDemo.Middlewares{class MyMiddleware{private readonly ILogger<MyMiddleware> _logger;private readonly RequestDelegate _next;public MyMiddleware(ILogger<MyMiddleware> logger, RequestDelegate next){_logger = logger;_next = next;}public async Task InvokeAsync(HttpContext context){_logger.LogInformation("Begin!");await _next(context);_logger.LogInformation("End!");}}}
MyBuilderExtensions类定义扩展方法方便直接注册
using LoggingSerilogDemo.Middlewares;namespace Microsoft.AspNetCore.Builder{public static class MyBuilderExtensions{public static IApplicationBuilder AddMyMiddleware(this IApplicationBuilder app){return app.UseMiddleware<MyMiddleware>();}}}
在Configure方法里注册app.AddMyMiddleware()
执行程序:
