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方法有个大致的了解了吧。

  • 相关阅读:
    SVM+HOG特征训练分类器
    数字图像处理的基本原理和常用方法
    c++中二进制和整数转化
    目标检测——HOG特征
    相似图片搜索的原理和实现——颜色分布法
    测试文本文件大小
    Direct2D 图形计算
    Direct2D 如何关闭抗锯齿
    MFC窗口显隐
    CISP/CISA 每日一题 22
  • 原文地址:https://www.cnblogs.com/majiang/p/2780591.html
Copyright © 2011-2022 走看看