zoukankan      html  css  js  c++  java
  • asp.net mvc源码分析Route的GetRouteData

    我知道Route这里东西应该算路由,这里把它放到mvc里面有些不怎么合适,但是我想大家多数遇到路由都是在mvc的时候吧.首先我们还是来看看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;
    }
    

     我还是沿用以前的思路,已一个demo来便说明吧,现在假设我的路由信息是:

       routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
                routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });
                routes.MapRoute(
                    "Default", // 路由名称
                    "{controller}/{action}/{id}", // 带有参数的 URL
                    new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // 参数默认值
                    new { controller="([A-Za-z])*" },
                    new string[] { "MvcApp.Controllers" }
                );

       请求路径: http://localhost:7503/Home/index
    我们知道httpContext.Request.AppRelativeCurrentExecutionFilePath的返回值都是以~/打头的,这里httpContext.Request.PathInfo为空,多数情况下该属性也是空的,所以这里的virtualPath=Home/index

    有关MapRoute的代码可以参照

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

    首先调用_parsedRoute.Match(virtualPath, this.Defaults)获取一个RouteValueDictionary ,至于这个方法的具体实现放到后面来说,然后实例化一个RouteData ,并且把先前的RouteValueDictionary的值添加到先前实例化的  RouteData中,如果DataTokens有元素的话也加入到RouteData的DataTokens中来。不过这个过程有个约束的处理

     if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
        {
            return null;
        }

    其中RouteDirection的定义如下:
        public enum RouteDirection
    {
        IncomingRequest,
        UrlGeneration
    }
    约束检查失败而返回null,现在我们来看看ProcessConstraints方法:

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

    如果Constraints有元素,依次检查每个成员,检查方法主要是调用ProcessConstraint方法,

    protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        object obj2;
        IRouteConstraint constraint2 = constraint as IRouteConstraint;
        if (constraint2 != null)
        {
            return constraint2.Match(httpContext, this, parameterName, values, routeDirection);
        }
        string str = constraint as string;
        if (str == null)
        {
            throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[] { parameterName, this.Url }));
        }
        values.TryGetValue(parameterName, out obj2);
        string input = Convert.ToString(obj2, CultureInfo.InvariantCulture);
        string pattern = "^(" + str + ")$";
        return Regex.IsMatch(input, pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase);
    }
    

     这里首先检查我们的约束类型,如果它是IRouteConstraint那么就直接调用它的Match方法,约束不是IRouteConstraint那 么就转化为字符串,再把约束验证的值从RouteValueDictionary 中取出来转化为字符串,最后在用正则表达式来验证我们的值是否通过。
    好,现在让我们来看看this._parsedRoute.Match(virtualPath, this.Defaults);这个方法是然后获取RouteValueDictionary的:

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

     这里RouteParser的SplitUrlToPathSegmentStrings方法很简单,就是把url字符串按照”/“来分割

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

     所以Match方法中的source 是成员是很好明白的,我的示例中它的值是:

    在ParsedRoute的Match方法中用到了一个PathSegments属性。该属性定义为:private IList<PathSegment> PathSegments { get; set; }真正该改属性复制的是ParsedRoute的构造函数。而Route中的_parsedRoute的获取是在Url属性中

       public string Url
        {
            get
            {
                return (this._url ?? string.Empty);
            }
            set
            {
                this._parsedRoute = RouteParser.Parse(value);
                this._url = value;
            }
        }

    在我们这个例子中url的value={controller}/{action}/{id}
    其中RouteParser的Parse方法如下:

    public static ParsedRoute Parse(string routeUrl)
    {
        if (routeUrl == null)
        {
            routeUrl = string.Empty;
        }
        if ((routeUrl.StartsWith("~", StringComparison.Ordinal) || routeUrl.StartsWith("/", StringComparison.Ordinal)) || (routeUrl.IndexOf('?') != -1))
        {
            throw new ArgumentException(SR.GetString("Route_InvalidRouteUrl"), "routeUrl");
        }
        IList<string> pathSegments = SplitUrlToPathSegmentStrings(routeUrl);
        Exception exception = ValidateUrlParts(pathSegments);
        if (exception != null)
        {
            throw exception;
        }
        return new ParsedRoute(SplitUrlToPathSegments(pathSegments));
    }
    

     在这里我们知道url不能以~ /打头,也不能含有?。这里的pathSegments也很好理解,其值:

    这里的ValidateUrlParts主要就是验证这里的pathSegments集合,ValidateUrlParts这里的具体是怎么验证的很是复杂,这里就忽略了它吧。

    有关SplitUrlToPathSegments的方法比较复杂,分为两部分,把urlParts中的"/" 变成SeparatorPathSegment对象作为站位,像{controller}这样转换为ContentPathSegment对象,其中它的 subsegments是一个List<PathSubsegment>实例,

    private static IList<PathSegment> SplitUrlToPathSegments(IList<string> urlParts)
    {
        List<PathSegment> list = new List<PathSegment>();
        foreach (string str in urlParts)
        {
            if (IsSeparator(str))
            {
                list.Add(new SeparatorPathSegment());
            }
            else
            {
                Exception exception;
                IList<PathSubsegment> subsegments = ParseUrlSegment(str, out exception);
                list.Add(new ContentPathSegment(subsegments));
            }
        }
        return list;
    }
    
    internal static bool IsSeparator(string s)
    {
        return string.Equals(s, "/", StringComparison.Ordinal);
    }
    
     private static IList<PathSubsegment> ParseUrlSegment(string segment, out Exception exception)
    {
        int startIndex = 0;
        List<PathSubsegment> list = new List<PathSubsegment>();
        while (startIndex < segment.Length)
        {
            int num2 = IndexOfFirstOpenParameter(segment, startIndex);
            if (num2 == -1)
            {
                string str = GetLiteral(segment.Substring(startIndex));
                if (str == null)
                {
                    exception = new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_MismatchedParameter"), new object[] { segment }), "routeUrl");
                    return null;
                }
                if (str.Length > 0)
                {
                    list.Add(new LiteralSubsegment(str));
                }
                break;
            }
            int index = segment.IndexOf('}', num2 + 1);
            if (index == -1)
            {
                exception = new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_MismatchedParameter"), new object[] { segment }), "routeUrl");
                return null;
            }
            string literal = GetLiteral(segment.Substring(startIndex, num2 - startIndex));
            if (literal == null)
            {
                exception = new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_MismatchedParameter"), new object[] { segment }), "routeUrl");
                return null;
            }
            if (literal.Length > 0)
            {
                list.Add(new LiteralSubsegment(literal));
            }
            string parameterName = segment.Substring(num2 + 1, (index - num2) - 1);
            list.Add(new ParameterSubsegment(parameterName));
            startIndex = index + 1;
        }
        exception = null;
        return list;
    }
    
    private static int IndexOfFirstOpenParameter(string segment, int startIndex)
    {
        while (true)
        {
            startIndex = segment.IndexOf('{', startIndex);
            if (startIndex == -1)
            {
                return -1;
            }
            if (((startIndex + 1) == segment.Length) || (((startIndex + 1) < segment.Length) && (segment[startIndex + 1] != '{')))
            {
                return startIndex;
            }
            startIndex += 2;
        }
    }
    
     private static string GetLiteral(string segmentLiteral)
    {
        string str = segmentLiteral.Replace("{{", "").Replace("}}", "");
        if (!str.Contains("{") && !str.Contains("}"))
        {
            return segmentLiteral.Replace("{{", "{").Replace("}}", "}");
        }
        return null;
    }
    
     internal sealed class LiteralSubsegment : PathSubsegment
    {
        // Methods
        public LiteralSubsegment(string literal)
        {
            this.Literal = literal;
        }
    
        // Properties
        public string Literal { get; private set; }
    }
    internal sealed class ParameterSubsegment : PathSubsegment
    {
        // Methods
        public ParameterSubsegment(string parameterName)
        {
            if (parameterName.StartsWith("*", StringComparison.Ordinal))
            {
                this.ParameterName = parameterName.Substring(1);
                this.IsCatchAll = true;
            }
            else
            {
                this.ParameterName = parameterName;
            }
        }
    
        // Properties
        public bool IsCatchAll { get; private set; }
    
        public string ParameterName { get; private set; }
    }
        internal sealed class ContentPathSegment : PathSegment
        {
            // Methods
            public ContentPathSegment(IList<PathSubsegment> subsegments)
            {
                this.Subsegments = subsegments;
            }
    
            // Properties
            public bool IsCatchAll
            {
                get
                {
                    return this.Subsegments.Any<PathSubsegment>(seg => ((seg is ParameterSubsegment) && ((ParameterSubsegment)seg).IsCatchAll));
                }
            }
    
            public IList<PathSubsegment> Subsegments { get; private set; }
        }
    

     ParseUrlSegment方法主要就是获取代码是:

    string parameterName = segment.Substring(num2 + 1, (index - num2) - 1);
            list.Add(new ParameterSubsegment(parameterName));

    例如我们传进来的字符串是{controller},那么这里parameterName就会处理为controller。如果传入的参数没有{、}如Channel,那么ParseUrlSegment将处理为

       if (str.Length > 0)
                        {
                            list.Add(new LiteralSubsegment(str));
                        }
                        break;

    现在是时候回到ParsedRoute的Match方法了,首先我们来看看这个方法用到的PathSegments是个什么东东,我的url是{controller}/{action}/{id}它对应的PathSegments如图:

    着了我们看看几次循环的主要变量取值:

    当前ContentPathSegment的IsCatchAll为false。那么我们现在主要关心的是MatchContentPathSegment方法了。

    private bool MatchContentPathSegment(ContentPathSegment routeSegment, string requestPathSegment, RouteValueDictionary defaultValues, RouteValueDictionary matchedValues)
    {
        if (string.IsNullOrEmpty(requestPathSegment))
        {
            if (routeSegment.Subsegments.Count <= 1)
            {
                object obj2;
                ParameterSubsegment subsegment = routeSegment.Subsegments[0] as ParameterSubsegment;
                if (subsegment == null)
                {
                    return false;
                }
                if (defaultValues.TryGetValue(subsegment.ParameterName, out obj2))
                {
                    matchedValues.Add(subsegment.ParameterName, obj2);
                    return true;
                }
            }
            return false;
        }
        int length = requestPathSegment.Length;
        int num2 = routeSegment.Subsegments.Count - 1;
        ParameterSubsegment subsegment2 = null;
        LiteralSubsegment subsegment3 = null;
        while (num2 >= 0)
        {
            int num3 = length;
            ParameterSubsegment subsegment4 = routeSegment.Subsegments[num2] as ParameterSubsegment;
            if (subsegment4 != null)
            {
                subsegment2 = subsegment4;
            }
            else
            {
                LiteralSubsegment subsegment5 = routeSegment.Subsegments[num2] as LiteralSubsegment;
                if (subsegment5 != null)
                {
                    subsegment3 = subsegment5;
                    int startIndex = length - 1;
                    if (subsegment2 != null)
                    {
                        startIndex--;
                    }
                    if (startIndex < 0)
                    {
                        return false;
                    }
                    int num5 = requestPathSegment.LastIndexOf(subsegment5.Literal, startIndex, StringComparison.OrdinalIgnoreCase);
                    if (num5 == -1)
                    {
                        return false;
                    }
                    if ((num2 == (routeSegment.Subsegments.Count - 1)) && ((num5 + subsegment5.Literal.Length) != requestPathSegment.Length))
                    {
                        return false;
                    }
                    num3 = num5;
                }
            }
            if ((subsegment2 != null) && (((subsegment3 != null) && (subsegment4 == null)) || (num2 == 0)))
            {
                int num6;
                int num7;
                if (subsegment3 == null)
                {
                    if (num2 == 0)
                    {
                        num6 = 0;
                    }
                    else
                    {
                        num6 = num3 + subsegment3.Literal.Length;
                    }
                    num7 = length;
                }
                else if ((num2 == 0) && (subsegment4 != null))
                {
                    num6 = 0;
                    num7 = length;
                }
                else
                {
                    num6 = num3 + subsegment3.Literal.Length;
                    num7 = length - num6;
                }
                string str = requestPathSegment.Substring(num6, num7);
                if (string.IsNullOrEmpty(str))
                {
                    return false;
                }
                matchedValues.Add(subsegment2.ParameterName, str);
                subsegment2 = null;
                subsegment3 = null;
            }
            length = num3;
            num2--;
        }
        if (length != 0)
        {
            return (routeSegment.Subsegments[0] is ParameterSubsegment);
        }
        return true;
    }
    

     这个方法就是真正把路由参数和路由的值给关联起来,如果参数requestPathSegment为null则参数值从defaultValues中获取,

     if (defaultValues.TryGetValue(subsegment.ParameterName, out obj2))
                {
                    matchedValues.Add(subsegment.ParameterName, obj2);
                    return true;
                }

    否则从我们传递进来的requestPathSegment获取。

       string str = requestPathSegment.Substring(num6, num7);
                if (string.IsNullOrEmpty(str))
                {
                    return false;
                }
                matchedValues.Add(subsegment2.ParameterName, str);

    Match方法在结束之前会检查我们的defaultValues字典,把defaultValues中的key(matchedValues不存 在对应的key)加到matchedValues中来。整个match方法及结束了,结合前面的东西我们也就可以明白Route类的 GetRouteData方法了。
    为了跟好的理解上面个方法,我这里再举一个demo:

     路由信息: routes.MapRoute("Default", "{ChannelName}/{action}", new { controller = "Home", action = "Index" });

    请求路径:http://localhost:7503/men/index

    调用Match方法的  virtualPath=men/index,

    source取值:

    PathSegments取值:

    至于Match方法中的有一种路径是调用MatchCatchAll方法,

    private void MatchCatchAll(ContentPathSegment contentPathSegment, IEnumerable<string> remainingRequestSegments, RouteValueDictionary defaultValues, RouteValueDictionary matchedValues)
    {
        object obj2;
        string str = string.Join(string.Empty, remainingRequestSegments.ToArray<string>());
        ParameterSubsegment subsegment = contentPathSegment.Subsegments[0] as ParameterSubsegment;
        if (str.Length > 0)
        {
            obj2 = str;
        }
        else
        {
            defaultValues.TryGetValue(subsegment.ParameterName, out obj2);
        }
        matchedValues.Add(subsegment.ParameterName, obj2);
    }
    

    它要求contentPathSegment.IsCatchAll为true。从 ParameterSubsegment类的定义可以知道当且仅当传进来parameterName以*打头才是True。parameterName是从url中来的,也就是说url中要含有*,IsCatchAll才为true。在前面提到的ValidateUrlParts方法会验证url的,其主要代码如下:

    private static Exception ValidateUrlParts(IList<string> pathSegments)
    {
        HashSet<string> usedParameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        bool? nullable = null;
        bool flag = false;
        foreach (string str in pathSegments)
        {
            bool flag2;
            if (flag)
            {
                return new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_CatchAllMustBeLast"), new object[0]), "routeUrl");
            }
     

            if (!flag2)
            {
                Exception exception;
                IList<PathSubsegment> pathSubsegments = ParseUrlSegment(str, out exception);
     
                flag = pathSubsegments.Any<PathSubsegment>(seg => (seg is ParameterSubsegment) && ((ParameterSubsegment) seg).IsCatchAll);
            }
        }
        return null;
    }
    所以以上的那个MatchCatchAll执行的条件也很明显就是url中带有*号,并且是最后一个参数带有*,希望大家注意一下。大家看到这里相信对Route类的GetRouteData方法有个大致的了解了吧。

  • 相关阅读:
    HDU 4069 Squiggly Sudoku
    SPOJ 1771 Yet Another NQueen Problem
    POJ 3469 Dual Core CPU
    CF 118E Bertown roads
    URAL 1664 Pipeline Transportation
    POJ 3076 Sudoku
    UVA 10330 Power Transmission
    HDU 1426 Sudoku Killer
    POJ 3074 Sudoku
    HDU 3315 My Brute
  • 原文地址:https://www.cnblogs.com/majiang/p/2780591.html
Copyright © 2011-2022 走看看