路由基础知识
优惠券 https://www.fenfaw.cn/ 在项目新建的时候都会自动生成路由相关代码。在 Startup.Configure中的中间件管道注册的。主要涉及到的则是 UseRouting和 UseEndpoints中间件。 UseRouting向中间件添加路由匹配。此中间件还会查看应用中定义的终结点集。也就是把应用中的路由统统注册到中间件管道,方便请求的时候进行匹配。 UseEndpoints向中间件添加终结点执行。会运行相关联的委托。简单将就是路由匹配之后的处理事件运行。// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); }
例如上面的代码就是 HTPP GET 请求并且Url是/的时候需要执行的委托、如果这里的请求不是 Get请求或者不是"/",那么没有路由匹配,则会返回404。同时指定匹配模式的还有 MapDelete、MapMethods、MapPost、MapPut、Map等。
终结点
上面讲的 MapGet或者未用到 MapPost等就是用于定义终结点的。它们都包含有两个参数,一个是用于 Url匹配的,另外一个就是需要执行的委托。这里在不一样的应用中都采用了不同的终结点定义方法-
- 用于 Razor Pages 的 MapRazorPages
- 用于控制器的 MapControllers
- 用于 SignalR 的 MapHub
- 用于 gRPC 的 MapGrpcService
app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/healthz").RequireAuthorization(); endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); });
而且我们看中间的使用顺序, UseAuthentication、UseAuthorization是穿插在 UseRouting和 UseEndpoints中间的,如此写法则是为了授权策略能在 UseRouting中查找终结点,但是能在 UseEndpoints发送到终结点执行之前应用所选择的授权策略
终结点元数据
上面的示例展示了运行状况检查终结点附加了授权策略。添加的授权策略是额外数据,也就是终结点元数据。-
- 可以通过路由感知中间件来处理元数据。
- 元数据可以是任意的 .NET 类型。
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.Use(next => context => { var endpoint = context.GetEndpoint(); if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit ==true) { Console.WriteLine("开始处理事务逻辑"); Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}"); } return next(context); }); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello world!"); }); // Using metadata to configure the audit policy. endpoints.MapGet("/sensitive", async context => { await context.Response.WriteAsync($"sensitive data{DateTime.UtcNow}"); }) .WithMetadata(new AuditPolicyAttribute(needsAudit: true)); }); } } public class AuditPolicyAttribute : Attribute { public AuditPolicyAttribute(bool needsAudit) { NeedsAudit = needsAudit; } public bool NeedsAudit { get; } }看上面的示例中,在终结点绑定 "/sensitive"的时候会附加元数据 WithMetadata。当访问“/”的时候会输出 "Hello world!"。但是在 app.Use中并不会执行输出"处理事务逻辑",因为并没有匹配的元数据。但是当执行 "/sensitive"的时候就会输出 Console.WriteLine("开始处理事务逻辑");。因为在终结点定义的时候添加了元数据。元数据可以是人意.NET类型。上面的元数据也是我们自定义 Class。
比较终端中间件和路由
上面我们使用app.Use来检测匹配元数据,如果匹配成功我们就执行对应的操作。我们称之为终端中间件,为什么是终端中间件呢,因为这里会停止搜索执行匹配和操作、最后返回。 那么相比较下终端中间件和路由有什么区别呢?-
-
这两种方法都允许终止处理管道:终端中间件允许在管道中的任意位置放置中间件:
- 中间件通过返回而不是调用 next 来终止管道。
- 终结点始终是终端。
-
终端中间件允许在管道中的任意位置放置中间件:
-
- 终结点在 UseEndpoints 位置执行。
- 终端中间件允许任意代码确定中间件匹配的时间:
-
- 自定义路由匹配代码可能比较复杂,且难以正确编写。
- 路由为典型应用提供了简单的解决方案。
- 大多数应用不需要自定义路由匹配代码。
- 带有中间件的终结点接口,如 UseAuthorization 和 UseCors。
-
- 通过 UseAuthorization 或 UseCors 使用终端中间件需要与授权系统进行手动交互
设置传统路由
上面我们知道了通过UseRouting向中间件添加路由匹配,然后通过UseEndpoints定义终结点去执行匹配委托。那么在MVC模式中如何设置呢?我们看看传统路由的设置方法。app.UseEndpoints(endpoints => { app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); });
上面我们设置传统路由的时候采用的是endpoints.MapControllerRoute();,其中附带有两个参数,一个是名称default,第二个则是路由模板。我们看路由模板{controller=Home}/{action=Index}/{id?},那么在匹配Url路径的时候,例如执行路径 WeatherForecast/Index/5。那么则会匹配控制器为WeatherForecast,其中方法是Index并且参数是int类型的一个处理方法。REST Api 的属性路由
上面讲的是传统路由设置,那么对于Api项目的路由设置是如何的呢?REST Api 应使用属性路由将应用功能建模为一组资源。我们看下示例代码// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
在上面的代码中使用MapControllers调用。映射属性路由。我们看在使用的时候属性路由的使用方式。- Route[]
[ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { [Route("Index")] public string Index(int? id) { return "Test"; } }
[ApiController] [Route("[controller]/[action]")] public class WeatherForecastController : ControllerBase { public string Index(int? id) { return "Test"; } }
[ApiController] public class WeatherForecastController : ControllerBase { [Route("[controller]/Index")] public string Index(int? id) { return "Test"; } }
- Http[Verb]
[ApiController] public class WeatherForecastController : ControllerBase { [HttpGet("[controller]/Index")] public string Index(int? id) { return "Test"; } }
- Route[]和Http[Verb]混合使用
[ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { [HttpGet("Index")] public string Index(int? id) { return "Test"; } }
-
-
这两种方法都允许终止处理管道:终端中间件允许在管道中的任意位置放置中间件: