MVC 源码系列之路由(二)
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中没有传入模板的值得话将会使用默认值去代替。理一下最后对比的逻辑。
- 获取url的分割后的组
- 进行循环比对
- SeparatorPathSegment
- ContentPathSegment MatchContentPathSegment方法将模板中的值和url中进行匹配放入到字典中。
- 测试url的时间长度是不是比模版的长
- 将默认值放入的最后的集合中去
现在关键是的是 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进行赋值。
- {action} 的情况,只剩下一个参数没有匹配, parameterStartIndex = 0; parameterTextLength = LastIndex;
- {aciont}page 的情况就是既有参数也有文字 subsegment2不为null 如果参数部分已经是最后了,也就是在参数部分在文字部分的前面时,parameterStartIndex = 0; parameterTextLength = LastIndex;
- 如果是 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 源码系列之路由(二)的更多相关文章
- MVC 源码系列之路由(一)
路由系统 注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口. 简单的说一下激活路由之前的一些操作.一开始是由MVC ...
- hbase源码系列(十二)Get、Scan在服务端是如何处理
hbase源码系列(十二)Get.Scan在服务端是如何处理? 继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...
- MVC 源码系列之控制器执行(二)
## 控制器的执行 上一节说道Controller中的ActionInvoker.InvokeAction public virtual bool InvokeAction(ControllerCon ...
- MVC 源码系列之控制器执行(一)
控制器的执行 之前说了Controller的激活,现在看一个激活Controller之后控制器内部的主要实现. public interface IController { void Execute( ...
- MVC 源码系列之控制器激活(一)
Controller的激活 上篇说到Route的使用,GetRoute的方法里面获得RouteData.然后通过一些判断,将最后的RouteData中的RouteHandler添加到context.R ...
- hbase源码系列(十二)Get、Scan在服务端是如何处理?
继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...
- MVC 源码系列之控制器激活(二)之GetControllerType和GetcontrollerInstance
GetControllerType和GetcontrollerInstance GetControllerType protected internal virtual Type GetControl ...
- MVC源码解析 - UrlRoutingModule / 路由注册
从前面篇章的解析, 其实能看的出来, IHttpModule 可以注册很多个, 而且可以从web.config注册, 可以动态注册. 但是有一个关键性的Module没有讲, 这里就先来讲一下这个关键性 ...
- nova创建虚拟机源码系列分析之二 wsgi模型
openstack nova启动时首先通过命令行或者dashborad填写创建信息,然后通过restful api的方式调用openstack服务去创建虚拟机.数据信息从客户端到达openstack服 ...
随机推荐
- 理解PHP面向对象三大特性
一.封装性 目的:保护类里面的数据,让类更安全, protected和private只能在类中或子类访问,通过public提供有限的接口供外部访问,封装是控制访问,而不是拒绝访问 封装关键字:publ ...
- Spring Boot实现SAAS平台的基本思路
一.SAAS是什么 SaaS是Software-as-a-service(软件即服务)它是一种通过Internet提供软件的模式,厂商将应用软件统一部署在自己的服务器 上,客户可以根据自己实际需求,通 ...
- jQuery学习总结06-插件开发
本文是参考了Joey的博客后整理的. 先从一个简单扩展jQuery对象的demo开始说起: //sample:扩展jquery对象的方法,redTextColor()用于改变字体颜色. (functi ...
- 关于jsp删除成功,添加成功等之后 页面自动跳转的js写法
因为比较常用,所以写在博客里保存起来,防止以后忘了不会写了: 删除成功,<span id="time" style="background:red"> ...
- 028-实现阿里云ESC多FLAT网络
实现类似于阿里云ECS的网络结构,其效果为云主机拥有两块和两个不同的网络,一个网络是用于用于和外网连接,一个用于内网通信,但宿主机上至少有两个网卡,整体配置如下:1.在wmare里给宿主机添加一块网卡 ...
- Crazy Search POJ - 1200 (字符串哈希hash)
Many people like to solve hard puzzles some of which may lead them to madness. One such puzzle could ...
- 【学习】019 SpringBoot
一.SpringBoot介绍 1.1.SpringBoot简介 在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配 ...
- Python核心技术与实战——三|字符串
一.字符串基础 Python的字符串支持单引号('').双引号("")和三引号之中('''....'''和"""...""&quo ...
- Linux shell 批量验证端口连通性
工作中会遇到验证到某某服务器端口是否连通,如果IP或端口多时,用shell还是很省时省力的,看下面的脚本: #!/bin/bash # #database check #set -o nounset ...
- java Byte源码分析
源码: public static int toUnsignedInt(byte x) { return ((int) x) & 0xff; } 原理: -128(byte) 原码:10000 ...