zoukankan      html  css  js  c++  java
  • 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,并将其注册到事件管道中,这里就是我们上一篇文章介绍的内容。

  • 相关阅读:
    mycat 1.6.6.1 distinct报错问题
    linux下Tomcat+OpenSSL配置单向&双向认证(自制证书)
    Too many open files错误与解决方法
    Tomcat类加载机制触发的Too many open files问题分析(转)
    spring boot 自签发https证书
    redis集群如何解决重启不了的问题
    centos7 docker 安装 zookeeper 3.4.13 集群
    centos7用docker安装kafka
    心怀感恩
    不使用if switch 各种大于 小于 判断2个数的大小
  • 原文地址:https://www.cnblogs.com/ghhlyy/p/2879737.html
Copyright © 2011-2022 走看看