路由系统

注释:这部分的源码是通过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/ 在线代码,有注释

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

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

    MVCParseData和Match方法的实现 ### ParseData 那么首先要了解一下ParseData. //namespace Route public string Url { get ...

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

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

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

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

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

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

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

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

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

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

  7. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

随机推荐

  1. VSCode配置 Debugger for Chrome插件

    Debugger for Chrome这个插件是直接在vscode里面进行调试js文件,跟谷歌的控制台是一样的功能,下载了它就不用打开浏览器的控制台就能进行打断点. 首先在左侧扩展栏找到这个插件下载好 ...

  2. 源讯科技(中国)有限公司(Atos Worldline)

    源讯公司是欧洲***的IT服务公司,去年营收达到88亿欧元,在全球52个国家拥有77100名员工.Worldline为Atos(源讯)全资子公司,专注于金融支付领域.Worldline在B2B及B2C ...

  3. 10年前文章_使用opkg 管理软件更新

    为避免调试过程中每次都要刷写flash, 可以使用opkg 管理工具来实现单个包更新 一.首先配置http 服务器,使之能访问生成的ipkg 格式的包,例如你的工作目录在/home/xxx/build ...

  4. XNUCA 2019ezPHP

    ezPHP 源码很简单(感觉越简单的源码越不好搞),一个写文件的功能且只能写文件名为[a-z.]* 的文件,且文件内容存在黑名单过滤,并且结尾被加上了一行,这就导致我们无法直接写入.htaccess里 ...

  5. Python---进阶---logging---装饰器打印日志

    #### logging - logging.debug - logging.info - logging.warning - logging.error - logging.critical --- ...

  6. day5 函数

      1.求全部元素的和 [1,2,1,2,3,3,3,3] 遍历 a = [1,2,1,2,3,3,3,3] sum = 0 n = len(a)-1 while n>=0: sum += a[ ...

  7. PageOffice修改注册码升级版本

    java: 删除pageoffice.jar所在目录(一般是WEB-INF/lib)下的license.lic文件(若服务器运行时找不到该文件,可在浏览器地址栏里面通过localhost方式访问pos ...

  8. codeforces 819B - Mister B and PR Shifts(思维)

    原题链接:http://codeforces.com/problemset/problem/819/B 题意:把一个数列整体往右移k位(大于n位置的数移动到数列前端,循环滚动),定义该数列的“偏差值” ...

  9. SPOJ 2798 QTREE3 - Query on a tree again!

    原oj题面 Time limit 2000 ms Memory limit 1572864 kB Code length Limit 50000 B OS Linux Language limit A ...

  10. Laya 使list渲染支持分帧的思路

    Laya 使list渲染支持分帧的思路 @author ixenos 2019-09-06 1.由于Laya的list渲染时没有做分帧处理,只做了延迟帧处理,所以当单页元素较多时,会有大量运算卡帧的情 ...