深入源码解析类Route
微软官网对这个类的说明是:提供用于定义路由及获取路由相关信息的属性和方法。这个说明已经很简要的说明了这个类的作用,下面我们就从源码的角度来看看这个类的内部是如何工作的。
1 public class Route : RouteBase { 2 3 private string _url; 4 private ParsedRoute _parsedRoute; 5 6 public Route(string url, IRouteHandler routeHandler) { 7 Url = url; 8 RouteHandler = routeHandler; 9 } 10 11 public RouteValueDictionary Defaults { 12 get; 13 set; 14 } 15 16 public IRouteHandler RouteHandler { 17 get; 18 set; 19 } 20 21 public string Url { 22 get { 23 return _url ?? String.Empty; 24 } 25 set { 26 _parsedRoute = RouteParser.Parse(value); 27 _url = value; 28 } 29 } 30 31 public override RouteData GetRouteData(HttpContextBase httpContext) { 32 33 string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; 34 35 RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults); 36 37 if (values == null) { 38 return null; 39 } 40 41 RouteData routeData = new RouteData(this, RouteHandler); 42 43 if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) { 44 return null; 45 } 46 foreach (var value in values) { 47 routeData.Values.Add(value.Key, value.Value); 48 } 49 50 if (DataTokens != null) { 51 foreach (var prop in DataTokens) { 52 routeData.DataTokens[prop.Key] = prop.Value; 53 } 54 } 55 56 return routeData; 57 } 58 }
上面是类的核心代码,如果在这里对所有的代码都进行列出或学习,则无法突出重点。
主要包括构造函数,几个属性,一个方法。我们从这个类的对象被创建的地方开始,逐步深入理解。
这个方法会在应用程序启动时被第一个调用,在这里,我们注册了全局的路由信息。
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); //将全局变量 RouteTable.Routes作为参数传递进方法里,然后向这个全局变量添加数据。系统定义的路由数据都被存放在这里变量里 RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } }
2从RouteConfig这个类开始,这是创建Mvc项目时,系统自动替我们添加的类,也是很重要的一个类.
1 public class RouteConfig 2 { 3 public static void RegisterRoutes(RouteCollection routes) 4 { 5 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 6 7 routes.MapRoute( 8 name: "Default", 9 url: "{controller}/{action}/{id}", 10 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 11 ); 12 } 13 }
在这里类里,系统替我们添加了默认的URL路由并添加了默认值。
这里调用了RouteCollection对象的MapRoute方法进行路由的映射,但是查看RouteCollection这个类,并不能找到MapRoute这个方法。
这个方法的实现,是通过RouteCollection的一个拓展方法进行实现。
1 public static class RouteCollectionExtensions 2 { 3 public static void IgnoreRoute(this RouteCollection routes, string url, object constraints) 4 { 5 if (routes == null) 6 { 7 throw new ArgumentNullException("routes"); 8 } 9 if (url == null) 10 { 11 throw new ArgumentNullException("url"); 12 } 13 14 IgnoreRouteInternal route = new IgnoreRouteInternal(url) 15 { 16 Constraints = CreateRouteValueDictionaryUncached(constraints) 17 }; 18 19 ConstraintValidation.Validate(route); 20 21 routes.Add(route); 22 } 23 24 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) 25 { 26 if (routes == null) 27 { 28 throw new ArgumentNullException("routes"); 29 } 30 if (url == null) 31 { 32 throw new ArgumentNullException("url"); 33 } 34 35 Route route = new Route(url, new MvcRouteHandler()) 36 { 37 Defaults = CreateRouteValueDictionaryUncached(defaults), 38 Constraints = CreateRouteValueDictionaryUncached(constraints), 39 DataTokens = new RouteValueDictionary() 40 }; 41 42 ConstraintValidation.Validate(route); 43 44 if ((namespaces != null) && (namespaces.Length > 0)) 45 { 46 route.DataTokens[RouteDataTokenKeys.Namespaces] = namespaces; 47 } 48 49 routes.Add(name, route); 50 51 return route; 52 } 53 }
在上面的MapRoute方法里,类Route的对象使用指定的值被创建。这也是在系统中,Route对象最初被创建的对象。在这里,路由被定义。
其中最核心的就是下面的代码,我们也将重点讲解下面的代码,看我们的路由信息时如何被定义的以及为什么这样定义。
1 Route route = new Route(url, new MvcRouteHandler()) 2 { 3 Defaults = CreateRouteValueDictionaryUncached(defaults), 4 Constraints = CreateRouteValueDictionaryUncached(constraints), 5 DataTokens = new RouteValueDictionary() 6 };
在第一行中,给Route的构造方法传递了两个参数。
第一个是url"{controller}/{action}/{id}"这样的值,这样的一个值,定义了系统能处理的路由模板,后面每当有请求被处理,
总是会将请求的http链接与这个路由模板进行匹配,看是否满足我们定义的模板。
第二个参数则是实现了IRouteHandler接口的类MvcRouteHandler。使用该类对象的主要作用是创建实际处理请求的类MvcHandler对象。
关于这两个类的作用及地位会在其他随笔里介绍,这里仅作了解。
下面让我们进入类Route的内部,看构造函数里到底发生了什么。也就是本片文章一开始的那段代码。然后在返回看看对Defaults属性的赋值。
1 public Route(string url, IRouteHandler routeHandler) { 2 Url = url; 3 RouteHandler = routeHandler; 4 } 5 public string Url { 6 get { 7 return _url ?? String.Empty; 8 } 9 set { 10 _parsedRoute = RouteParser.Parse(value); 11 _url = value; 12 } 13 }
在构造方法里,将传递进去的连个参数保存在两个属性里,而仅在Url属性中,对url路由模板做了特殊处理。所做的这些特殊处理,也是它可以匹配用户的
请求连接的关键。我将第10行使用的代码用源代码中拷贝了出来,新建了一个项目,进行测试。返回的_parsedRoute对象如下图所示。
这里主要使用了两个类,一个是ContentPathSegment,一个是SeparatorPathSegment(代表URL中的“/”分隔符)。重点解释ContentPathSegment类。上截图。
从截图中可以看出,这个类主要存储的是我们定义的url模板切割后子段值。
下面列出该类的具体代码,可以比较理解.
//代表不是分隔符的网段。 它包含诸如文字和参数的子段。 internal sealed class ContentPathSegment : PathSegment { public ContentPathSegment(IList<PathSubsegment> subsegments) { Subsegments = subsegments; } public bool IsCatchAll { get { // return Subsegments.Any<PathSubsegment>(seg => (seg is ParameterSubsegment) && (((ParameterSubsegment)seg).IsCatchAll)); } } public IList<PathSubsegment> Subsegments { get; private set; } }
至此,Route类构造函数中的结束已经完毕,下面解释另外一个属性的赋值--Defaults。这是属性中存储的是我们给url路由模板指定的默认值,在http请求时,如果只输入了ip,没有指定控制器名称和action时,使用这里的默认值。
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
Defaults = CreateRouteValueDictionaryUncached(defaults),
下面是经转化后返回的值的截图,该属性返回的是一个RouteValueDictionary字典。
至此,类Route的创建已经完成,该类的创建工作在应用程序启动时完成,且只会被初始化一次,然后保存在路由集合中。
有两个重点,1是路由模板经过处理后得到的_parsedRoute对象;2是传递的路由默认值。这两个对象是Route类的核心功能点。
下面,我们就从这个类的使用角度开始解析。UrlRoutingModule这个类是使用Route开始的地方
1 public class UrlRoutingModule : IHttpModule { 2 private static readonly object _contextKey = new Object(); 3 private static readonly object _requestDataKey = new Object(); 4 private RouteCollection _routeCollection; 5 6 public RouteCollection RouteCollection { 7 get { 8 if (_routeCollection == null) { 9 _routeCollection = RouteTable.Routes; 10 } 11 return _routeCollection; 12 } 13 set { 14 _routeCollection = value; 15 } 16 } 17 18 protected virtual void Dispose() { 19 } 20 21 protected virtual void Init(HttpApplication application) { 22 23 if (application.Context.Items[_contextKey] != null) { 24 return; // already added to the pipeline 25 } 26 application.Context.Items[_contextKey] = _contextKey; 27 application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; 28 } 29 30 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) { 31 HttpApplication app = (HttpApplication)sender; 32 HttpContextBase context = new HttpContextWrapper(app.Context); 33 PostResolveRequestCache(context); 34 } 35 36 37 public virtual void PostResolveRequestCache(HttpContextBase context) { 38 // Match the incoming URL against the route table 39 RouteData routeData = RouteCollection.GetRouteData(context); 40 41 // Do nothing if no route found 42 if (routeData == null) { 43 return; 44 } 45 46 // If a route was found, get an IHttpHandler from the route's RouteHandler 47 IRouteHandler routeHandler = routeData.RouteHandler; 48 if (routeHandler == null) { 49 throw new InvalidOperationException( 50 String.Format( 51 CultureInfo.CurrentCulture, 52 SR.GetString(SR.UrlRoutingModule_NoRouteHandler))); 53 } 54 55 if (routeHandler is StopRoutingHandler) { 56 return; 57 } 58 RequestContext requestContext = new RequestContext(context, routeData); 59 context.Request.RequestContext = requestContext; 60 61 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 62 if (httpHandler == null) { 63 throw new InvalidOperationException( 64 String.Format( 65 CultureInfo.CurrentUICulture, 66 SR.GetString(SR.UrlRoutingModule_NoHttpHandler), 67 routeHandler.GetType())); 68 } 69 70 if (httpHandler is UrlAuthFailureHandler) { 71 if (FormsAuthenticationModule.FormsAuthRequired) { 72 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); 73 return; 74 } 75 else { 76 throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3)); 77 } 78 } 79 80 // Remap IIS7 to our handler 81 context.RemapHandler(httpHandler); 82 } 83 84 #region IHttpModule Members 85 void IHttpModule.Dispose() { 86 Dispose(); 87 } 88 89 void IHttpModule.Init(HttpApplication application) { 90 Init(application); 91 } 92 #endregion 93 }
这是一个路由模块类,在网站启动时,会自动从配置文件中加载这个类。在类的Init方法中,会传递一个HttpApplication对象,然后会注册HttpApplication对象的PostResolveRequestCache事件,关于类HttpApplication
会在其他随笔中重点的讲解,这里仅做介绍。我们只需知道,每当一个请求到达时,HttpApplication对象的PostResolveRequestCache事件会被出发,进而调用类UrlRoutingModule的PostResolveRequestCache方法。
在这个方法里,会根据亲求的url链接,去匹配一个系统中已经定义的路由信息。
1 RouteData routeData = RouteCollection.GetRouteData(context);
这是PostResolveRequestCache方法的第一行代码,从路由集合中获取一个路由数据。RouteCollection这个属性的真实值就是RouteTable.Routes,这其中的数据,就是本文上半部分介绍的Route被创建后存储的地方。RouteCollection中存储的就是一个个的Route对象,所以上面的一行代码,最终就是遍历这些Route对象,然后调用每一个对象的GetRouteData方法。
1 public override RouteData GetRouteData(HttpContextBase httpContext) { 2 //解析传入的URL(我们修剪掉前两个字符,因为它们总是“〜/”) 3 //解析后的字符串类似这样 Home/Index、User/Info 这样的格式 4 string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; 5 //将用户请求的url中的与控制器和Action相关的字符串与我们定义的路由信息进行匹配 6 RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults); 7 //如果没有匹配到数据,则返回空值 8 if (values == null) { 9 return null; 10 } 11 //说明请求的url解析成功,则新建一个路由数据对象,且将当前对象及当前对象的一个属性传递给构造函数 12 RouteData routeData = new RouteData(this, RouteHandler); 13 //将匹配到的路由信息添加到路由数据中 14 foreach (var value in values) { 15 routeData.Values.Add(value.Key, value.Value); 16 } 17 return routeData; 18 }
根据此RouteData对象可以获取一个实现了IRouteHandler接口的类(MvcRouteHandler)的对象,而该对象又可以获取一个实现了IHttpHanlder的类(MvcHandler)的对象,
该对象,是真正处理用于请求的对象,其他所做的一切都只是准备工作。
结束语:
该类的作用第一是用来存储我们定义的基础路由信息,第二是用来匹配检测用户输入的URL,第三是保存一个实现了IRouteHandler接口的类对象。