Asp .Net core 2 学习笔记(2) —— 中间件
这个系列的初衷是便于自己总结与回顾,把笔记本上面的东西转移到这里,态度不由得谨慎许多,下面是我参考的资源:
记在这里的东西我会不断的完善丰满,对于文章里面一些局限于我自己知识积累的观点,希望没有跳走坚持看完的朋友,能够予以指正和鼓励.
系列目录
中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件
- 可在管道中的下一个组件前后执行工作
请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
请求委托通过使用 IApplicationBuilder 类型的 Run、Map 以及 Use 扩展方法来配置,并在Starup类中传给configure方法 。每个单独的请求委托都可以被指定为一个 内嵌匿名方法,或其定义在一个可重用的类中。这些可重用的类被称作中间件或中间件组件。每个位于请求管道内的中间件组件负责调用管道中下一个组件,或适时短路调用链。
使用 IApplicationBuilder 创建中间件管道
ASP.NET Core 请求管道包含一系列请求委托,依次调用
这系列委托并不是一条路走到底:每个委托均可在下一个委托前后执行操作,使得请求管道短路.
短路的方式存在两种:
1任何委托都能选择停止传递到下一个委托,转而自己处理该请求。这被叫做请求管道的短路
1 public void Configure(IApplicationBuilder app) 2 { 3 //只会打印"Hello, World!",下面的已经被短路 4 app.Run(async context => 5 { 6 await context.Response.WriteAsync("Hello, World!"); 7 }); 8 9 app.Run(async context => 10 { 11 await context.Response.WriteAsync("Hello, World, Again!"); 12 }); 13 }
所以,最简单的 ASP.NET 应用程序只需要单个请求委托来处理所有请求即可。而在这种情况下并不存在所谓的“管道”,调用单个匿名函数以响应每个 HTTP 请求
2委托可以不将请求传递给下一个委托时,它被称为“让请求管道短路”。
1 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 2 { 3 var logger = loggerFactory.CreateLogger(""); 4 app.Use(async (context, next) => 5 { 6 logger.LogInformation("Handling request."); 7 //await next.Invoke(); //next 参数表示管道内下一个委托.注释则短路 8 }); 9 10 app.Run(async (context) => 11 { 12 await context.Response.WriteAsync("the page lost"); 13 }); 14 }
那么用 Use 将多个请求委托链接(参见下面顺序一节示例)在一起时。 就可以通过不调用 next 参数使管道短路。
短路的存在是非常有意义的,这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件的作用。 如果
中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理next.Invoke 语句后面的代码。
Warnning
响应发送到客户端后,请勿调用next.Invoke。 响应开始之后,对HttpResponse的更改将抛出异常。
例如,设置响应头,状态代码等更改将会引发异常。在调用next之后写入响应体:
- 可能导致协议违规。 例如,写入超过content-length所述内容长度。
- 可能会破坏响应内容格式。 例如,将HTML页脚写入CSS文件。
HttpResponse.HasStarted是一个有用的提示,指示是否已发送响应头和/或正文已写入。
顺序
你添加中间件组件的顺序通常会影响到它们处理请求的顺序,然后在响应时则以相反的顺序返回。这对应用程序安全、性能和功能很关键。
以下为常见应用方案中间件组件顺序:
- 错误处理(同时针对于开发环境和非开发环境)
- 静态文件服务器
- 身份验证
- MVC
对应的代码如下:
1 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 2 { 3 loggerFactory.AddConsole(Configuration.GetSection("Logging")); 4 loggerFactory.AddDebug(); 5 6 if (env.IsDevelopment()) 7 { 8 app.UseDeveloperExceptionPage(); 9 app.UseDatabaseErrorPage(); 10 app.UseBrowserLink(); 11 } 12 else 13 { 14 app.UseExceptionHandler("/Home/Error"); 15 } 16 17 app.UseStaticFiles(); 18 19 app.UseIdentity(); 20 21 app.UseMvc(routes => 22 { 23 routes.MapRoute( 24 name: "default", 25 template: "{controller=Home}/{action=Index}/{id?}"); 26 }); 27 }
上面的代码中(在非开发环境时):
UseExceptionHandler 是第一个被加入到管道中的中间件,因此将会捕获之后调用中出现的任何异常。
静态文件模块 不提供授权检查,由它提供的任何文件,包括那些位于wwwroot 下的文件都是公开的可被访问的。如果你想基于授权来提供这些文件:
- 将它们存放在 wwwroot 外面以及任何静态文件中间件都可访问得到的目录。
- 利用控制器操作来判断授权是否允许,如果允许则通过返回 FileResult 来提供它们。
被静态文件模块处理的请求会在管道中被短路。如果该请求不是由静态文件模块处理,那么它就会被传给 Identity 模块 执行身份验证。如果未通过身份验证,则管道将被短路。如果请求的身份验证没有失败,则管道的最后一站是 MVC 框架。
如果修改Configure方法中间件添加的顺序,则上述预期的的功能将会发生变
Run Use Map
使用 Run、Map 和 Use 配置 HTTP 管道。
Run 方法将会短路管道,所以Run 应该只能在你的管道尾部被调用。并且某些中间件组件可公开在管道末尾运行的 Run[Middleware] 方法
Use用来构建请求管道,例如上面那套常用的请求管道,当然它也可以用来短路,发挥和run一样的作用,
Map*扩展被用于分支管道,支持基于请求路径或使用谓词来进入分支,Map 只接受路径,并配置单独的中间件管道的功能。
在下例中,任何基于路径 /maptest 的请求都会被管道中所配置的 HandleMapTest 方法所处理。
1 private static void HandleMapTest1(IApplicationBuilder app) 2 { 3 app.Run(async context => 4 { 5 await context.Response.WriteAsync("Map Test 1"); 6 }); 7 } 8 private static void HandleMapTest2(IApplicationBuilder app) 9 { 10 app.Run(async context => 11 { 12 await context.Response.WriteAsync("Map Test 2"); 13 }); 14 } 15 public void Configure(IApplicationBuilder app) 16 { 17 app.Map("/map1", HandleMapTest1); 18 app.Map("/map2", HandleMapTest2); 19 app.Run(async (context) => 20 { 21 await context.Response.WriteAsync("the page lost"); 22 }); 23 }
请求响应结果如下:
请求 | 响应 |
localhost:xxxx | the page lost |
localhost:xxxx/map1 | Map Test 1 |
localhost:xxxx/map2 | Map Test 2 |
localhost:xxxx/map3 | the page lost |
在使用 Map 时,将从 HttpRequest.Path 中删除匹配的线段,并针对每个请求将该线段追加到HttpRequest.PathBase 。
MapWhen基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量branch 是否存在:
1 private static void HandleBranch(IApplicationBuilder app) 2 { 3 app.Run(async context => 4 { 5 var branchVer = context.Request.Query["branch"]; 6 await context.Response.WriteAsync($"Branch used = {branchVer}"); 7 }); 8 } 9 public void Configure(IApplicationBuilder app) 10 { 11 app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch); 12 app.Run(async context => 13 { 14 await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); 15 }); 16 }
请求响应结果如下:
请求 | 响应 |
localhost:xxxx | Hello from non-Map delegate. |
localhost:xxxx/?branch=master | Branch used = master |
Map 支持嵌套,例如:
1 app.Map("/level1", level1App => 2 { 3 level1App.Map("/level2a", HandleMapTest1);// /level2a/level2a 4 level1App.Map("/level2b", HandleMapTest2);// /level2a/level2b 5 });
内置中间件
ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”,例如:静态文件中间件
中间件 | 描述 | 顺序 |
Authentication | 提供身份验证支持。 | 在需要 HttpContext.User 之前。OAuth 回调的终端。 |
Cookie Policy | 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secure和 SameSite )的最低标准 | 在发出 cookie 的中间件之前。 示例:身份验证、会话、MVC (TempData) |
CORS | 配置跨域资源共享 | 资源共享。 在使用 CORS 的组件之前。 |
Exception Handling | 处理异常 | 在生成错误的组件之前。 |
Forwarded Headers | 将代理标头转发到当前请求 | 在使用已更新字段的组件之前。 示例:方案、主机、客户端 IP、方法。 |
Health Check | 检查 ASP.NET Core 应用及其依赖项的运行状况,如检查数据库可用性。 |
如果请求与运行状况检查终结点匹 |
HTTP Method Override | 允许传入 POST 请求重写方法 | 在使用已更新方法的组件之前。 |
HTTPS Redirection |
将所有 HTTP 请求重定向到 |
在使用 URL 的组件之前。 |
HTTP Strict Transport Security (HSTS) |
添加特殊响应标头的安全增强中间 |
在发送响应之前,修改请求的组件之 |
MVC | 用 MVC/Razor Pages 处理请求(ASP.NET Core 2.0 或更高版本)。 | 如果请求与路由匹配,则为终端。 |
OWIN | 与基于 OWIN 的应用、服务器和中间件进行互操作。 | 如果 OWIN 中间件处理完请求,则为终端。 |
Response Caching | 提供对缓存响应的支持。 | 在需要缓存的组件之前。 |
Response Compression | 提供对压缩响应的支持。 | 在需要压缩的组件之前。 |
Request Localization | 提供本地化支持 | 在对本地化敏感的组件之前。 |
Routing | 定义和约束请求路由。 | 用于匹配路由的终端。 |
Session | 提供对管理用户会话的支持。 | 在需要会话的组件之前。 |
Static Files | 为提供静态文件和目录浏览提供支持。 | 如果请求与文件匹配,则为终端。 |
URL Rewriting | 提供对重写 URL 和重定向请求的支持。 | 在使用 URL 的组件之前。 |
WebSockets | 启用 WebSockets 协议。 | 在接受 WebSocket 请求所需的组件之前。 |
自定义中间件
对于更复杂的请求处理功能,ASP.NET 团队推荐在自己的类中实现中间件,并暴露 IApplicationBuilder 扩展方法,这样就能通过 Configure方法来被调用如下是一个自定义中间件实例,它从查询字符串设置当前请求的Culture:
1 public class RequestCultureMiddleWare 2 { 3 private readonly RequestDelegate _next; 4 public RequestCultureMiddleWare(RequestDelegate next) 5 { 6 _next = next; 7 } 8 9 public async Task InvokeAsync(HttpContext context) 10 { 11 var cultureQuery = context.Request.Query["culture"]; 12 if (!string.IsNullOrWhiteSpace(cultureQuery)) 13 { 14 var culture = new CultureInfo(cultureQuery); 15 CultureInfo.CurrentCulture = culture; 16 CultureInfo.CurrentUICulture = culture; 17 } 18 await _next(context); 19 } 20 }
通过IApplicationBuilder的扩展方法暴露中间件:
1 public static class RequestMiddleWareExtensions 2 { 3 public static IApplicationBuilder UserRequestCultrue(this IApplicationBuilder builder) 4 { 5 return builder.UseMiddleware<RequestCultureMiddleWare>(); 6 } 7 8 }
通过 Startup下Configure 方法调用中间件:
1 public void Configure(IApplicationBuilder app) 2 { 3 app.UseRequestCulture(); 4 app.Run(async (context) => 5 { 6 await context.Response.WriteAsync( 7 $"Hello {CultureInfo.CurrentCulture.DisplayName}"); 8 }); 9 }
请求依赖项
由于中间件是在应用启动时构造的,而不是按请求构造的,因此在每个请求过程中,中间件构造函数使用的范围内生存期服务不与其他依赖关系注入类型共享。 如果必须在中间件和其他类型之间共享范围内服务,请将这些服务添加到Invoke方法的签名Invoke,方法可接受由 DI 填充的其他参数:
1 public class CustomMiddleware 2 { 3 private readonly RequestDelegate _next; 4 public CustomMiddleware(RequestDelegate next) 5 { 6 _next = next; 7 } 8 // IMyScopedService is injected into Invoke 9 public async Task Invoke(HttpContext httpContext, IMyScopedService svc) 10 { 11 svc.MyProperty = 1000; 12 await _next(httpContext); 13 } 14 }
文档信息
- 发表作者: 半路独行
- 发表出处: 博客园
- 原文地址: https://www.cnblogs.com/banluduxing/p/10723130.html
- 版权信息:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮。本文欢迎各位转载,但是转载文章之后必须在文章页面中给出作者和原文连接。