21 | 中间件:掌控请求处理过程的关键
这一节讲解一下如何通过中间件来管理请求处理过程
中间件工作原理
next 表示后面有一个委托,每一层每一层套下去可以在任意的中间件来决定在后面的中间件之前执行什么,或者说在所有中间件执行完之后执行什么
整个中间件的处理过程实际上有两个核心对象:
IApplicationBuilder
RequestDelegate:处理整个请求的委托
源码链接: https://github.com/witskeeper/geektime/tree/master/samples/MiddlewareDemo
IApplicationBuilder
代码语言:javascript复制namespace Microsoft.AspNetCore.Builder
{
public interface IApplicationBuilder
{
IServiceProvider ApplicationServices { get; set; }
IDictionary<string, object> Properties { get; }
IFeatureCollection ServerFeatures { get; }
// 最终它会 Build 返回一个委托
// 这个委托就是把所有的中间件串起来之后,合并成一个委托方法
// 这个方法的入参可以看下方委托的定义
RequestDelegate Build();
IApplicationBuilder New();
// 它可以让我们去注册我们的中间件,把委托注册进去,每一个委托的入参也是一个委托
// 这也就意味着可以把这些委托注册成一个链,就像上面的图显示的那样
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}
}
委托的定义
代码语言:javascript复制namespace Microsoft.AspNetCore.Http
{
// 委托的入参是 HttpContext,所有的注册中间件的委托实际上都是对 HttpContext 的处理
public delegate Task RequestDelegate(HttpContext context);
}
接着让我们看一下应用程序里面是怎么让它工作的?
之前课程讲过 Configure 方法是用来注册中间件的
代码语言:javascript复制app.UseMyMiddleware();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
根据刚才流程图表示的话,实际上中间件的执行顺序是跟注册顺序有关系的,最早注册的中间件它的权力是最大的,它可以越早的发生作用
中间件的注册实际上不仅仅是有上面展示的已有内置的中间件,实际上还可以用注册委托的方法来注册我们的逻辑
代码语言:javascript复制app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello");
});
因为这个中间件注册最早,而且不对后续的 next 做任何操作,所以启动之后无论输入什么都会输出 Hello
如果需要后续的中间件执行,那就意味着需要调用 next,可以在中间件执行之后再次 Hello 一次
代码语言:javascript复制app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello");
await next();
await context.Response.WriteAsync("Hello2");
});
启动程序报错:
代码语言:javascript复制System.InvalidOperationException: Headers are read-only, response has already started.
意味着一旦应用程序已经对 Response 输出内容,我们就不能对 header 进行操作了,但是可以在 Response 后续继续写出信息
代码语言:javascript复制app.Use(async (context, next) =>
{
//await context.Response.WriteAsync("Hello");
await next();
await context.Response.WriteAsync("Hello2");
});
实际上除了 Use 这种方式的话,还有 Map 的方式
代码语言:javascript复制app.Map("/abc", abcBuilder =>
{
abcBuilder.Use(async (context, next) =>
{
//await context.Response.WriteAsync("Hello");
await next();
await context.Response.WriteAsync("Hello2");
});
});
启动程序不会直接看到 Hello 输出,如果把地址改为 localhost:5001/abc,我们的输出就会变成 Hello2
也就是说当我们需要对特定的路径进行指定中间件的时候可以这样做