MVC4路由机制源码剖析
首先我们从如何设置路由开始吧。
我们知道,在MVC4中注册路由,可以在App_Star文件夹中的RouteConfig(路由配置类)中注册路由,我们来看看
RouteConfig
1 public class RouteConfig 2 { 3 public RouteConfig(); 4 public static void RegisterRoutes(RouteCollection routes); 5 }
可以直接使用其中的静态方法RegisterRoutes对路由进行注册。将路由信息内容存放在路由集合
RouteCollection
public class RouteCollection : Collection<RouteBase> { public void Ignore(string url, object constraints); public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens); }
RouteCollection 是一个RouteBase类型的集合,我们来分析一下两个比较重要的方法,一个是Ignore,一个是MapPageRoute,前者是用于定义不需要检查是否匹配路由的URL,后者是注册路由信息。
我们来仔细分析这两个的实现。
Ignore方法:
1 public void Ignore(string url, object constraints) 2 { 3 if (url == null) 4 { 5 throw new ArgumentNullException("url"); 6 } 7 IgnoreRouteInternal item = new IgnoreRouteInternal(url) { 8 Constraints = new RouteValueDictionary(constraints) 9 }; 10 base.Add(item); 11 }
Ignore方法创建了一个IgnoreRouteInternal对象,该对象看名称也应该知道了吧,忽略路由信息类,我们来看看其构造函数内部是怎样的。
public IgnoreRouteInternal(string url) : base(url, new StopRoutingHandler()) { }
调用了父类,也就是Route的一个构造方法,忘了说了,IgnoreRouteInternal是Route的派生子类。我们仔细看看参数,有一个 StopRoutingHandler实例对象。
这是一个什么对象呢?StopRoutingHandler继承自IRouteHandler接口,使用StopRoutingHandler的方式可以确保忽略通过路由的请求。
IgnoreRouteInternal item = new IgnoreRouteInternal(url) 只是间接的调用了Route的构造器,所以,如果想忽略某些url不通过路由访问,其实我们也可以这样做:
1 public static void RegisterRoutes(RouteCollection routes) 2 { 3 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //使用IgnoreRoute方法进行排除 4 5 routes.Add(new Route //使用Route的构造器进行排除 6 ( 7 “{resource}.axd/{*pathInfo}”, 8 new StopRoutingHandler() 9 )); 10 }
我们再来看看注册Ignore方法中IgnoreRouteInternal对象的Constraints属性所赋的值,是一个RouteValueDictionary对象,看看方法中使用到的构造器,我们进一步查看:
1 public RouteValueDictionary(object values) 2 { 3 this._dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); 4 this.AddValues(values); 5 }
这是一个形参为object 的构造器,看见此处你应该了解到了一点什么吧?是的,可以使用匿名类。
1 var routeConstraint = new RouteValueDictionary( 2 new { Controller = "[0-4]", Action = "[5-8]" } 3 );
这是添加了一个路由约束,当然,匿名类设置成约束的时候,属性必须和当前设置的路由的参数要保持一致,那样才能进行验证操作。值得注意的是:排除路由的设置必须在注册路由之前实现,否则是没有效果的。
再来看看MapPageRoute方法:
1 1 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) 2 2 { 3 3 if (routeUrl == null) 4 4 { 5 5 throw new ArgumentNullException("routeUrl"); 6 6 } 7 7 Route item = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess)); 8 8 this.Add(routeName, item); 9 9 return item; 10 10 }
内部是根据提供的参数创建了一个Route对象,再添加到当前的路由集合中。
当然,注册路由我们也可以通过路由表来进行注册,我们来看看RouteTable
public class RouteTable { // Fields private static RouteCollection _instance; // Methods static RouteTable(); public RouteTable(); // Properties public static RouteCollection Routes { get; } }
RouteTable没什么特别,有一个类型为RouteCollection的Routes属性,用来存放当前所有的路由信息数据。
可以通过调用RouteTable的Routes属性来对路由集合进行操作。
好了,说了那么久,路由的操作就这样了,我们来看看今天的主角Route类型吧。
Route:
1 public class Route:RouteBase 2 { 3 public override RouteData GetRouteData(HttpContextBase httpContext); 4 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); 5 protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection); 6 private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection); 7 8 // Properties 9 public RouteValueDictionary Constraints {get; set; } 10 public RouteValueDictionary DataTokens {get; set; } 11 public RouteValueDictionary Defaults { get; set; } 12 public IRouteHandler RouteHandler { get; set; } 13 public string Url { get; set; } 14 }
Route继承于RouteBase,其中GetRouteData 和GetVirtualPath方法,是重写父类RouteBase的两个方法,前者得到的是一个RouteData路由对象,而后者则是得到VirtualPathData虚拟路径对象,还有两个ProcessConstraint方法,用来处理约束。提供了一个protected虚方法用于子类重写。
Route有五个属性,Constraints,表示为路由设置的约束;DataTokens,也就是路由信息提供的自定义变量;Defaults,路由的默认值。RouteHandler,处理请求路由的对象.
就拿那几个重写的方法开始讲起吧:
首先是:GetRouteData方法
1 public override RouteData GetRouteData(HttpContextBase httpContext) 2 { 3 string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; 4 RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults); 5 if (values == null) 6 { 7 return null; 8 } 9 RouteData data = new RouteData(this, this.RouteHandler); 10 if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) 11 { 12 return null; 13 } 14 foreach (KeyValuePair<string, object> pair in values) 15 { 16 data.Values.Add(pair.Key, pair.Value); 17 } 18 if (this.DataTokens != null) 19 { 20 foreach (KeyValuePair<string, object> pair2 in this.DataTokens) 21 { 22 data.DataTokens[pair2.Key] = pair2.Value; 23 } 24 } 25 return data; 26 } 27 28
方法内部调用了ParsedRoute对象的Match方法,其功能是将URL(即virtualPath)解析为一个RouteValueDictionary.
接着调用ProcessConstraints方法,挨个对URL遍历验证,我们来看看此方法。
private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection) { if (this.Constraints != null) { foreach (KeyValuePair<string, object> pair in this.Constraints) { if (!this.ProcessConstraint(httpContext, pair.Value, pair.Key, values, routeDirection)) { return false; } } } return true; }
遍历了约束集合,每个处理约束调用的还是当前类Route的另一个ProcessConstraint方法。
Route的GetRouteData方法,处理完约束后,遍历将自定义变量添加到当前的DataTokens属性中。
再来看看GetVirtualPath方法:
1 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 2 { 3 BoundUrl url = this._parsedRoute.Bind(requestContext.RouteData.Values, values, this.Defaults, this.Constraints); 4 if (url == null) 5 { 6 return null; 7 } 8 if (!this.ProcessConstraints(requestContext.HttpContext, url.Values, RouteDirection.UrlGeneration)) 9 { 10 return null; 11 } 12 VirtualPathData data = new VirtualPathData(this, url.Url); 13 if (this.DataTokens != null) 14 { 15 foreach (KeyValuePair<string, object> pair in this.DataTokens) 16 { 17 data.DataTokens[pair.Key] = pair.Value; 18 } 19 } 20 return data; 21 }
首先调用的是ParseRoute的Bind方法,Bind的作用是根据几个RouteValueDictionary集合构造一个URL. 接着便是执行对URL和路由约束进行处理的方法ProcessConstraints方法。之后便是创建VirtualPathData对象,将当前DataTokens自定义变量集合的值,遍历,复制到VirtualPathData对象的DataTokens中,返回一个VirtualPathData对象。
RouteBase
讲了这几个方法我们再回头来看看Route的基类RouteBase:
1 publicabstractclassRouteBase 2 { 4 public abstract RouteData GetRouteData(HttpContextBase httpContext); 5 public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); 6 public bool RouteExistingFiles{get;set;} 7 8 }
RouteBase 有两个抽象方法,前者返回RouteData对象,后者返回的是VirtualPathData对象。还有一个属性:RouteExistingFiles。该属性默认设置为false,当设置为true的时候,将会与现有文件匹配的URL注册到路由中.
RouteData:
1 public class RouteData 3 { 5 public RouteValueDictionary DataTokens {get; } 6 public RouteBase Route { get; set; } 7 public IRouteHandler RouteHandler { get; set; } 8 public RouteValueDictionary Values { get; } 9 10 }
RouteData对象有几个个属性:
DataTokens:返回的类型为RouteValueDictionary,在上文中也细讲过,该属性用来存放路由信息中,传递过来的自定义变量集合。
Route:返回类型是:RouteBase,该属性得到的是一个路由对象
Values:返回的类型也是RouteValueDictionary,该属性用来保存路由的URL参数和默认值。
再来看看VirtualPathData对象:
VirtualPathData
1 public class VirtualPathData 2 { 3 public RouteValueDictionary DataTokens{get;} 4 public RouteBase Route{get;set;} 5 public string VirtualPath{get;set;} 6 }
其中有一个VirtualPath属性,这个属性也不特殊,是用来设置和保存根据路由生成的URL。
也许你仍然对一个类型不是很理解,也很感兴趣,那接下来我们就来看看吧。
RouteValueDictionay:
1 public class RouteValueDictionary : IDictionary<string, object>, ICollection<KeyValuePair<string, object>>, IEnumerable<KeyValuePair<string, object>>, IEnumerable 2 { 3 // Fields 4 private Dictionary<string, object> _dictionary; 5 6 // Methods 7 public RouteValueDictionary(); 8 public RouteValueDictionary(IDictionary<string, object> dictionary); 9 public RouteValueDictionary(object values); 10 public void Add(string key, object value); 11 private void AddValues(object values); 12 public void Clear(); 13 public bool ContainsKey(string key); 14 public bool ContainsValue(object value); 15 public Dictionary<string, object>.Enumerator GetEnumerator(); 16 public bool Remove(string key); 17 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item); 18 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item); 19 void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex); 20 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item); 21 IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator(); 22 IEnumerator IEnumerable.GetEnumerator(); 23 public bool TryGetValue(string key, out object value); 24 25 // Properties 26 public int Count { get; } 27 public object this[string key] { get; set; } 28 public Dictionary<string, object>.KeyCollection Keys { get; } 29 bool ICollection<KeyValuePair<string, object>>.IsReadOnly { get; } 30 ICollection<string> IDictionary<string, object>.Keys { get; } 31 ICollection<object> IDictionary<string, object>.Values { get; } 32 public Dictionary<string, object>.ValueCollection Values { get; } 33 }
这是一个字典集合类型,也没什么特殊,在路由设置中,通常运用于约束,默认值,自定义变量集合等等。