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]:内联路由约束的检验
作者:蒋金楠 
微信公众账号:大内老A
微博:www.weibo.com/artech

Router的创建者——RouteBuilder的更多相关文章

  1. ASP.NET Core的路由[3]:Router的创建者——RouteBuilder

    在<注册URL模式与HttpHandler的映射关系>演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来 ...

  2. ASP.NET Core的路由[2]:路由系统的核心对象——Router

    ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路由参数的形式解析出来供后续请求处理流 ...

  3. 路由系统的核心对象——Router

    路由系统的核心对象--Router ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路 ...

  4. ASP.NET Core框架揭秘(持续更新中…)

    之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进 ...

  5. ASP.NET Core的路由[5]:内联路由约束的检验

    当某个请求能够被成功路由的前提是它满足某个Route对象设置的路由规则,具体来说,当前请求的URL不仅需要满足路由模板体现的路径模式,请求还需要满足Route对象的所有约束.路由系统采用IRouteC ...

  6. ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件

    虽然ASP.NET Core应用的路由是通过RouterMiddleware这个中间件来完成的,但是具体的路由解析功能都落在指定的Router对象上,不过我们依然有必要以代码实现的角度来介绍一下这个中 ...

  7. ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系

    ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间 ...

  8. 实现路由的RouterMiddleware中间件

    实现路由的RouterMiddleware中间件 虽然ASP.NET Core应用的路由是通过RouterMiddleware这个中间件来完成的,但是具体的路由解析功能都落在指定的Router对象上, ...

  9. ASP.NET Core框架揭秘(持续更新中…)

    之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进 ...

随机推荐

  1. Swift开发第十篇——可变参数函数&初始化方法顺序

    本篇分为两部分: 一.Swift中的可变参数函数 二.初始化方法的顺序 一.Swift中的可变参数函数 可变参数函数指的是可以接受任意多个参数的函数,在 OC 中,拼接字符串的函数就属于可变参数函数 ...

  2. iOS tableView 静态单元格的实现

    本文转自:http://home.cnblogs.com/u/wendingding/ iOS开发UI篇—简单介绍静态单元格的使用 一.实现效果与说明 说明:观察上面的展示效果,可以发现整个界面是由一 ...

  3. (视频) 《快速创建网站》 3.2 WordPress多站点及Azure在线代码编辑器 - 扔掉你的ftp工具吧,修改代码全部云端搞定

    本文是<快速创建网站>系列的第6篇,如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文. 访问本系列目录,请点击:http://devopshub.cn/tag ...

  4. HTML5离线存储原理

    找到一篇介绍离线缓存的,感觉比之前看到的解释的更透彻,新的知识点记录如下: 大家都知道Web App是通过浏览器来访问的,所以离线状态下是无法使用app的.其中web app中的一些资源并不经常改变, ...

  5. java获取客户端ID地址

    转:http://zhenchengchagangzi.iteye.com/blog/1199300#bc2372048 在JSP里,获取客户端的IP地址的方法是:request.getRemoteA ...

  6. 修改Mac系统的默认截图保存路径到指定目录

    注:此文仅针对mac系统如果你是mac用户,会发现桌面经常一团糟,桌面到处都是平时的截图(mac系统的截图是command+shift+3 和 command+shift+4 两个快捷命令) 之前一直 ...

  7. my_strlen()

    int my_strlen(const char* S){ int i=0; while('\0'!=*(S+i)){ i++; } return i; }

  8. Linux IPC POSIX 信号量

    模型 #include<semaphore.h> #include<sys/stat.h> #include<fcntl.h> sem_open() //初始化并打 ...

  9. linux 添加用户、权限

    # useradd –d /usr/sam -m sam 此命令创建了一个用户sam,其中-d和-m选项用来为登录名sam产生一个主目录/usr/sam(/usr为默认的用户主目录所在的父目录). 假 ...

  10. php魔术方法罗列

    ##__sleep() 和 __wakeup() 当序列化(serialize)对象时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep() .__sleep() 方法常用于提交未提交 ...