理解ASP.NET Core 中间件(Middleware)

中间件

先借用微软官方文档的一张图:

理解ASP.NET Core 中间件(Middleware)

可以看到,中间件实际上是一种配置在HTTP请求管道中,用来处理请求和响应的组件。它可以:

决定是否将请求传递到管道中的下一个中间件

可以在管道中的下一个中间件处理之前和之后进行操作

此外,中间件的注册是有顺序的,书写代码时一定要注意!

中间件管道 Run

该方法为HTTP请求管道添加一个中间件,并标识该中间件为管道终点,称为终端中间件。也就是说,该中间件就是管道的末尾,在该中间件之后注册的中间件将永远都不会被执行。所以,该方法一般只会书写在Configure方法末尾。

public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); } }

Use

通过该方法快捷的注册一个匿名的中间件

public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // 下一个中间件处理之前的操作 Console.WriteLine("Use Begin"); await next(); // 下一个中间件处理完成后的操作 Console.WriteLine("Use End"); }); } }

注意:

1.如果要将请求发送到管道中的下一个中间件,一定要记得调用next.Invoke / next(),否则会导致管道短路,后续的中间件将不会被执行

2.在中间件中,如果已经开始给客户端发送Response,请千万不要调用next.Invoke / next(),也不要对Response进行任何更改,否则,将抛出异常。

3.可以通过context.Response.HasStarted来判断响应是否已开始。

以下都是错误的代码写法

错误1:

public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("Use"); await next(); }); app.Run(context => { // 由于上方的中间件已经开始 Response,此处更改 Response Header 会抛出异常 context.Response.Headers.Add("test", "test"); return Task.CompletedTask; }); } }

错误2

public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("Use"); // 即使没有调用 next.Invoke / next(),也不能在 Response 开始后对 Response 进行更改 context.Response.Headers.Add("test", "test"); }); } }

UseWhen

通过该方法针对不同的逻辑条件创建管道分支。需要注意的是:

进入了管道分支后,如果管道分支不存在管道短路或终端中间件,则会再次返回到主管道。

当使用PathString时,路径必须以“/”开头,且允许只有一个'https://www.jb51.net/'字符

支持嵌套,即UseWhen中嵌套UseWhen等

支持同时匹配多个段,如 /get/user

public class Startup { public void Configure(IApplicationBuilder app) { // /get 或 /get/xxx 都会进入该管道分支 app.UseWhen(context => context.Request.Path.StartsWithSegments("/get"), app => { app.Use(async (context, next) => { Console.WriteLine("UseWhen:Use"); await next(); }); }); app.Use(async (context, next) => { Console.WriteLine("Use"); await next(); }); app.Run(async context => { Console.WriteLine("Run"); await context.Response.WriteAsync("Hello World!"); }); } }

当访问 /get 时,输出如下:

UseWhen:Use
Use
Run

如果你发现输出了两遍,别慌,看看是不是浏览器发送了两次请求,分别是 /get 和 /favicon.ico

Map

通过该方法针对不同的请求路径创建管道分支。需要注意的是:

一旦进入了管道分支,则不会再回到主管道。

使用该方法时,会将匹配的路径从HttpRequest.Path 中删除,并将其追加到HttpRequest.PathBase中。

路径必须以“/”开头,且不能只有一个'https://www.jb51.net/'字符

支持嵌套,即Map中嵌套Map、MapWhen(接下来会讲)等

支持同时匹配多个段,如 /post/user

public class Startup { public void Configure(IApplicationBuilder app) { // 访问 /get 时会进入该管道分支 // 访问 /get/xxx 时会进入该管道分支 app.Map("/get", app => { app.Use(async (context, next) => { Console.WriteLine("Map get: Use"); Console.WriteLine($"Request Path: {context.Request.Path}"); Console.WriteLine($"Request PathBase: {context.Request.PathBase}"); await next(); }); app.Run(async context => { Console.WriteLine("Map get: Run"); await context.Response.WriteAsync("Hello World!"); }); }); // 访问 /post/user 时会进入该管道分支 // 访问 /post/user/xxx 时会进入该管道分支 app.Map("/post/user", app => { // 访问 /post/user/student 时会进入该管道分支 // 访问 /post/user/student/1 时会进入该管道分支 app.Map("/student", app => { app.Run(async context => { Console.WriteLine("Map /post/user/student: Run"); Console.WriteLine($"Request Path: {context.Request.Path}"); Console.WriteLine($"Request PathBase: {context.Request.PathBase}"); await context.Response.WriteAsync("Hello World!"); }); }); app.Use(async (context, next) => { Console.WriteLine("Map post/user: Use"); Console.WriteLine($"Request Path: {context.Request.Path}"); Console.WriteLine($"Request PathBase: {context.Request.PathBase}"); await next(); }); app.Run(async context => { Console.WriteLine("Map post/user: Run"); await context.Response.WriteAsync("Hello World!"); }); }); } }

当你访问 /get/user 时,输出如下:

Map get: Use
Request Path: /user
Request PathBase: /get
Map get: Run

当你访问 /post/user/student/1 时,输出如下:

Map /post/user/student: Run
Request Path: /1
Request PathBase: /post/user/student

其他情况交给你自己去尝试啦!

MapWhen

与Map类似,只不过MapWhen不是基于路径,而是基于逻辑条件创建管道分支。注意事项如下:

一旦进入了管道分支,则不会再回到主管道。

当使用PathString时,路径必须以“/”开头,且允许只有一个'https://www.jb51.net/'字符

HttpRequest.Path和HttpRequest.PathBase不会像Map那样进行特别处理

支持嵌套,即MapWhen中嵌套MapWhen、Map等

支持同时匹配多个段,如 /get/user

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zysfdd.html