MVCParseData和Match方法的实现

ParseData


那么首先要了解一下ParseData。

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

其实在Route创建的时候 ,便将属性_parsedRoute赋值了。

public static ParsedRoute Parse(string routeUrl)
{
if (routeUrl == null)
{
routeUrl = string.Empty;
}
if (IsInvalidRouteUrl(routeUrl))
{
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));
} 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 str2 = url.Substring(i);
if (str2.Length > 0)
{
list.Add(str2);
}
return list;
}
string item = url.Substring(i, index - i);
if (item.Length > 0)
{
list.Add(item);
}
list.Add("/");
}
}
return list;
}

直接看代码有点困难,可以将参数代入这样会容易一点。url: "{controller}/{action}/{id}"。一开始判断/的位置。如果有的话,将第一个/之前的都截取下来,放到集合的一个位置,然后添加/,然后循环的时候位置加1 跳过/号,重新开始,到最后的时候将剩下的都添加到集合中。拿上面说的例子集合中的情况应该是这样的。['{controller}','/','{action}','/','{id}']。然后是判断路由的每个部分有没有不规范。最后就是构造ParsedRoute了,将IList转化为IList。每个string 我都称为段(文章后面我也会这样描述)。因为会出现复杂的段 比如{action}-{city}这种复杂的段。后面会说到怎么处理。

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

如果是/的话 就生成一个SeparatorPathSegment放入集合中,不是/的话就在生成一个IList 。用/来分割每个段。复杂的段中会许多部分,c#

中用subsgment来表示子部分。字部分会有两种形式,变量(参数部分)和常量(文字部分)。变量用ParameterSubsegment(参数部分)表示,常量LiteralSubsegment(文字部分)表示。看看里面构造,是如何将路由模板分解为一个一个片段的。

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 str3 = GetLiteral(segment.Substring(startIndex));
if (str3 == null)
{
object[] args = new object[] { segment };
exception = new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "Route_MismatchedParameter", args), "routeUrl");
return null;
}
if (str3.Length > 0)
{
list.Add(new LiteralSubsegment(str3));
}
break;
}
int index = segment.IndexOf('}', num2 + 1);
if (index == -1)
{
object[] objArray2 = new object[] { segment };
exception = new ArgumentException(string.Format(CultureInfo.CurrentUICulture,"Route_MismatchedParameter", objArray2), "routeUrl");
return null;
}
string literal = GetLiteral(segment.Substring(startIndex, num2 - startIndex));
if (literal == null)
{
object[] objArray3 = new object[] { segment };
exception = new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "Route_MismatchedParameter", objArray3), "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;
}

可以看到,将段传入ParseUrlSegment方法。首先创建了一个startIndex,这个是每一次开始的位置,一个段可能是这样的 {action}-{city},里面就包含了两个元素,所以每次开始处理完前一个部分的时候会用startIndex变量记录第二次开始的位置。开始通过IndexOfFirstOpenParameter获得了一个num2,如果返回-1也就是找不到{号,那就直接当文字进行处理。方法内部实现其实挺简单的,获得{的初始位置,如果找不到的话会放回-1。后面还有个判断,条件会比较复杂

((startIndex + 1) == segment.Length) || (((startIndex + 1) < segment.Length) && (segment[startIndex + 1] != '{'))

这个条件是说,只有在{的位置不在最后并且是{{的时候才StartIndex+=2跳过{{寻找接下去的{位置。条件看不懂也没事,这个也就是寻找段中的有效的第一个{位置。然后返回出去,会做判断,如果为位置-1直接通过GetLiteral(获得文字)的方法去过滤文字,如果里面含有{{就被换成{符号。其实没看懂这两个方法,只看懂了是这样去执行的,不知道为什么要这么去执行。如果不是-1,获取}的位置,然后看看{的之前有没有符号,就是{action}-{city}中的这个-,当然在第一次执行的时候{action}前面没有任何符号,当执行到{city}的时候

string literal = GetLiteral(segment.Substring(startIndex, num2 - startIndex));

就会通过这个区获取 - 号,也就是默认的文本。添加到List中。最后在{的位置和}的位置之间,将文字表示的变量放到ParameterSubsegment。然后index+1进行下一轮的筛选。应为前面两个不懂的方法,会造成一些奇怪的现象,比如{controller}/{{city}}/{id} 这样的路由的话,中的{{city}}将会变成一个文本,如果改成三个{}包涵的话,就可以将{号转义成功了。有兴趣的同学可以试试。

Match的实现

可以看出,集合中无非就是SeparatorPathSegment和ContentPathSegment中又包含很多个subsegment。最后就是这个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;
}

将之前的获得的virtualPath传入方法,通过SplitUrlToPathSegmentStrings分割,前面说过这个是通过/进行分组的。然后循环PathSegments。在上面说过了,这个是将模板转化成ParseData,里面包含了两种类型SeparatorPathSegment和ContentPathSegment。会通过每一个部分去一一匹配。如果没有匹配上就返回null。判断实际传入的url经过/分割之后的数量是不是比模板的数量少。如果url的长度少于模板长度,将flag为true。如果是/号也就是SeparatorPathSegment就跳过,如果是ContentPathSegment有内容的话,this.MatchContentPathSegment通过这个方法进行判断如果没有符合的话就返回null,并且这个方法会将模板中变量于url中对应的部分放到字典中去。如果url的长度大于模板的长度,而且大于的长度部分不是/号,便视为不匹配。最后的时候,使用defaultValues,url中没有传入模板的值得话将会使用默认值去代替。理一下最后对比的逻辑。

  1. 获取url的分割后的组
  2. 进行循环比对
    • SeparatorPathSegment
    • ContentPathSegment MatchContentPathSegment方法将模板中的值和url中进行匹配放入到字典中。
  3. 测试url的时间长度是不是比模版的长
  4. 将默认值放入的最后的集合中去

现在关键是的是 MatchContentPathSegment方法的实现。

private bool MatchContentPathSegment(ContentPathSegment routeSegment, string requestPathSegment, RouteValueDictionary defaultValues, RouteValueDictionary matchedValues)
{ if (string.IsNullOrEmpty(requestPathSegment))
{
if (routeSegment.Subsegments.Count <= 1)
{
object obj2;
ParameterSubsegment subsegment3 = routeSegment.Subsegments[0] as ParameterSubsegment;
if (subsegment3 == null)
{
return false;
}
if (defaultValues.TryGetValue(subsegment3.ParameterName, out obj2))
{
matchedValues.Add(subsegment3.ParameterName, obj2);
return true;
}
}
return false;
} int LastIndex = requestPathSegment.Length;
int indexOfLastSegmentUsed = routeSegment.Subsegments.Count - 1;
ParameterSubsegment subsegment = null;
LiteralSubsegment subsegment2 = null; while (indexOfLastSegmentUsed >= 0)
{
//第一部分
int newLastIndex = LastIndex;
ParameterSubsegment subsegment4 = routeSegment.Subsegments[indexOfLastSegmentUsed] as ParameterSubsegment;
if (subsegment4 != null)
{
subsegment = subsegment4;
}
else
{
LiteralSubsegment subsegment5 = routeSegment.Subsegments[indexOfLastSegmentUsed] as LiteralSubsegment;
if (subsegment5 != null)
{
subsegment2 = subsegment5;
int startIndex = LastIndex - 1;
if (subsegment != null)
{
startIndex--;
}
if (startIndex < 0)
{
return false;
}
int indexOfLiteral = requestPathSegment.LastIndexOf(subsegment5.Literal, startIndex, StringComparison.OrdinalIgnoreCase);
if (indexOfLiteral == -1)
{
return false;
}
if ((indexOfLastSegmentUsed == (routeSegment.Subsegments.Count - 1)) && ((indexOfLiteral + subsegment5.Literal.Length) != requestPathSegment.Length))
{
return false;
}
newLastIndex = indexOfLiteral;
}
}
//第二部分
if ((subsegment != null) && (((subsegment2 != null) && (subsegment4 == null)) || (indexOfLastSegmentUsed == 0)))
{
int parameterStartIndex;
int parameterTextLength;
if (subsegment2 == null)
{
//没有文字部分 全都是参数部分
if (indexOfLastSegmentUsed == 0)
{
parameterStartIndex = 0;
}
else
{
parameterStartIndex = newLastIndex;
}
parameterTextLength = LastIndex;
}
else if ((indexOfLastSegmentUsed == 0) && (subsegment4 != null))
{
//文字部分在参数部分后面
parameterStartIndex = 0;
parameterTextLength = LastIndex;
}
else
{
//文字部分在参数前面
parameterStartIndex = newLastIndex + subsegment2.Literal.Length;
parameterTextLength = LastIndex - parameterStartIndex;
}
string str = requestPathSegment.Substring(parameterStartIndex, parameterTextLength);
if (string.IsNullOrEmpty(str))
{
return false;
}
matchedValues.Add(subsegment.ParameterName, str);
subsegment = null;
subsegment2 = null;
}
LastIndex = newLastIndex;
indexOfLastSegmentUsed--;
} //第三部分
if (LastIndex != 0)
{
return (routeSegment.Subsegments[0] is ParameterSubsegment);
}
return true;
}

代码有点长,主要分为两部分。requestPathSegment为空和不为空的两种情况。

如果requestPathSegment为空的话,获取ParameterSubsegment,然后从传入的默认值字典中去取值。如果在默认值中有值的话添加到matchedValues中,如果没有返回false(也就是没有匹配成功)。

requestPathSegment有值的话,先获取url的长度和ContentPathSegment里面的Subsegments个数(前面时候过,每个段可能会有多个部分)。分别创建了ParameterSubsegment和LiteralSubsegment来判断这个部分是参数还是文字部分。将subsegments转化为ParameterSubsegment参数类型如果不为空的话便赋给subsegment,或者是LiteralSubsegment文字类型。如果是LiteralSubsegment,将subsegment2 = subsegment5赋给subsegment2。int startIndex = LastIndex(==requestPathSegment.Length) - 1,如果这个段中 只有文字或者是参数部分是在文字部分前面(例如:{actiuon}page)这样的就不用--,如果是其他的情况需要startIndex--。如果小于0了之后便返回false。

这个变量的作用就是下面这句的参数,找到文字的位置。

int indexOfLiteral = requestPathSegment.LastIndexOf(subsegment5.Literal, startIndex, StringComparison.OrdinalIgnoreCase);

找到模板中的文字在url中的位置,开始位置就是startIndex。如果找不到返回false,匹配失败。然后又有一个条件判断

 if ((indexOfLastSegmentUsed == (routeSegment.Subsegments.Count - 1)) && ((indexOfLiteral + subsegment5.Literal.Length) != requestPathSegment.Length))

如果indexOfLastSegmentUsed是最后一个subsegment并且是LiteralSubsegment,获得的文字的长度和请求的长不同的话,也放回false。indexOfLiteral这个变量就是说明如果在既有参数又有文字的段里面的话文字的位置。这个判断就判断了文字部分是否相同(首先看是否含有模板中的字符,然后判断长度是否相同)。如果这个判断为false的话就newLastIndex = indexOfLiteral。接下去又是一个长判断。

 if ((subsegment != null) && (((subsegment2 != null) && (subsegment4 == null)) || (indexOfLastSegmentUsed == 0)))

上面的情况大致分了几种情况来讨论着个条件。里面的具体实现是什么呢?开始创建了parameterStartIndex和parameterTextLength也就是参数的开始的位置和长度,最后给在requestPathSegment中截取出参数的值,和subsegment的name属性放到字典中去。里面还是分情况对parameterStartIndex和indexOfLastSegmentUsed进行赋值。

  1. {action} 的情况,只剩下一个参数没有匹配, parameterStartIndex = 0; parameterTextLength = LastIndex;
  2. {aciont}page 的情况就是既有参数也有文字 subsegment2不为null 如果参数部分已经是最后了,也就是在参数部分在文字部分的前面时,parameterStartIndex = 0; parameterTextLength = LastIndex;
  3. 如果是 Page{action} 就会走第三个判断 parameterStartIndex = newLastIndex + subsegment2.Literal.Length; parameterTextLength = LastIndex - parameterStartIndex;

其实判断里面分了三部分:

  • 第一部分获取参数部分或者是文字部分的元素保存在subsegment和subsegment2里面(随便说一下subsegment4和subsegment5是表明当前循环中的Subsegment是文字部分还是参数部分)。 如果是文字部分的话找出文字部分在url中的位置,如果这个段只有文字且长度不对,则返回false
  • 第二部分,首先subsegment要有参数部分(有参数部分才能发到字典里面去) 并且 如果有段中由文字部分的话需要循环到文字部分才会进到循环里面。
    • 没有文字部分,都是参数部分,如果只有一个那就直接将parameterStartIndex为0,parameterStartIndex=LastIndex(=requestPathSegment.Length)也就是所有的。如果有两个subsegment会将所有的数据都给第一个sub(例如:{action}{id} url:RouteTest 字典中的情况为[action,RouteTest],[id,""])
    • 有文字部分在参数部分后面,但是参数部分在文字部分的前面({acion}page 而且参数部分前面就没有文字部分的情况),这种情况会先处理文字部分,再处理文字部分的时候会将newLastIndex = indexOfLiteral文字开头的部分给newLastIndex变量,然后变量间接的parameterTextLength这个长度。
    • 有文字部分在参数部分前面(例如:page{action}),会通过newLastIndex + subsegment2.Literal.Length 最后的位置加文字的长度,parameterTextLength = LastIndex - parameterStartIndex;来算出参数值得位置。
    • 其实所有的情况都可以分解为以上三种情况 举个例子:{action}page{action} 系统就会分解成 page{action}和{action}去处理,这就是上面的第三种情况和第一中情况。
  • 第三部分 最后的LastIndex会为0 ,如果最后还没有为0的情况,判断一下参数的第一个是不是ParameterSubsegment

MVC 源码系列之路由(二)的更多相关文章

  1. MVC 源码系列之路由(一)

    路由系统 注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口. 简单的说一下激活路由之前的一些操作.一开始是由MVC ...

  2. hbase源码系列(十二)Get、Scan在服务端是如何处理

    hbase源码系列(十二)Get.Scan在服务端是如何处理?   继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...

  3. MVC 源码系列之控制器执行(二)

    ## 控制器的执行 上一节说道Controller中的ActionInvoker.InvokeAction public virtual bool InvokeAction(ControllerCon ...

  4. MVC 源码系列之控制器执行(一)

    控制器的执行 之前说了Controller的激活,现在看一个激活Controller之后控制器内部的主要实现. public interface IController { void Execute( ...

  5. MVC 源码系列之控制器激活(一)

    Controller的激活 上篇说到Route的使用,GetRoute的方法里面获得RouteData.然后通过一些判断,将最后的RouteData中的RouteHandler添加到context.R ...

  6. hbase源码系列(十二)Get、Scan在服务端是如何处理?

    继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...

  7. MVC 源码系列之控制器激活(二)之GetControllerType和GetcontrollerInstance

    GetControllerType和GetcontrollerInstance GetControllerType protected internal virtual Type GetControl ...

  8. MVC源码解析 - UrlRoutingModule / 路由注册

    从前面篇章的解析, 其实能看的出来, IHttpModule 可以注册很多个, 而且可以从web.config注册, 可以动态注册. 但是有一个关键性的Module没有讲, 这里就先来讲一下这个关键性 ...

  9. nova创建虚拟机源码系列分析之二 wsgi模型

    openstack nova启动时首先通过命令行或者dashborad填写创建信息,然后通过restful api的方式调用openstack服务去创建虚拟机.数据信息从客户端到达openstack服 ...

随机推荐

  1. 奇葩的狐火浏览器border属性

    今天接到一个bug任务,客户反映火狐浏览器访问时某个商品楼层不显示商品.我立即打开我的火狐浏览器发现没有复现这个bug,后来经过一番折腾,才发现火狐浏览器缩放到90%时,商品楼层果然就消失了,而且每台 ...

  2. Python爬虫之urllib.parse详解

    Python爬虫之urllib.parse 转载地址 Python 中的 urllib.parse 模块提供了很多解析和组建 URL 的函数. 解析url 解析url( urlparse() ) ur ...

  3. 02-第一个Python程序

    第一个HelloPython程序 1.1Python源程序的基本概念 Python源程序是一个特殊格式的文本文件,可以使用任意文本编辑软件做Python的开发 Python程序的文件扩展名通常都是.p ...

  4. python set 集合操作

    转自:https://www.cnblogs.com/alex3714/articles/5740985.html s = set([3,5,9,10]) #创建一个数值集合 t = set(&quo ...

  5. SubwayPlan

    GitHub:https://github.com/wakerh1/subwayBJ 北京地铁图片: 地铁出行路线规划项目需求及实现概要: 1.设计一种文件格式用于存储地铁信息 2.设计启动程序并读取 ...

  6. AC电源品字插座和空开接线图

  7. 【BZOJ1488】[HNOI2009]图的同构计数

    题目链接 题意 求 n 个点的同构意义下不同的图的数量.\((n\leq 60)\) Sol \(Polya\) 定理的练手题. 我们这里先把边的存在与否变成对边进行黑白染色,白色代表不存在,这样就变 ...

  8. MAN RPM

    RPM(8)   Red Hat Linux   RPM(8) NAME/名称       rpm - RPM Package Manager/RPM-RPM包管理器SYNOPSIS/简介  QUER ...

  9. 【leetcode】1104. Path In Zigzag Labelled Binary Tree

    题目如下: In an infinite binary tree where every node has two children, the nodes are labelled in row or ...

  10. CSS-动画,让图片上的图形有涨起来的效果(逐渐变高)和(逐渐变长)

    效果图: html: <div class="inner3"> <div class="over"> <img src=" ...