关键词:
Router的创建者——RouteBuilder
在《注册URL模式与HttpHandler的映射关系》演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来我们就着重来介绍这个对象。RouteBuilder是我们对所有实现了IRouteBuilder接口的所有类型以及对应对象的统称。[本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、RouteBuilder
二、RouteCollection
三、多个Route共享同一个Handler
四、每个Route具有独立的Handler
五、扩展方法MapVerb
一、RouteBuilder
如下面的代码片段所示,RouteBuilder对Router对象的创建体现在它的Build方法上。除此之外,IRouteBuilder接口还定义了一系列属性,我们可以利用它们得到用来注册中间件的ApplicationBuilder和用来提供服务的ServiceProvider。我们可以将多个Router注册到RouteBuilder上,这些注册的Router保存在Routes(不是Routers)属性上,而DefaultHandler属性返回一个默认的Router。
1: public interface IRouteBuilder
2: {
3: IApplicationBuilder ApplicationBuilder { get; }
4: IServiceProvider ServiceProvider { get; }
5: IRouter DefaultHandler { get; set; }
6: IList<IRouter> Routes { get; }
7:
8: IRouter Build();
9: }
ASP.NET Core默认使用的是如下一个实现了IRouteBuilder的RouteBuilder类型。如下面的代码片段所示,它的属性ApplicationBuilder是调用构造函数时通过相应的参数指定的,作为服务提供者的ServiceProvider则直接来源于这个ApplicationBuilder对象。至于最为核心的Build方法,我们可以看出它返回的实际上是通过注册的Router对象创建的一个RouteCollection对象。
1: public class RouteBuilder : IRouteBuilder
2: {
3: public IApplicationBuilder ApplicationBuilder { get; }
4: public IServiceProvider ServiceProvider { get; }
5: public IList<IRouter> Routes { get; }
6: public IRouter DefaultHandler { get; set; }
7:
8: public RouteBuilder(IApplicationBuilder applicationBuilder) : this(applicationBuilder, null){}
9:
10: public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter defaultHandler)
11: {
12: this.ApplicationBuilder = applicationBuilder;
13: this.ServiceProvider = applicationBuilder.ApplicationServices;
14: this.DefaultHandler = defaultHandler;
15: this.Routes = new List<IRouter>();
16: }
17:
18: public IRouter Build()
19: {
20: RouteCollection routes = new RouteCollection();
21: foreach (IRouter router in this.Routes)
22: {
23: routes.Add(router);
24: }
25: return routes;
26: }
27: }
二、RouteCollection
一个RouteCollection是一个特殊的Router,因为RouteCollection实现如下了如下这个IRouteCollection接口,后者最终实现了IRouter接口。一个RouteCollection对象实际上是对多个Router对象的封装,我们可以调用其Add方法添加封装的Router对象。
1: public interface IRouteCollection : IRouter
2: {
3: void Add(IRouter router);
4: }
为了更能更好的认识RouteCollection,尤其是实现在它的RouteAsync方法中路由解析原理,我们定义了如下这么一个模拟的类型。如下面的代码片段所示,当RouteAsync方法被执行的时候,它会遍历每个注册的Router对象并将当前RouteContext上下文作为参数调用它们的RouteAsync方法,直到遇到第一个与当前请求相匹配的Router。由于只有路由规则与当前请求相匹配的Router才会去设置RouteContext的Handler,所以判断Router是否与当前请求匹配的方法很简单,那就是判断当前RouteContext的Handler属性是否为null。
1: public class RouteCollection : IRouteCollection
2: {
3: private readonly List<IRouter> _routes = new List<IRouter>();
4:
5: public void Add(IRouter router)
6: {
7: _routes.Add(router);
8: }
9:
10: public async Task RouteAsync(RouteContext context)
11: {
12: var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);
13: foreach (var router in _routes)
14: {
15: context.RouteData.Routers.Add(router);
16: try
17: {
18: await router.RouteAsync(context);
19: if (null != context.Handler)
20: {
21: break;
22: }
23: }
24: finally
25: {
26: if (null == context.Handler)
27: {
28: snapshot.Restore();
29: }
30: }
31: }
32: }
33: public IRouter this[int index] => _routes[index];
34: public int Count => _routes.Count;
35: …
36: }
当整个路由解析流程完成之后,最终的RouteData的状态应该只与那个匹配的Router对象有关。换句话说,对于路由规则与当前请求不匹配的Router来说,针对它们的路由解析过程不应该“污染”最终的这个RouteData对象。为了达到这个目的,上面介绍的关于RouteData的快照机制被应用在这个RouteAsync方法上,上面所示的代码片段也体现了这一点。
由于RouteBuilder对RouterMiddleware中间件提供的Router对象实际上是一个RouteCollection对象,换句话说这其实是一个由多个Router对象组成的“路由表”。所谓的路由注册,本质上就是在这个路由表中添加相应的Router对象。RouteBuilder具有若干扩展方法帮助我们以一种很简洁的方式相这个路由表中添加Router,我们先来介绍如下这四个MapRoute重载。
1: public static class MapRouteRouteBuilderExtensions
2: {
3: public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template);
4: public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults);
5: public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints);
6: public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints, object dataTokens);
7: }
三、多个Route共享同一个Handler
上述这四个MapRoute方法执行之后在路由表中添加的都是一个Route对象,这个Route对象的名称、路由模板、路由参数的默认值和约束和DataToken都是由对应的参数来指定的。我们知道Route对象其实是对另一个Router对象的封装,那么被封装的究竟是个怎样的Router呢?
如上图所示,被注册的Route对象封装的其实是同一个Router,它就是RouteBuilder的DefaultHandler属性返回的那个Router。换句话说,通过调用这些MapRoute方法注册的Route采用同一个处理器来处理被成功路由的请求。所以当我们采用调用这些方法注册路由的时候要求这个RouteBuilder的DefaultHandler属性作了正确的设置。如下所示的代码体现了最后一个MapRoute方法的实现。
1: public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints, object dataTokens)
2: {
3: IInlineConstraintResolver requiredService = routeBuilder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
4: routeBuilder.Routes.Add(new Route(routeBuilder.DefaultHandler, name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), requiredService));
5: return routeBuilder;
6: }
对于我们在《注册URL模式与HttpHandler的映射关系》演示的关于获取天气预报信息的实例来说,我们也可以按照如下的形式调用RouteBuilder的MapRoute方法来注册所需的两个路由。为了以“流畅”的链式编程的方式来甚至RouteBuilder的默认处理器,我们特意定义了如下这个扩展方法SetDefaultHandler。
1: new WebHostBuilder()
2: .ConfigureServices(svcs => svcs.AddRouting())
3: .Configure(app =>app.UseRouter(builder=>builder
4: .SetDefaultHandler(WeatherForecast)
5: .MapRoute("route1", @"weather/{city:regex(^0\d{{2,3}}$)}/{days:int:range(1,4)}")
6: .MapRoute("route2", @"weather/{city:regex(^0\d{{2,3}}$)}/{@date}")))
7: …
8:
9: public static IRouteBuilder SetDefaultHandler(this IRouteBuilder builder, RequestDelegate handler)
10: {
11: builder.DefaultHandler = new RouteHandler(handler);
12: return builder;
13: }
对于上面通过调用MapRoute方法注册的两个Route对象来说,我们将路由约束以内联的形式直接定义在路由模板上,其实我们也可以将路由约束作为MapRoute方法的参数。如下面的代码片段所示,我们以不仅以参数的形式设置了路由约束,还设置了路由参数的默认值。
1: IRouteConstraint city = new RegexRouteConstraint(@"^0\d{2,3}$");
2: IRouteConstraint days = new CompositeRouteConstraint(new IRouteConstraint[] { new IntRouteConstraint(), new RangeRouteConstraint(1, 4) });
3:
4: new WebHostBuilder()
5: .ConfigureServices(svcs => svcs.AddRouting())
6: .Configure(app => app.UseRouter(builder => builder
7: .SetDefaultHandler(WeatherForecast)
8: .MapRoute(
9: name : "route1",
10: template : @"weather/{city}/{days}",
11: constraints : new { city = city, days = days },
12: defaults : new {city="010", days=4 })
13: .MapRoute(
14: name : "route2",
15: template : @"weather/{city}/{@date}",
16: constraints : new { city = city},
17: defaults :null)))
18: …
四、每个Route具有独立的Handler
上面介绍的这四个MapRoute方法重载都会在路由表中注册一个Route对象,它们都将RouteBuilder的DefaultHandler属性返回的Router作为默认的处理器。如果每个注册的Route具有如下图所示各自不同的请求处理逻辑,我们又该如何注册这样的Route呢?
如果需要为注册的Route指定不同的处理器来处理成功路由的请求,我们可以调用RouteBuilder如下两个同样命名为MapRoute的扩展方法。如上所示的这两个MapRoute方法依然会在路由表中注册一个Route对象。调用第一个方法重载除了需要指定一个路由模板之外,还需要显式指定作为请求处理器的RequestDelegate对象。
1: public static class RequestDelegateRouteBuilderExtensions
2: {
3: public static IRouteBuilder MapRoute(this IRouteBuilder builder, string template, RequestDelegate handler)
4: {
5: IInlineConstraintResolver resolver = builder.ServiceProvider.GetService< IInlineConstraintResolver>();
6: Route route = new Route(new RouteHandler(handler), template, null, null, null, resolver);
7: builder.Routes.Add(route);
8: return builder;
9: }
10:
11: public static IRouteBuilder MapRoute(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
12: {
13: IApplicationBuilder appBuilder = builder.ApplicationBuilder.New();
14: action(appBuilder);
15: return builder.MapRoute(template, appBuilder.Build());
16: }
17: }
对于我们实例来说,如果我们使用WeatherForecastForDays方法来返回未来指定天数的天气信息,而使用另一个方法WeatherForecastForDate来返回指定日期的天气信息,那么我们就可以采用如下的形式调用上面这个MapRoute方法来注册所需的两个路由。
1: new WebHostBuilder()
2: .ConfigureServices(svcs => svcs.AddRouting())
3: .Configure(app => app.UseRouter(builder => builder
4: .MapRoute(@"/weather/{city:^0\d{{2,3}}$}/{days:int:range(1,2)", WeatherForecastForDays)
5: .MapRoute(@"/weather/{city:^0\d{{2,3}}$}/{*date}", WeatherForecastForDate)))
6: …
7:
8: public static Task WeatherForecastForDays(HttpContext context);
9: public static Task WeatherForecastForDate(HttpContext context);
另一个MapRoute方法除了接收一个作为路由模板的字符串作为第一个参数之外,它的第二个参数是一个类型为Action<IApplicationBuilder>的委托对象。我们可以利用这个委托注册一个或者多个中间件,这些中间件最终会装换成一个RequestDelegate对象并作为注册Route的处理器。如下所示的代码片段展示了这个方法重载的实现。如果改用这个MapRoute方法来注册我们实例中所需的两个路由,我们可以采用如下的编程方式。
1: new WebHostBuilder()
2: .ConfigureServices(svcs => svcs.AddRouting())
3: .Configure(app => app.UseRouter(builder => builder
4: .MapRoute(@"/weather/{city:^0\d{{2,3}}$}/{days:int:range(1,2)",appBuilder => appBuilder.Run(WeatherForecastForDays))
5: .MapRoute(@"/weather/{city:^0\d{{2,3}}$}/{*date}", appBuilder=> appBuilder.Run(WeatherForecastForDate))))
6: …
五、扩展方法MapVerb
在《注册URL模式与HttpHandler的映射关系》演示的实例中,我们实际上是调用RouteBuilder的另一个名为MapGet的扩展方法来进行路由注册的,这个方法要求被成功路由的HTTP请求必须采用GET方法。除了针对GET请求,RouteBuilder还具有如下这些针对POST、PUT和DELETE请求的扩展方法(MapPost、MapPut和MapDelete)。
1: public static class RequestDelegateRouteBuilderExtensions
2: {
3: public static IRouteBuilder MapGet(this IRouteBuilder builder, string template, RequestDelegate handler);
4: public static IRouteBuilder MapGet(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action);
5:
6: public static IRouteBuilder MapPost(this IRouteBuilder builder, string template, RequestDelegate handler);
7: public static IRouteBuilder MapPost(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action);
8:
9: public static IRouteBuilder MapPut(this IRouteBuilder builder, string template, RequestDelegate handler);
10: public static IRouteBuilder MapPut(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action);
11:
12: public static IRouteBuilder MapDelete(this IRouteBuilder builder, string template, RequestDelegate handler);
13: public static IRouteBuilder MapDelete(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action);
14:
15: public static IRouteBuilder MapVerb(this IRouteBuilder builder, string verb, string template, RequestDelegate handler);
16: public static IRouteBuilder MapVerb(this IRouteBuilder builder, string verb, string template, Action<IApplicationBuilder> action);
17: }
实际上MapGet、MapPost、MapPut和MapDelete方法重载最终都会调用MapVerb方法,后者可以采用字符串的形式指定任意HTTP方法名称(比如“HEAD”和“OPTIONS”等)。这些方法针对HTTP方法的过滤是同一个类型为HttpMethodRouteConstraint的路由约束来实现的,它要求被路由的请求必须采用指定的方法。这两个MapVerb方法重载的实现原理体现在如下所示的代码片段中。
1: public static IRouteBuilder MapVerb(this IRouteBuilder builder, string verb, string template, RequestDelegate handler)
2: {
3: string[] allowedMethods = new string[] { verb };
4: Route item = new Route(new RouteHandler(handler), template, null, new RouteValueDictionary(new {
5: httpMethod = new HttpMethodRouteConstraint(allowedMethods) }), null, GetConstraintResolver(builder));
6: builder.Routes.Add(item);
7: return builder;
8: }
9:
10: public static IRouteBuilder MapVerb(this IRouteBuilder builder, string verb,string template, Action<IApplicationBuilder> action)
11: {
12: IApplicationBuilder builder2 = builder.ApplicationBuilder.New();
13: action(builder2);
14: return builder.MapVerb(verb, template, builder2.Build());
15: }
ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系
ASP.NET Core的路由[2]:路由系统的核心对象——Router
ASP.NET Core的路由[3]:Router的创建者——RouteBuilder
ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件
ASP.NET Core的路由[5]:内联路由约束的检验
如何在 Apache Camels RouteBuilder.restConfiguration() 中添加 OpenApi/Swagger securitySchemes?
】如何在ApacheCamelsRouteBuilder.restConfiguration()中添加OpenApi/SwaggersecuritySchemes?【英文标题】:HowtoaddOpenApi/SwaggersecuritySchemesinApacheCamelsRouteBuilder.restConfiguration()?【发布时间】:2022-01-1720:20:31【问题描述】:我尝试添加springdoc 查看详情
在 Apache Camel RouteBuilder 配置中,.id() 和 .routeId() 有啥区别
】在ApacheCamelRouteBuilder配置中,.id()和.routeId()有啥区别【英文标题】:InApacheCamelRouteBuilderconfigure,whatisthedifferencebetween.id()and.routeId()在ApacheCamelRouteBuilder配置中,.id()和.routeId()有什么区别【发布时间】:2018-02-1408:54:28【问题描述】... 查看详情
vue-routerv4.x版本创建router对象注意点
vue-router的v4.x版本是为适应vue3而开发的版本,它的引入和创建实例对象的方式有所改变,在使用的时候容易忽略变化导致低级错误。1、采用旧的导入方式 在npm管理依赖包的开发模式下使用import导... 查看详情
使用 ui-router 创建路由参数
】使用ui-router创建路由参数【英文标题】:creatingarouteParamusingui-router【发布时间】:2015-11-2102:51:11【问题描述】:我无法设置和维护路由参数。当我设置参数时。它去正确的地方。但是,如果我更改路线,内容不会改变。我试图... 查看详情
UI-Router 不使用 ui-sref 注册动态创建的元素
】UI-Router不使用ui-sref注册动态创建的元素【英文标题】:UI-RouterdoesnotRegisterDynamicallyCreatedElementwithui-sref【发布时间】:2017-01-2304:32:42【问题描述】:我使用UI-Router创建了一个SPA来管理我的视图。导航栏最初是一个静态导航栏,... 查看详情
如何使用 react-router-dom 创建动态路由?
】如何使用react-router-dom创建动态路由?【英文标题】:Howtocreatedynamicrouteswithreact-router-dom?【发布时间】:2019-11-2506:51:31【问题描述】:我学习反应并知道如何创建静态路由,但无法弄清楚动态路由。也许有人可以解释一下,我... 查看详情
Vuetify - 将道具传递给由 router-link 创建的标签
】Vuetify-将道具传递给由router-link创建的标签【英文标题】:Vuetify-Passingpropstothetagcreatedbyrouter-link【发布时间】:2020-12-1003:36:35【问题描述】:当使用tag覆盖用于创建链接的标签时,如何将props传递给创建的Vue组件?在这里您可以... 查看详情
vue-routerv4.x版本创建router对象注意点
vue-router的v4.x版本是为适应vue3而开发的版本,它的引入和创建实例对象的方式有所改变,在使用的时候容易忽略变化导致低级错误。1、采用旧的导入方式 在npm管理依赖包的开发模式下使用import导... 查看详情
如何使用 Vue Router 创建锚标签
】如何使用VueRouter创建锚标签【英文标题】:HowtocreateanchortagswithVueRouter【发布时间】:2017-03-1312:31:37【问题描述】:我正在创建一个小的Vuewebapp,我想在其中创建一个锚标记。我已将id分配给div之一,我想拥有这样的锚标签:<... 查看详情
如何使用 Vue Router 创建锚标签
】如何使用VueRouter创建锚标签【英文标题】:HowtocreateanchortagswithVueRouter【发布时间】:2017-03-1312:31:37【问题描述】:我正在创建一个小的Vuewebapp,我想在其中创建一个锚标记。我已将id分配给div之一,我想拥有这样的锚标签:<... 查看详情
vue-router 总是创建一个新的 Component 实例
】vue-router总是创建一个新的Component实例【英文标题】:vue-routercreatesalwaysanewComponentinstance【发布时间】:2018-08-3115:29:54【问题描述】:我在vue-router中发现了一个让我很受触动的问题。当我在我的路线之间切换时,总是会创建一... 查看详情
vue-router路由的简单使用
...-cli脚手架搭建,项目创建过程中会提示你自否选择使用vue-router,选择使用即可, 二.创建组件 1.vue-cli项目自动创建的路由文件是src包下面的router.js文件,你也可以创建一个文件夹.单独放置路由的js文件 例如, 差&nbs... 查看详情
vue-routerv4.x版本创建router对象注意点
vue-router的v4.x版本是为适应vue3而开发的版本,它的引入和创建实例对象的方式有所改变,在使用的时候容易忽略变化导致低级错误。1、采用旧的导入方式 在npm管理依赖包的开发模式下使用import导... 查看详情
vue中路由的创建跳转
...route.js文件,创建路由,导出路由。 //创建路由,从vue-router中导入两个方法。 importcreateWebHistory,createRouterfrom"vue-router"; //导入需要跳转页面的文件 importFlexWork1from"../views/FlexWork1"; //创建路由模式 consthistory=creat... 查看详情
vue-router总结(代码片段)
之前写过一篇关于vue-router的文章,主要是介绍怎么结合cli2在项目中使用vue-router,比较的简单,今天想结合cli3来总结一下vue-router的具体用法。cli3在介绍vue-router前,先简单介绍一下cli3吧。1,安装vue-cli3:npminstall-g@vue/cli2,vue-cli... 查看详情
vue3的路由创建
下载路由npminstallvue-router@4在文件夹创建router文件夹然后写入importHomefrom'./components/Home.vue';importAfrom'./components/a.vue';importcreateRouter,createWebHistoryfrom'vue-router';//和vu 查看详情
跟我一起探索neutron-router
(二层配置的是linuxbridge;l3agent提供路由router服务)实验环境:创建了4个网络provider:flat网、外网net1、net2、net3均为vxlan网络对应涉及5个虚拟机provider-instance、self-instance为分别指定provider、net1网络所创建的虚拟机net1-vm、net2-vm、... 查看详情
vue-router编程式导航
Vue-Router(三)编程式导航在vue中,我们除了使用创建a标签来定义导航链接之外,还可以使用router实例方法,通过编写代码的方式来实现router.push(location)想要导航到不容的URL,我们可以使用创建多个,当然也可以使用router.push()方法... 查看详情