路由系统的核心对象——Router
路由系统的核心对象——Router
ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路由参数的形式解析出来供后续请求处理流程使用。但是具体的路由解析功能其实并没有直接实现在RouterMiddleware中间件中,而是由一个Router对象来完成的。[本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、IRouter接口
二、RouteContext
三、RouteData
四、Route
五、RouteHandler
总结
一、IRouter接口
Router是我们对所有实现了IRouter接口的所有类型以及对应对象的统称,如下面所示的RouterMiddleware类型定义可以看出,当我们创建这个中间件对象的时候,我们需要指定这个Router。
1: public class RouterMiddleware
2: {
3: public RouterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IRouter router);
4: public Task Invoke(HttpContext httpContext);
5: }
除了检验请求是否与自身设置的路由规则相匹配,并在成功匹配的情况下解析出路由参数并指定请求处理器之外,Router的路由解析还为另一个领用场景服务,那就是根据自身的路由规则和提供的参数生成一个URL。我们把这两个方面称为路由的两个“方向”,它们分别对应着RouteDirection枚举的两个选项。针对这两个方向的路由解析分别实现在IRouter的如下两个方法(RouteAsync和GetVirtualPath),目前我们主要关注针对前者的RouteAsync方法。
1: public interface IRouter
2: {
3: Task RouteAsync(RouteContext context);
4: VirtualPathData GetVirtualPath(VirtualPathContext context);
5: }
6:
7: public enum RouteDirection
8: {
9: IncomingRequest,
10: UrlGeneration
11: }
如上面的代码片段所示,针对请求实施路由解析的RouteAsync方法的输入参数是一个类型为RouteContext的上下文对象。这个RouteContext实际上是对一个HttpContext对象的封装,Router可以利用它得到所有与当前请求相关的信息。如果Router完成路由解析并判断当前请求与自身的路由规则一致,那么它会将解析出来的路由参数转换成一个RouteData并存放到RouteContext对象代表的上下文之中,另一个一并被放入上下文的是代表当前请求处理器的RequestDelegate对象。下图基本上展示了RouteAsync方法试试路由解析的原理。
二、RouteContext
接下来我们来了解一下整个路由解析涉及到了几个核心类型,首先来看看为整个路由解析提供执行上下文的这个RouteContext类型。如上图所示,一个RouteContext上下文包含三个核心对象,一个是代表当前请求上下文的HttpContext对象,对应的属性是HttpContext。它实际上是作为路由解析的输入,并在RouteContext创建的时候以构造函数参数的形式提供。另外两个则是作为路由解析的输出,一个是代表存放路由参数的RouteData对象,另一个则是作为请求处理器的RequestDelegate对象,对应的属性分别是RouteData和Handler。
1: public class RouteContext
2: {
3: public HttpContext HttpContext { get; }
4: public RouteData RouteData { get; set; }
5: public RequestDelegate Handler { get; set; }
6:
7: public RouteContext(HttpContext httpContext);
8: }
三、RouteData
我们先来看看用于存放路由参数的RouteData类型。从数据来源的角度来讲,路由参数具有两种类型,一种是通过请求路径携带的参数,另一种则是Router对象自身携带的参数,这两种路由参数分别对应着RouteData的Values和DataTonkens属性。至于另一个属性Routers,则保存着实施路由解析并提供路由参数的所有Router对象。
1: public class RouteData
2: {
3: public RouteValueDictionary Values { get; }
4: public RouteValueDictionary DataTokens { get; }
5: public IList<IRouter> Routers { get; }
6: }
7:
8: public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
9: {
10: public RouteValueDictionary(object values);
11: …
12: }
从上面的代码片可以看出,RouteData的Values和DataTokens属性的类型都是RouteValueDictionary,它实际上就是一个字典对象而已,其Key和Value分别代表路由参数的名称和值,而作为Key的字符串是不区分大小写的。值得一提的是RouteValueDictionary具有一个特殊的构造函数,作为唯一参数的是一个object类型的对象。如果我们指定的参数是一个RouteValueDictionary对象或者是一个元素类型为KeyValuePair<string, object>>的集合,指定的数据将会作为原始数据源。这个特性体现在如下所示的调试断言中。
1: var values1 = new RouteValueDictionary() ;
2: values1.Add("foo", 1);
3: values1.Add("bar", 2);
4: values1.Add("baz", 3);
5:
6: var values2 = new RouteValueDictionary(values1);
7: Debug.Assert(int.Parse(values2["foo"].ToString()) == 1);
8: Debug.Assert(int.Parse(values2["bar"].ToString()) == 2);
9: Debug.Assert(int.Parse(values2["baz"].ToString()) == 3);
10:
11: values2 = new RouteValueDictionary(new Dictionary<string, object>
12: {
13: ["foo"] = 1,
14: ["bar"] = 2,
15: ["baz"] = 3,
16: });
17: Debug.Assert(int.Parse(values2["foo"].ToString()) == 1);
18: Debug.Assert(int.Parse(values2["bar"].ToString()) == 2);
19: Debug.Assert(int.Parse(values2["baz"].ToString()) == 3);
RouteValueDictionary的这个构造函数的特殊之处其实并不止于此。除了将一个自身具有字典结构的对象作为原始数据源作为参数之外,我们还可以将一个普通的对象作为参数,在此情况下这个构造函数会解析定义在对象自身类型的所有属性定义,并将属性名称和值作为路由参数的名称和值。如下面的代码片段所示,我们创建一个匿名类型的对象并根据它来创建一个RouteValueDictionary,这种方式在MVC应用使用得比较多。
1: RouteValueDictionary values = new RouteValueDictionary(new
2: {
3: Foo = 1,
4: Bar = 2,
5: Baz = 3
6: });
7:
8: Debug.Assert(int.Parse(values["foo"].ToString()) == 1);
9: Debug.Assert(int.Parse(values["bar"].ToString()) == 2);
10: Debug.Assert(int.Parse(values["baz"].ToString()) == 3);
由于RouteData被直接置于RouteContext这上下文中,所以任何可以访问到这个上下文的对象都可以随意地修改其中的路由参数,为了全局对象造成的“数据污染”问题,一种类型与“快照”的策略被应用到RouteData上。具体来说,我们为某个RouteData当前的状态创建一个快照,在后续的某个时刻我们利用这个快照让这个RouteData对象回复到当初的状态。
针对RouteData的这个快照通过具有如下定义的结构RouteDataSnapshot表示。当我们创建这个一个对象的时候,需要指定目标RouteData对象和当前的状态(Values、DataTokens和Routers)。当我们调用其Restore方法的时候,目标RouteData将会恢复到快照创建时的状态。我们可以直接调用RouteData的PushState为它自己创建一个快照。
1: public struct RouteDataSnapshot
2: {
3: public RouteDataSnapshot(RouteData routeData, RouteValueDictionary dataTokens, IList<IRouter> routers, RouteValueDictionary values);
4: public void Restore();
5: }
6:
7: public class RouteData
8: {
9: public RouteDataSnapshot PushState(IRouter router, RouteValueDictionary values, RouteValueDictionary dataTokens);
10: }
如下面的代码片段所示,我们创建了一个RouteData对象并调用其PushState方法为它创建了一个快照,调用该方法指定的三个参数均为null。虽然我们在后续步骤中修改了这个RouteData的状态,但是一旦我们调用了这个RouteDataSnapshot对象的Restore方法,这个RouteData将重新恢复到最初的状态。
1: RouteData routeData = new RouteData();
2: RouteDataSnapshot snapshot = routeData.PushState(null, null, null);
3:
4: routeData.Values.Add("foo", 1);
5: routeData.DataTokens.Add("bar", 2);
6: routeData.Routers.Add(new RouteHandler(null));
7:
8: snapshot.Restore();
9: Debug.Assert(!routeData.Values.Any());
10: Debug.Assert(!routeData.DataTokens.Any());
11: Debug.Assert(!routeData.Routers.Any());
四、Route
除了IRouter这个最为基础的接口之外,路由系统中还定义了额外一些接口和抽象类,其中就包含如下这个INamedRouter接口。这个接口代表一个“具名的”Router,说白了就是这个Router具有一个通过属性Name表示的名字。
1: public interface INamedRouter : IRouter
2: {
3: string Name { get; }
4: }
所有具体的Route基本上都最终继承自如下这个抽象基类RouteBase,前面演示实例体现的基于“路由模板”的路由解析策略就体现在这个类型中。如下面的代码片段所示,RouterBase实现了INamedRouter接口,所以它具有一个名称作为标识。它的ParsedTemplate属性返回的RouteTemplate对象表示这个路由模板,它的Defaults和Constraints则是针对以内联方式设置的默认值和约束的解析结果。针对内联约束的解析是利用一个InlineConstraintResolver对象来完成的,RouteBase的ConstraintResolver属性返回就是这么一个对象。RouteData的DataTokens来源于Router对象,对应的属性就是DataTokens。
1: public abstract class RouteBase : INamedRouter
2: {
3: public virtual string Name { get; protected set; }
4: public virtual RouteTemplate ParsedTemplate { get; protected set; }
5: protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
6:
7: public virtual RouteValueDictionary DataTokens { get; protected set; }
8: public virtual RouteValueDictionary Defaults { get; protected set; }
9: public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
10:
11: public RouteBase(string template, string name, IInlineConstraintResolver constraintResolver, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens);
12:
13: public virtual Task RouteAsync(RouteContext context);
14: protected abstract Task OnRouteMatched(RouteContext context);
15: …
16: }
对于实现在 RouteAsync方法中针对入栈请求而进行的路由解析,RouteBase中的实现只负责判断是否给定的条件是否满足自身的路由规则,并在规则满足的情况下将解析出来的路由参数保存到RouteContext这个上下文中。至于满足路由规则情况下实施的后续操作, 则实现在抽象方法OnRouteMatched中。
我们在进行路由注册的时候经常使用的Route类型是具有如下定义的Route它是上面这个抽象类RouteBase子类。从如下的代码片段我们不难看出,一个Route对象其实是对另一个Router对象的封装,它自身并没有承载任何具体的路由功能。我们在创建这个Route对象的时候,需要提供这个被封装的Router,这个Router对象在重写的OnRouteMatched方法中被添加到RouteData的Routers属性中,随后它的RouteAsync方法被执行。
1: public class Route : RouteBase
2: {
3: private readonly IRouter _target;
4: public string RouteTemplate
5: {
6: get { return this.ParsedTemplate.TemplateText; }
7: }
8:
9: public Route(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver) : this(target, routeTemplate, null, null, null, inlineConstraintResolver){}
10:
11: public Route(IRouter target, string routeTemplate, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens, IInlineConstraintResolver inlineConstraintResolver)
12: : this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver){}
13:
14: public Route(IRouter target, string routeName, string routeTemplate, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens, IInlineConstraintResolver inlineConstraintResolver)
15: : base(routeTemplate, routeName, inlineConstraintResolver, defaults, constraints, dataTokens)
16: {
17: _target = target;
18: }
19:
20: protected override Task OnRouteMatched(RouteContext context)
21: {
22: context.RouteData.Routers.Add(_target);
23: return _target.RouteAsync(context);
24: }
25:
26: protected override VirtualPathData OnVirtualPathGenerated(VirtualPathContext context)
27: {
28: return _target.GetVirtualPath(context);
29: }
30: }
五、RouteHandler
一个Router在进行针对请求的路由解析过程中需要判断当前请求是否与自身设置的路由规则相匹配,并在匹配情况下将解析出来的路由参数存放与RouteContext这个上下文中,这些都实现在RouteBase这个基类中。由于Route派生于RouteBase,所以它自身也提供了这项基本功能。但是Router还具有另一个重要的任务,那就是在路由匹配情况下将作为处理器的RequestDelegate对象存放到RouteContext上下文中,这个任务最终落实到RouteHandler这个特殊的Router上。
RouteHandler是一种特殊的Router类型,它不仅实现了IRouter接口,还同时实现了另一个IRouteHandler接口,后者提供了一个GetRequestHandler方法根据表示当前请求上下文的HttpContext对象和封装了路由参数的RouteData对象得到一个RequestDelegate对象,后者将会用来处理当前请求。如下面的代码片段所示,我们创建一个RouteHandler对象是需要显式指定一个RequestDelegate对象,GetRequestHandler方法返回的正是这个对象。在实现的RouteAsync方法中,它将这个RequestDelegate赋值给RouteContext的Handler属性。
1: public class RouteHandler : IRouteHandler, IRouter
2: {
3: private readonly RequestDelegate _requestDelegate;
4:
5: public RouteHandler(RequestDelegate requestDelegate)
6: {
7: _requestDelegate = requestDelegate;
8: }
9:
10: public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
11: {
12: return _requestDelegate;
13: }
14:
15: public Task RouteAsync(RouteContext context)
16: {
17: context.Handler = _requestDelegate;
18: return Task.CompletedTask;
19: }
20: }
21:
22: public interface IRouteHandler
23: {
24: RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
25: }
基类RouteBase能够确定当前请求是否与自身设置的路由规则相匹配,并在匹配的情况下设置路由参数,而RouteHandler只提供设置请求处理器的功能,但是一个真正的Router必须同时具有这两项功能,那么后者究竟是怎样一个对象呢?我们在上面介绍继承自RouteBase的Route类型时,我们说一个Route对象是对另一个Router对象的封装,那么被封装的Router如果是一个RouteHanlder,那么这个Route对象不就具有完整的路由解析功能了吗?
总结
我们介绍了一系列与Router相关的接口和类,包括IRouter、INameRouter和IRouteHandler接口,抽象类RouteBase,以及两个具体的Route和RouteHandler类性。这些与Router相关额接口和类性具有如下图所示的关系。
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]:内联路由约束的检验
路由系统的核心对象——Router的更多相关文章
- ASP.NET Core的路由[2]:路由系统的核心对象——Router
ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路由参数的形式解析出来供后续请求处理流 ...
- ASP.NET Web API路由系统:路由系统的几个核心类型
虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...
- ASP.NET Web API框架揭秘:路由系统的几个核心类型
ASP.NET Web API框架揭秘:路由系统的几个核心类型 虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分 ...
- 深入理解 react-router 路由系统
作者:范洪春链接:https://zhuanlan.zhihu.com/p/20381597来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 在 web 应用开发中,路由 ...
- ASP.NET Web API路由系统:Web Host下的URL路由
ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...
- .NET/ASP.NET Routing路由(深入解析路由系统架构原理)
阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4 ...
- .NET/ASP.NET Routing路由(深入解析路由系统架构原理)http://wangqingpei557.blog.51cto.com/1009349/1312422
阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4 ...
- NET/ASP.NET Routing路由(深入解析路由系统架构原理)(转载)
NET/ASP.NET Routing路由(深入解析路由系统架构原理) 阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模 ...
- WebApi学习总结系列第四篇(路由系统)
由于工作的原因,断断续续终于看完了<ASP.NET Web API 2 框架揭秘>第二章关于WebApi的路由系统的知识. 路由系统是请求消息进入Asp.net WebApi的第一道屏障, ...
随机推荐
- 结对编程-地铁续(有种上个学期OO的既视感)
我们组比较特殊..三人结对 github:https://github.com/qingchanghan/WPFUI_Metro po一张照片: 石浩然,韩青长.陈彦吉 (台式机真的很高端,分屏贼帅) ...
- 用Phaser实现Flappy Bird 游戏
How to Make a Flappy Bird in HTML5 With Phaser - Part 1 Flappy Bird is a nice little game with easy ...
- Javascript之旅——第三站:几个需要注意的运算符
平时写惯了C#,所以会觉得什么样的运算符就应该做什么样的运算,但是有一天你的习惯被其他语言颠覆了,不知道是不是有一股强大的好奇 心,刚好在js中,我的这种习惯就被颠覆了,下面就看看哪些运算符颠覆了我的 ...
- 编写Java应用程序,定义Animal类,此类中有动物的属性:名称 name,腿的数量legs,统计动物的数量 count;方法:设置动物腿数量的方法 void setLegs(),获得腿数量的方法 getLegs(),设置动物名称的方法 setKind(),获得动物名称的方法 getKind(),获得动物数量的方法 getCount()。定义Fish类,是Animal类的子类,统计鱼的数量 co
package com.hanqi.test; public class Animal { private String name; private int legs; private int cou ...
- Navicat常用快捷键
[ctrl+q] 打开查询窗口 [ctrl+/] 注释sql语句 [ctrl+shift +/] 解除注释 [ctrl+r] ...
- srping MVC 工程简单搭建
Spring版本:3.2.2.RELEASE 第一步:导入必须的jar包 spring-beans.jar spring-context.jar spring-core.jar spring-expr ...
- Debian deb源方法升级PHP软件包
学习Linxu以来,一直坚持编译方式安装软件包,貌似圈子里面也是都倾向于编译,可是搜索到的编译方法都是一堆复制粘贴来的指令, 每个人都这么编译, 却几乎没有人去写明那些五花八门的编译指令代表了什么,是 ...
- PADS_AD_Cadence转换
PADS_AD_Cadence转换 软件版本:PADS的版本是PADS9.5,Altium designer的版本是Altium designer winter 09 PADS和AD的转换 PADS的 ...
- 学习OpenStack之(5):在Mac上部署Juno版本OpenStack 四节点环境
0. 前沿 经过一段时间的折腾,终于在自己的Mac上装好了Juno版本的四节点环境.这过程中,花了大量的时间,碰到了许多问题,学到不少知识,折腾过不少其实不需要折腾的东西,本文试着来对这过程做个总结. ...
- Interlocked系列函数线程同步的缺陷
1. Code int Work() { while (m_lInterlockedData < 10) { InterlockedIncrement(&m_lInterlockedDa ...