zoukankan      html  css  js  c++  java
  • MVC 源码系列之路由(一)

    路由系统

    注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口。


    简单的说一下激活路由之前的一些操作。一开始是由MVC中的UrlRouteingModule进行开始MVC的执行,也是说是整个MVC的入口。这是继承HttpModule,可以对管道进行自定义操作的类型。开始看看里面的代码。这个是UrlRouteModule中PostResolveRequestCache的方法,也就是它在init的时候在管道的PostResolveRequestCache的方法里面添加的方法。

    RouteData routeData = this.RouteCollection.GetRouteData(context);
    if (routeData != null)
    {
        IRouteHandler routeHandler = routeData.RouteHandler;
        if (routeHandler == null)
        {
            throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
        }
        if (!(routeHandler is StopRoutingHandler))
        {
            RequestContext requestContext = new RequestContext(context, routeData);
            context.Request.RequestContext = requestContext;
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            if (httpHandler == null)
            {
                object[] args = new object[] { routeHandler.GetType() };
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), args));
            }
            if (httpHandler is UrlAuthFailureHandler)
            {
                if (!FormsAuthenticationModule.FormsAuthRequired)
                {
                    throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));
                }
                UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
            }
            else
            {
                context.RemapHandler(httpHandler);
            }
        }
    }
    
    

    先看下总体代码。一个获得了routeData,然后判断是否为null,然后获得RouteHttpHandle对请求进行处理。判断是否为空,是否为UrlAuthFailureHandler(url验证失败),如果都没问题,重新调用httpHandler(也叫重新映射,激活Controller)。

    • 获得RouteData
    • 获取正确RouteData中的RouteHandler
    • 判断是否为空,否则报错
    • 判断是否为StopRoutingHandler,如果是什么都不做停止路由,否则继续判断
      • 封装出RequestContext,放到context.Request(Context的类型是HttpContextBase)
      • routeHandler获得真正的HttpHandler处理请求者。
        • 是否为空判断,否则报错
        • 是否url验证失败,则不用对他进行处理
        • 最后重映射到IIS7上面的Handler(之后会说)

    这篇主要说的就是第一句。路由。

    route类型

    RouteData routeData=this.RouteCollection.GetRouteData(context)
    
    
    public RouteCollection RouteCollection
    {
        get
        {
            if (this._routeCollection == null)
            {
                this._routeCollection = RouteTable.Routes;
            }
            return this._routeCollection;
        }
        set
        {
            this._routeCollection = value;
        }
    }
    

    this.RouteCollection返回的其实是RouteTable的Routes(类型是RouteCollection)。GetRouteData也是Routes的方法。这个RouteTable是个什么东西呢?

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

    很简单,只有个RouteCollection。很显然是个Route的集合。这个类型继承了Collection,其中的RouteBase就是Route的父类(后面会说道)。这个RouteTable的Routes是个集合的话,我们什么时候给他们添加元素呢?其实在每个MVC的代码中肯定会有这么个代码:

    routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
    

    routes的类型正好是RouteCollection。这个MapRoute是写在RouteCollectionExtensions这个类下面的,可以看出来是路由集合扩展方法的一个类。看看里面的代码。

    public static Route MapRoute(this RouteCollection routes, string name, string url)
    {
        return MapRoute(routes, name, url, null /* defaults */, (object)null /* constraints */);
    }
    
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults)
    {
        return MapRoute(routes, name, url, defaults, (object)null /* constraints */);
    }
    
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints)
    {
        return MapRoute(routes, name, url, defaults, constraints, null /* namespaces */);
    }
    
    public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces)
    {
        return MapRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces);
    }
    
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces)
    {
        return MapRoute(routes, name, url, defaults, null /* constraints */, namespaces);
    }
    
    //具体实现
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
    {
        if (routes == null)
        {
            throw new ArgumentNullException("routes");
        }
        if (url == null)
        {
            throw new ArgumentNullException("url");
        }
    
        Route route = new Route(url, new MvcRouteHandler())
        {
            Defaults = CreateRouteValueDictionaryUncached(defaults),
            Constraints = CreateRouteValueDictionaryUncached(constraints),
            DataTokens = new RouteValueDictionary()
        };
    
        ConstraintValidation.Validate(route);
    
        if ((namespaces != null) && (namespaces.Length > 0))
        {
            route.DataTokens[RouteDataTokenKeys.Namespaces] = namespaces;
        }
    
        routes.Add(name, route);
    
        return route;
    }
    

    前面的所有都是MapRoute的重载,最后一个方法才是最后的实现。一开始入口检查,然后直接将url和MVCRouteHandler放到了Route的构造函数中,然后给Defaults和Constraints赋值了都是调用了CreateRouteValueDictionaryUncached(创建Route的Value的字典,不缓存)。然后创建了一个RouteValueDicitonary()。然后对route的每个部分进行验证合理性。记录Namespaces。创建好之后添加到routes。

    • 创建Route
    • 验证Route的每个部分
    • 记录Namespaces
    • 添加到集合中

    上面就是大概的逻辑,有两个没有仔细说到,一个Route的构造函数,一个是CreateRouteValueDictionaryUncached的方法实现.

    route构造细节


    先说一下Route吧,这个类型是在路由系统中是非常重要的(最后我会和大家理理所有的类的关系)。

    public class Route : RouteBase
    {
        // Fields
        private ParsedRoute _parsedRoute;
        private string _url;
        private const string HttpMethodParameterName = "httpMethod";
    
        // Methods
        // Methods
        public Route(string url, IRouteHandler routeHandler)
        {
            this.Url = url;
            this.RouteHandler = routeHandler;
        }
    
        public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
        {
            this.Url = url;
            this.Defaults = defaults;
            this.RouteHandler = routeHandler;
        }
    
        public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
        {
            this.Url = url;
            this.Defaults = defaults;
            this.Constraints = constraints;
            this.RouteHandler = routeHandler;
        }
    
        public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
        {
            this.Url = url;
            this.Defaults = defaults;
            this.Constraints = constraints;
            this.DataTokens = dataTokens;
            this.RouteHandler = routeHandler;
        }
    
        public override RouteData GetRouteData(HttpContextBase httpContext);
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
        protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
        private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection);
    
        // Properties
        public RouteValueDictionary Constraints { get; set; }
        public RouteValueDictionary DataTokens { get; set; }
        public RouteValueDictionary Defaults { get; set; }
        public IRouteHandler RouteHandler { get; set; }
        public string Url { get; set; }
    }
    
    

    Route的构造函数和属性就是这么多了,构造函数也很简单,只是把参数简单的赋给了属性。上面MapRoute使用的就第一个构造函数,将url和MvcRouteHandler赋值到了Route中。然后又给Defaults和Constraints(约束)赋值了,这边要注意一下这两个类型都是RouteValueDictionary,但是在MapRoute传入的类型是Object。CreateRouteValueDictionaryUncached的实现就是将object转化为RouteValueDictionary。那么我们再来看看这个方法的实现。

    private static RouteValueDictionary CreateRouteValueDictionaryUncached(object values)
    {
        var dictionary = values as IDictionary<string, object>;
        if (dictionary != null)
        {
            return new RouteValueDictionary(dictionary);
        }
    
        return TypeHelper.ObjectToDictionaryUncached(values);
    }
    
    public static RouteValueDictionary ObjectToDictionaryUncached(object value)
    {
        RouteValueDictionary dictionary = new RouteValueDictionary();
    
        if (value != null)
        {
            foreach (PropertyHelper helper in PropertyHelper.GetProperties(value))
            {
                dictionary.Add(helper.Name, helper.GetValue(value));
            }
        }
    
        return dictionary;
    }
    

    如果类型为字典直接就可以放到RouteValueDictionary中。如果不是字典类型是个类,便循环他的属性获得他的属性名称和值添加到创建的RouteValueDictionary。可以对照之前的MapRoute中的参数

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

    defaults传入的就是一个object,它会运行ObjectToDictionaryUncached。循环它的属性,将值和名字放入类型为RouteValueDictionary的dictionary,赋给Route的defaulte属性。理一下之前的逻辑。

    1. 程序运行会去找RouteTable中的Routes获取RouteData
    2. RouteTable中Routes会被Route.MapRoute中添加
      • 传入设置的url和MvcRouteHandler(处理MVC请求的真正请求者)
      • 转化default和Constraints并复制
      • 添加到Routes
        3.调用GetRoutedata,获取正确的RouteData

    GetRouteData


    第三步其实还没说到,this.RouteCollection.GetRouteData(context),看看GetRouteData中最后的方法。

    public RouteData GetRouteData(HttpContextBase httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }
        if (httpContext.Request == null)
        {
            throw new ArgumentException(SR.GetString("RouteTable_ContextMissingRequest"), "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;
    }
    

    有点长。慢慢看,一开始参数检查,然后判断了一个属性RouteExistingFiles,如果为true的话,会将所有的文件包括静态文件都通过MVC的方式去进行检查。属性默认为false。第一个if里面就是说,如果存在文件的话返回null,停止处理请求。然后就循环集合中的route。HttpContext当做参数获取到RouteData。然后判断RouteExistingFiles。这个属性是在RouteBase基类里面,默认为true。所以基本就是找到之后就会返回routeData。

    以上就是GetRoute的逻辑了。看看Route是如何获得GetRouteData的

    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;
    }
    
    

    这里进行一下测试,可以看出一般情况下httpContext.Request.PathInfo都是为空的,前面的AppRelativeCurrentExecutionFilePath部分就是:

    类似这么关系。然后使用Route中的_parsedRoute属性,_parsedRoute类型为ParseRoute的属性,调用了Match()的方法,传入了Default。我猜是应该给没有传参数的部分进行默认值得赋值。返回的是一个RouteValueDictionary对象。创建了一个RouteData,将刚刚获取的Values添加到data的Value属性中。DataToken也添加了到Data的同名属性中。最总返回data。关键点在于values里面是什么。

    再理一下到这的逻辑。

    • 在UrlRoutingModule里面执行了 this.RouteCollction.GetRouteData(context)
      • this.RouteCollction 是RouteTable.Routes Routes是通过MapRoute添加进去的
      • 循环调用GetRouteData(其实调用的都是Route中的GetRouteData方法)
      • 创建一个RouteData,将通过_parsedRoute.Match匹配出来的RouteValueDictionary,添加到RouteData的Value中。如果DataTokens不为空,也将DataTokens放到Route的DataTokens中。

    最后的部分理一下各个类的关系

    this.RouteCollection=RouteTable.Routes(Routes类型为RouteCollection)

    RouteCollection继承Collection

    Route是RouteBase的唯一实现类

    RouteValueDictionary就是一个字典,是Route中Constraints,DataTokens,Defaults的类型

    RouteDat是Route通过GetRouteData返回的类型

    ParsedRoute是Route的属性

    PathSegments是 ParsedtRoute的属性

    PathSegments有两个子类 SeparatorPathSegment 表示/,ContentPathSegment 表示内容。

    PathSubsegment是ContentPathSegment的一个属性

    PathSubsegment有两个子类 LiteralSubsegment表是文字部分 ParameterSubsegment 表是参数部分

    https://referencesource.microsoft.com/ 在线代码,有注释

  • 相关阅读:
    Linux
    Linux
    Linux
    Linux
    Linux
    shell tr命令
    grep 正则表达
    shell 运算符
    shell 重定向
    bzoj 1026 DP,数位统计
  • 原文地址:https://www.cnblogs.com/shaoqi/p/7350816.html
Copyright © 2011-2022 走看看