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 }

这是一个字典集合类型,也没什么特殊,在路由设置中,通常运用于约束,默认值,自定义变量集合等等。

posted @ 2013-03-28 17:26  虔城墨客  阅读(...)  评论(...编辑  收藏