前言
我是从. net 4.5 直接跳到. net core 3.x 的, 感觉 ASP.NET 这套东西最初是从 4.5 中的 owin 形成的.
目前官方文档重点是讲路由, 没有特别说明与传统路由的区别, 本篇主要介绍终结点路由的相关概念和如何使用, 不会详细介绍路由, 这个参考官方文档就 ok 了. 如果将来有机会研究到底层再深度剖析.
参考:
https://q.cnblogs.com/q/113644/
概述
最初我们访问 http://www.abc.com/a.aspx 时, 服务端是存在 a.aspx 这个文件的, 服务端根据此文件帮我们创建一个对应类的实例处理请求.
后来需求越来越复杂, 出现了路由, 目的是将请求地址与执行请求的处理器的直接关联, 变成映射关联, 映射规则由我们自己配置.
在 ASP.NET core 3.x 之前这个路由系统是包含在 mvc 内部的,.net framework 时代有个特殊的 HttpModule 来实现 mvc, 路由系统也包含其中..net core 是由有个特殊的中间件来实现 mvc 的, 路由系统就包含在这个中间件中.
这种方式有个问题, mvc 只是一个中间件, 路由系统包含在其中, 如果我们希望在 mvc 中间件之后加入其它中间件, 其它中间件是无法 (也许是不方便) 访问路由相关信息的.
另外 ASP.NET core 并不是只有 mvc, 还有 webapi,blazor,signlR, 接入 gRpc 等, 将来还有更多, 我们的路由系统能否提出来, 让所有框架都可以用?
因此出现了终结点路由, 我们说路由的根本目的是将用户请求地址, 映射为一个请求处理器, 最简单的请求处理器可以是一个委托 Func<HttpCotnext,Task>, 也可以是 mvc/webapi 中某个 controller 的某个 action, 所以从抽象的角度讲 一个终结点 就是一个处理请求的委托. 由于 mvc 中 action 上还有很多 attribute, 因此我们的终结点还应该提供一个集合, 用来存储与此请求处理委托的关联数据.
从抽象的角度可以简单理解为 一个终结点 = 处理请求的委托 + 与之关联的附加 (元) 数据. 对应到 mvc 来理解的话 终结点 = action + 应用其上的 attribute 集合. 但记住终结点是个抽象的概念, 并不只服务于 mvc, 原理大概如下:
在程序启动前我们应该定义好程序中有哪些终结点, 当然不是我们手动一个个定义, 而是根据目标框架自动生成, 针对 mvc 来说的话可以自动将程序中与路由匹配的 action 转换成对应的终结点, 其它框架应该也有对应的方式, 反正最终我们所有用来处理请求的东东都变成了终结点. 这步是在定义路由时自动完成的
除了定义终结点我们还要定义 请求路径 与 终结点的对应关系, 将来请求抵达时才能匹配找到合适的终结点来处理我们的请求, 这步相当于定义路由
我们还需要定义一个解析器, 当请求抵达时根据终结点与路径的对应关系找到终结点, 微软已定义好对应的中间件来表示这个解析器.
最后我们需要定义一个中间件, 在上面的中间件执行后 我们可以拿到与当前请求匹配的终结点, 最终调用它的委托处理请求, 这个中间件就是 mvc 中间件
到此 ASP.NET core 3.x 的中间件路由默认差不多就这样了, 此时我们可以定义自己的中间件, 放在步骤 3 后面, 拿到终结点做一些高级处理. 微软定义的一些中间件也是这个套路
如何使用
在通过 vs 默认模板创建 ASP.NET core 3.x 项目时, 在 startup 中会看到这样的代码
- App.UseRouting();
- App.UseEndpoints(endpoints => {
- endpoints.MapControllerRoute(
- name: "default",
- pattern: "{controller=Home}/{action=Index}/{id?}");
- });
注册路由
看代码的第 2 行. 它有如下 3 个任务
创建终结点定义, 针对 mvc 来说会自动将程序中与路由格式匹配上的 action 转换为终结点. 在第 5 行之后可以调试观察 endpoints.DataSource 属性, 生成好的终结点就在里面
建立 url 与终结点的对应关系, 这种关系存在哪? 我也不晓得
注册 mvc 中间件(它在将来请求抵达, 且之前有中间件解析得到与当前请求匹配的终结点后, 开始 mvc 旅程)
这里路由跟以前的写法差不多, 上面默认值啊, 约束啊就去看官方文档吧.
创建终结点也会参照属性路由, 微软推荐 webapi 使用属性路由, mvc 使用传统路由. 你会看到创建默认 webapi 项目时这样的 endpoints.MapControllers();
终结点进一步定制
默认情况下是根据定义的路由去找到匹配的 action 最后生成终结点, 这个生成终结点的过程我们是可以参与的, 具体办法是通过 endpoints.MapControllerRoute 的返回对象上调用相关扩展方法, 本质上是向终结点的创建过程加入一些委托, 将来创建终结点时, 这些委托将被调用, 代码如下:
- endpoints.MapControllerRoute(
- name: "default",
- pattern: "{controller=Home}/{action=Index}/{id?}").Add(endpointBuilder=> {
- // 通过 endpointBuilder 获取与 action 关联的数据, 比如 attribute 和其它元数据
- // 通过 endpointBuilder 插入我们向放进终结点的数据
- });
动态路由
App.UseEndpointsmvc 时就说明了使用 mvc 和 webapi 了, 默认情况下一个 action 会创建一个对应的终结点, 请求抵达时匹配到终结点就直接执行了. 但有时候我们希望自己控制一个请求过来时使用哪个 controller 的哪个 action, 具体做法:
定义一个类, 继承 DynamicRouteValueTransformer, 并注册到 IoC 容器中, 最后调用一个扩展方法, 看代码:
- class MyRouteValueTransformer : DynamicRouteValueTransformer
- {
- public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
- {
- // 通过 values 可以拿到原始路由数据
- // 可以替换或加入新的数据
- values.Add("controller", "jj");
- values.Add("action", "kkk");
- return new ValueTask<RouteValueDictionary>(values);
- }
- }
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddSingleton<MyRouteValueTransformer>();
- services.AddControllers();
- }
- endpoints.MapDynamicControllerRoute<MyRouteValueTransformer>("aaa/bbb/{id}");
这样将来请求抵达时, 解析得到终结点时会调用我们的 MyRouteValueTransformer, 我们可以获取已解析得到的路有数据, 然后选择替换 / 增加某些路由数据, 从而达到定制化
回退路由
默认情况下请求抵达时, 若没有找到匹配的终结点, 就直接 404 了, 我们希望当没有匹配到任何终结点时直接执行某个默认的终结点, 可以用如下方式:
endpoints.MapFallbackToController("{controller}/{action}/{id?}", "kkk", "jj");
当请求抵达时, 如果没有匹配到任何终结点, 则默认执行 jjController.kkk 方法. 可以想象得到此功能可能是通过动态路由实现的
还有几个相关的扩展方法, 有了上面的讲解, 估计你也能猜出是干嘛用的了. 关于路由注册就暂时说这么多
自定义中间件提前拿到终结点数据
App.UseRouting(); 对应概述中的步骤 3, 此扩展方法内部会注册一个中间件, 将来请求抵达时它会帮我们找到与当前请求匹配的终结点并存储在 HttpContext 中, 且匹配过程中解析得到的路由数据在 Request.RouteValues 中. 我们可以在它后面加入自己的中间件
- 1 App.UseRouting();
- 2 App.Use((conttext,next)=> {
- 3 var endpoint = conttext.GetEndpoint();// 拿到终结点
- 4 var routeData = conttext.Request.RouteValues;// 拿到路由数据
- // 做些牛 B 的事
- 5 return next();
- 6 });
来源: https://www.cnblogs.com/jionsoft/p/12115417.html