mvc源码解读(2)-mvc路由注册

    mvc虽然开源了,但是mvc核心的路由注册机制微软并没有公开,因此在开源的mvc源码中我们并不能完全的分析到mvc的路由注册原理,我们必须借助反编译工具来查看路由的注册。我们在新建一个mvc项目的时候,在全局文件Global.asax进行初始化的时候都会有一个注册路由的RegisterRoutes方法,该方法具体如下:

public static void RegisterRoutes(RouteCollection routes)        

{            

        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");            

        routes.IgnoreRoute("{folder}/{*pathInfo}", new { folder = "Service" });            

  routes.MapPageRoute("CommonReportRoute", // Route name                            "

            Reports/{reportmodel}/{reportname}",// URL                           

            "~/Reports/{reportmodel}.aspx"   // File                           

);

   routes.MapRoute("Default", // Route name 

           "{controller}/{action}/{id}", // URL with parameters

             new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults

             ).DataTokens["UseNamespaceFallback"] = false;

        }

蓝色代码表示映射页面路由信息,为什么会有映射页面类的文件路由信息呢?因为在mvc3中我们我们可以创建两种视图引擎,一种是.aspx视图引擎,另外一种就是Razor视图引擎。我们来看看路由集合RouteCollection中的MapRoute方法,该方法有很多个重载的方法,但是所有的重载方法都统一的调用了一个核心的MapRoute方法,该方法具体实现如下:

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) 
{
Route route = new Route(url, new MvcRouteHandler()) {Defaults = new RouteValueDictionary(defaults),Constraints = new RouteValueDictionary(constraints),DataTokens = new RouteValueDictionary()};
if ((namespaces != null) && (namespaces.Length > 0)) 
{
  route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
 return route;
}

该方法里面涉及到一个很重要的类Route,Asp.net mvc中正是通过该类实现了基于URL模板的路由机制。我们应该还记得在上一篇文章中的介绍的PostResolveRequestCache事件,该事件里面的第一句话就是:

RouteData routeData = this.RouteCollection.GetRouteData(context);

来获取路由信息,GetRouteData方法属于路由集合类RouteCollection,具体实现如下:

public RouteData GetRouteData(HttpContextBase httpContext)
{
    if (base.Count != 0)
    {
        bool flag = false;
        bool flag2 = false;
        if (!this.RouteExistingFiles)
        {
            flag = this.IsRouteToExistingFile(httpContext);
            flag2 = true;
            if (flag)
            {
                return null;
            }
        }
        using (this.GetReadLock())
        {
            foreach (RouteBase base2 in this)
            {
                RouteData routeData = base2.GetRouteData(httpContext);
                if (routeData != null)
                {
                    if (!base2.RouteExistingFiles)
                    {
                        if (!flag2)
                        {
                            flag = this.IsRouteToExistingFile(httpContext);
                            flag2 = true;
                        }
                        if (flag)
                        {
                            return null;
                        }
                    }
                    return routeData;
                }
            }
        }
    }
    return null;
}

该方法返回的routeData是由抽象类RouteBaseGetRouteData方法获取的,而Route实现了该抽象类的GetRouteData方法,因此我们可以知道base2指的就是Route对象。该方法具体实现如下:

 public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
            RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
            if (values == null)
            {
                return null;
            }
            RouteData data = new RouteData(this, this.RouteHandler);
            if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
            {
                return null;
            }
            foreach (KeyValuePair<string, object> pair in values)
            {
                data.Values.Add(pair.Key, pair.Value);
            }
            if (this.DataTokens != null)
            {
                foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
                {
                    data.DataTokens[pair2.Key] = pair2.Value;
                }
            }
            return data;
        }

在这个方法里面直接返回了一个RouteData对象,但是该对象已经携带了特殊的信息。我们主要来看一下这一句代码: 

string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;

httpContext.Request属性返回的是HttpRequest对象,该对象的AppRelativeCurrentExecutionFilePath属性返回的是方法 MakeVirtualPathAppRelative返回的值,该方法具体实现如下:

internal static string MakeVirtualPathAppRelative(string virtualPath, string applicationPath, bool nullIfNotInApp)

{       

       int length = applicationPath.Length;    

       int num2 = virtualPath.Length;    

       if ((num2 == (length - 1)) && StringUtil.StringStartsWithIgnoreCase(applicationPath, virtualPath))    

         {         return "~/";     }    

         if (!VirtualPathStartsWithVirtualPath(virtualPath, applicationPath))     {        

         if (nullIfNotInApp)        

          {             

              return null;        

          }        

         return virtualPath;    

        }    

        if (num2 == length)    

         {         return "~/";     }    

        if (length == 1)    

        {        

              return ('~' + virtualPath);    

        }    

         return ('~' + virtualPath.Substring(length - 1)); }

      由这个方法我们可以知道httpContext.Request.AppRelativeCurrentExecutionFilePath通常返回的就是一个以“~/”开头的url,httpContext.Request.PathInfo在一般情况下返回的也是空,这里面的具体实现我们在此略过,里面的逻辑较为复杂。因此我们可以根据上面的全局文件注册的方法可以知道:假如我们要请求的URl为http://localhost:7989/Home/index的话,则httpContext.Request.AppRelativeCurrentExecutionFilePath返回的值就是:~/Home/index,因此virtualPath的值就是:Home/index。

      我们再来看看这一句代码: RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);Match方法的具体实现如下:

 public RouteValueDictionary Match(string virtualPath, RouteValueDictionary defaultValues)
        {
            IList<string> source = RouteParser.SplitUrlToPathSegmentStrings(virtualPath);
            if (defaultValues == null)
            {
                defaultValues = new RouteValueDictionary();
            }
            RouteValueDictionary matchedValues = new RouteValueDictionary();
            bool flag = false;
            bool flag2 = false;
            for (int i = 0; i < this.PathSegments.Count; i++)
            {
                PathSegment segment = this.PathSegments[i];
                if (source.Count <= i)
                {
                    flag = true;
                }
                string a = flag ? null : source[i];
                if (segment is SeparatorPathSegment)
                {
                    if (!flag && !string.Equals(a, "/", StringComparison.Ordinal))
                    {
                        return null;
                    }
                }
                else
                {
                    ContentPathSegment contentPathSegment = segment as ContentPathSegment;
                    if (contentPathSegment != null)
                    {
                        if (contentPathSegment.IsCatchAll)
                        {
                            this.MatchCatchAll(contentPathSegment, source.Skip<string>(i), defaultValues, matchedValues);
                            flag2 = true;
                        }
                        else if (!this.MatchContentPathSegment(contentPathSegment, a, defaultValues, matchedValues))
                        {
                            return null;
                        }
                    }
                }
            }
            if (!flag2 && (this.PathSegments.Count < source.Count))
            {
                for (int j = this.PathSegments.Count; j < source.Count; j++)
                {
                    if (!RouteParser.IsSeparator(source[j]))
                    {
                        return null;
                    }
                }
            }
            if (defaultValues != null)
            {
                foreach (KeyValuePair<string, object> pair in defaultValues)
                {
                    if (!matchedValues.ContainsKey(pair.Key))
                    {
                        matchedValues.Add(pair.Key, pair.Value);
                    }
                }
            }
            return matchedValues;
        }

   首先看红色代码:IList<string> source = RouteParser.SplitUrlToPathSegmentStrings(virtualPath);SplitUrlToPathSegmentStrings方法在密封类ParsedRoute的实现如下:

 internal static IList<string> SplitUrlToPathSegmentStrings(string url)
        {
            List<string> list = new List<string>();
            if (!string.IsNullOrEmpty(url))
            {
                int index;
                for (int i = 0; i < url.Length; i = index + 1)
                {
                    index = url.IndexOf('/', i);
                    if (index == -1)
                    {
                        string str = url.Substring(i);
                        if (str.Length > 0)
                        {
                            list.Add(str);
                        }
                        return list;
                    }
                    string item = url.Substring(i, index - i);
                    if (item.Length > 0)
                    {
                        list.Add(item);
                    }
                    list.Add("/");
                }
            }
            return list;
        }

    依然以我们刚才的例子来做解释:传进来的参数url=virtualPath就是Home/Index,经过SplitUrlToPathSegmentStrings方法处理之后返回的list集合中有三个元素:list[0]="Home",list[1]="/",list[2]="Index";其实里面获取路由信息的方法是相当的繁琐和复杂的,我看了很久了源码都没有完全的读懂,后来参考了博客园另一位仁兄dz45693的关于读取路由信息详细解析,才慢慢领悟,大家可以参考一下他的这篇文章http://www.cnblogs.com/majiang/archive/2012/11/21/2780591.html讲的很详细,值得大家的学习。我们再回到GetRouteData方法中,我们获取到相匹配的RouteValueDictionary之后,遍历RouteValueDictionary对象values,将通过地址解析出来的变量存放到RouteData对象的Values属性中,DataTokens 属性检索或分配与路由关联且未用于确定该路由是否匹配 URL 模式的值,这些值会传递到路由处理程序,以便用于处理请求。

    关于mvc的路由信息,这里面还牵涉到一个十分重要的类:RouteTable,顾名思义就是路由表的意思,这是由于我们的mvc web程序中可能会有不同的url模式,可能并不是默认的~/{Controller}/{Action}/{Parameters},有可能是~/Action}/{Controller}/{Parameters},因此用RouteTable来表示整个mvc应用的全局路由信息。这个RouteTable的定义:

    public class RouteTable
    {
        private static RouteCollection _instance = new RouteCollection();
       
        public static RouteCollection Routes
        {
            get
            {
                return _instance;
            }
        }
    }

 RouteTable是对RouteCollection的封装,顾名思义RouteCollection就是路由集合的意思,我们来看RouteCollection里面的Add方法定义:

 public void Add(string name, RouteBase item)
        {
            base.Add(item);
            if (!string.IsNullOrEmpty(name))
            {
                this._namedMap[name] = item;
            }
        }

 这里面_namedMap是一个字典集合,定义如下:

        private Dictionary<string, RouteBase> _namedMap;

 

     RouteDictionary表示一个具体的路由对象的列表,其中的Key表示注册的路由对象的名称,具体的Route对象为value。例如:

RouteTable.Routes.Add("myTest", new Route { url = "{controller}/{action}/{Parameter}" });

理解了这一点之后,我们回过头来看看RouteCollection类里面的GetRouteData方法里面的这一句代码:

  foreach (RouteBase base2 in this)

 就是在循环便遍历 Dictionary<string, RouteBase>集合,从中找到相互匹配的路由对象,并返回RouteData。最后我们再次回到RouteCollection中的MapRoute方法,将路由名称和路由对象添加到RouteCollection中,实现了将url映射到相应的Controller和Action。我们获取到RouteData之后,将其作为参数获取到RequestContext,将获取到的RequestContext作为参数来获取到MvcHnadler,并将其注册到事件管道中,这里就是我们上一篇文章介绍的内容。

posted @ 2013-01-28 18:40  肖&申&克  阅读(418)  评论(0编辑  收藏  举报