好久没有写关于ASP.NET MVC的东西了,虽然《ASP.NET MVC4框架揭秘》已经完完整整的看完一遍,但是感觉和一锅粥差不多,没什么可写的,因为我自己不理解,也就写不出来。现在开始看《ASP.NET MVC5框架揭秘》,应该说第二遍了,每个代码都调试了,也看了很多的源代码,突然有一种清新的感觉,很多东西都连起来了,原来是这样啊,不不经意发出这样的感叹。既然有了一个好的理解,就整理一下,写出来,也就算巩固学习了。
言归正传吧,很多学习Asp.Net MVC的人把整个MVC请求处理的过程人为地划分成了几个小系统,分法很多了,我比较中意划分方法的是:Url路由,Controller激活,Action执行。一个请求进来,必须要经过路由系统处理,生成必要的数据,比如:Controller的名字,Action的名字,路由系统获得了Controller的名字,才会有后面的Controller的激活系统,激活了Controller,然后执行Action,返回处理结果给客户,整个流程就结束了。但是每个部分里面又包含了很多辅助的小系统来完成相应的工作。Controller的激活和Actionde执行以后再说吧,今天我们就先来说说Url路由。
本文章里面不打算翻译一个个大家都知道的名词,比如:Controller,Action,ModelBinder,ActionInvoker等等众多类型,直接用英文单词,因为翻译成中文有时候很难表示完整的意思。
一、简介
Url路由:在ASP.NET MVC系统里,来自客户端的请求总是指向一个定义在某个Controller类型中的某个Action方法,并且目标Controller和Action的名称由请求的Url决定,既URL驱动的,所以必须采取某种机制根据请求的Url地址把目标的Controller和Action的名称解析出来,我们将这种机制就称为“路由(Routing)”。但是我们要说明的是这个路由系统是独立的,不是专属ASP.NET MVC的。独立的意思是可以在ASP.NET WEB FORMS里使用,可以在ASP.NET MVC里面使用,因为路由系统专门针对MVC的特点扩展了其原有的路由系统的实现。所以我把ASP.NET的路由系统分成两个部分,可能说法不太准确,我这样分是方便我更好的理解,大家可以自行分解,便于理解就好。
第一:ASP.NET路由系统,定义在System.Web.dll程序集中,命名空间是System.Web.Routing,这个可以认为是针对ASP.NET WEB FORMS的,路由设置里面要写映射的物理.aspx文件,具体详情可以自行研究,就不多说了。
protected void Application_Start(object sender, EventArgs e) { var defaults = new RouteValueDictionary{ {"name","*" }, {"id","*" } }; RouteTable.Routes.MapPageRoute("","employees/{name}/{id}","~/Default.aspx",true,defaults); }
第二:针对ASP.NET MVC扩展出来的新的路由系统,定义在System.Web.MVC.dll程序集里面。扩展类是定义在命名空间System.Web.Mvc下的RouteCollectionExtensions类型,路由注册的时候要写Controller和Action了。
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Employees", action = "GetAllEmployees", id = UrlParameter.Optional } ); }
我们知道ASP.NET MVC是通过扩展ASP.NET处理管道实现的,这里面有两个最重要的组件,一个是实现了IHttpModule接口的UrlRoutingModule,此组件用于截获请求,进行路由解析,并重新Remap到请求的处理程序上,这个处理程序就是第二个组件,实现了IHttpHandler的MvcHandler,此组件用于激活Controller和Action方法的执行。可以这样说,路由系统的解析操作就发生在UrlRoutingModule组件里面。我们先看看他的代码,然后我们按着请求的先后顺序一步一步的介绍所涉及到的对象。
1 namespace System.Web.Routing 2 { 4 public class UrlRoutingModule : IHttpModule 5 { 6 private static readonly object _contextKey = new object(); 7 8 private static readonly object _requestDataKey = new object(); 9 10 private RouteCollection _routeCollection; 11 12 public RouteCollection RouteCollection 13 { 14 get 15 { 16 if (this._routeCollection == null) 17 { 18 this._routeCollection = RouteTable.Routes; 19 } 20 return this._routeCollection; 21 } 22 set 23 { 24 this._routeCollection = value; 25 } 26 } 31 32 protected virtual void Init(HttpApplication application) 33 { 34 if (application.Context.Items[UrlRoutingModule._contextKey] != null) 35 { 36 return; 37 } 38 application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey; 39 application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache); 40 } 41 42 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) 43 { 44 HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context); 45 this.PostResolveRequestCache(context); 46 } 47 53 public virtual void PostResolveRequestCache(HttpContextBase context) 54 { 55 RouteData routeData = this.RouteCollection.GetRouteData(context); 56 if (routeData == null) 57 { 58 return; 59 } 60 IRouteHandler routeHandler = routeData.RouteHandler; 61 if (routeHandler == null) 62 { 63 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0])); 64 } 65 if (routeHandler is StopRoutingHandler) 66 { 67 return; 68 } 69 RequestContext requestContext = new RequestContext(context, routeData); 70 context.Request.RequestContext = requestContext; 71 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 72 if (httpHandler == null) 73 { 74 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] 75 { 76 routeHandler.GetType() 77 })); 78 } 79 if (!(httpHandler is UrlAuthFailureHandler)) 80 { 81 context.RemapHandler(httpHandler); 82 return; 83 } 84 if (FormsAuthenticationModule.FormsAuthRequired) 85 { 86 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); 87 return; 88 } 89 throw new HttpException(401, SR.GetString("Assess_Denied_Description3")); 90 }
101 } 102 }
二、路由解析的先后顺序
路由规则注册-----》截获请求-----》路由解析并获得RouteData对象-----》根据RouteData的RouteHandler获得MvcRouteHandler对象-----》根据MvcRouteHandler获得MvcHandler对象-----》请求对象重新路由,HttpContext.Remap(MvcHandler)-----》MvcHandler接管请求处理-----》Controller激活-----》Action方法的执行-----》返回处理结果并结束
上面是路由解析的全过程,我再用白话描述一遍:要想解析路由,必须先注册路由规则的对象吧,也就是Route对象,连注册都没有还解析个什么劲啊,我们一般在Global.asax文件的Application_Start方法里面注册Route对象。注册好了路由规则,启动系统,早已注册好的UrlRoutingModule截获请求,用当前的请求的Url和路由表【RouteTable】里面存储的路由对象【Route】进行比较,其实是Url地址和路由对象【Route】的路由地址模板Url进行匹配,没有匹配就返回空值,如果多个匹配,就选择第一个匹配路由对象【Route】,根据选择的路由对象【Route】生成路由数据【RouteData】。因为路由数据【RouteData】包含RouteHandler属性,RouteHandler属性用于提供最终处理请求的HttpHandler,ASP.NET MVC中RouteHandler的属性值就是MvcRouteHandler,MvcRouteHandler实现了IRouteHandler接口,这个接口有一个方法GetHttpHandler,这个方法就提供了用于处理最终请求的HttpHandler,这个HttpHandler就是MvcHandler,好了,该获取的对象都准备好了,那就把请求交给MvcHandler吧,交接是通过HttpContext.Remap方实现的,好了,大概就是这么一个过程。
我先简要的把路由解析所涉及到的类型说一下,我们是面向对象编程的,所以很多东西已经对象化了,说的不错。哈哈老王卖瓜了:
1、Route:路由规则抽象获得RouteBase类型,此类型是抽象类,他有唯一的一个子类就是Route对象,路由规则对象肯定要有路由模板的地址Url,要有路由的默认值了,路由的约束值了等等一些东西。也可以这样理解,我们注册的每一个规则就是一个Route对象,每一个Route对象实例就是代表一种路由的规则。
2、UrlRoutingModule:我们有了Route路由规则对象,也注册好了,系统启动,我们要把请求截获,不截获请求,就没办法处理了,所以我们就是扩展了ASP.Net处理管道,实现了IHttpModule接口,定义了UrlRoutingModule类型,它用于截获请求,进行路由解析,我上面也提到过该类,并贴出了代码,下面会详细说的,非常核心的类,如果对ASP.NET处理管道不熟悉的可以去网上查找一些资料,很容易找的。
3、RouteTable:ASP.NET MVC有一个代表全局的路由表,所有注册Route对象都存在RouteTable.Routes表示的集合里面,路由解析的时候就是和RouteTable.Routes表示的路由表里面的每一个Route对象进行匹配,如果RouteTable里面的所有Route对象所代表的路由规则和当前的Url都不匹配就返回Null值,如果有多个匹配就选择第一个匹配的Route对象,并根据Route对象生成RouteData对象。
4、RouteData:当请求的Url和RouteTable路由表中表示路由规则的Route相匹配的时候,会根据匹配的Route对象生成RouteData,它里面包含了根据Url解析所得到东西,如:controller的名字,Action的名字等信息。
5、MvcRouteHandler:MvcRouteHandler是MvcHandler对象的提供对象,RouteData的RouteHandler属性的值针对MVC来说就是MvcRouteHandler,如果是ASP.NET的路由系统,那就是PageRouteHandler对象了。
6、MvcHandler:既然我们获得了MvcHandler,通过HttpContext的Remap方法重新路由,把请求交给MvcHandler来处理,后面就是Controller激活和Action方法的解析了。
好了,简单的说了一下每个对象的用途,大家也许有了一个大概的印象了吧,我们下面就来详细的说说每一个对象的实际情况。
三、路由对象的详解
我们使用的是面向对象的语言,所操作的一切都是对象,路由规则经过抽象就是RouteBase对象,有了RouteBase对象,我们才可以注册路由对象,才有针对RouteBase的路由解析。接下来就让我们开始说我们的第一个对象吧,Route路由对象,刚才不是说要说RouteBase,咱们现在又要说Route对象了,怎么变了,其实没变,两个对象是一回事。RouteBase其实是 一个抽象类,我们所指的或者所说的Route路由对象,其实都是从RouteBase对象继承下来的。
1、RouteBase和Route
我们看看RouteBase的源代码吧,不看源代码,很多东西不能搞清楚的。
1 public abstract class RouteBase 2 { 3 private bool _routeExistingFiles = true; 4 5 public bool RouteExistingFiles 6 { 7 get 8 { 9 return this._routeExistingFiles; 10 } 11 set 12 { 13 this._routeExistingFiles = value; 14 } 15 } 16 17 public abstract RouteData GetRouteData(HttpContextBase httpContext); 18 19 public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); 20 }
RouteBase有两个抽象方法,第一个是返回的是RouteData类型的GetRouteData方法,RouteData说白了就是路由数据,在白点就是根据路由规则解析获得的数据,所以方法名是GetRouteData,该方法的参数是HttpContextBase对象,这个对象表示的当前请求。或者说这个方法就是根据当前的Url请求和路由规则对象进行比较,如果匹配就根据路由规则对象Route生成对象的RouteData路由数据对象。另外一个抽象方法就是,返回类型为VirtualPathData对象的GetVirtualPath方法,此方法的作用就是根据提供的数据和注册的路由规则生成相应的虚拟路径。RouteData稍后会将,让我们看看VirtualPathData是一个什么样的东西,源代码如下:
1 public class VirtualPathData 2 { 3 private string _virtualPath; 4 5 private RouteValueDictionary _dataTokens = new RouteValueDictionary(); 6 7 public RouteValueDictionary DataTokens 8 { 9 get 10 { 11 return this._dataTokens; 12 } 13 } 14 15 public RouteBase Route 16 { 17 get; 18 set; 19 } 20 21 public string VirtualPath 22 { 23 get 24 { 25 return this._virtualPath ?? string.Empty; 26 } 27 set 28 { 29 this._virtualPath = value; 30 } 31 } 32 33 public VirtualPathData(RouteBase route, string virtualPath) 34 { 35 this.Route = route; 36 this.VirtualPath = virtualPath; 37 } 38 }
返回字符串类型VirtualPath属性就是生成虚拟路径,返回类型RouteBase的Route属性表示的匹配规则那个RouteBase对象。现在我想访问真实存在的一个物理文件怎么办呢?RouteBase有一个RouteExistingFiles属性,这个属性表示是否路由物理存在的文件,默认值是True,意味着我们想访问某个物理文件在不改变设置的情况下是不行的,因为已经按着路由规则发生了路由了。
我们在来看看Route对象吧,源码如下:
1 public class Route : RouteBase 2 { 3 private const string HttpMethodParameterName = "httpMethod"; 4 5 private string _url; 6 7 private ParsedRoute _parsedRoute; 8 9 public RouteValueDictionary Constraints 10 { 11 get; 12 set; 13 } 14 15 public RouteValueDictionary DataTokens 16 { 17 get; 18 set; 19 } 20 21 public RouteValueDictionary Defaults 22 { 23 get; 24 set; 25 } 26 27 public IRouteHandler RouteHandler 28 { 29 get; 30 set; 31 } 32 33 public string Url 34 { 35 get 36 { 37 return this._url ?? string.Empty; 38 } 39 set 40 { 41 this._parsedRoute = RouteParser.Parse(value); 42 this._url = value; 43 } 44 } 45 46 public Route(string url, IRouteHandler routeHandler) 47 { 48 this.Url = url; 49 this.RouteHandler = routeHandler; 50 } 51 52 public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) 53 { 54 this.Url = url; 55 this.Defaults = defaults; 56 this.RouteHandler = routeHandler; 57 } 58 59 public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) 60 { 61 this.Url = url; 62 this.Defaults = defaults; 63 this.Constraints = constraints; 64 this.RouteHandler = routeHandler; 65 } 66 67 public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) 68 { 69 this.Url = url; 70 this.Defaults = defaults; 71 this.Constraints = constraints; 72 this.DataTokens = dataTokens; 73 this.RouteHandler = routeHandler; 74 } 75 76 public override RouteData GetRouteData(HttpContextBase httpContext) 77 { 78 string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; 79 RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults); 80 if (routeValueDictionary == null) 81 { 82 return null; 83 } 84 RouteData routeData = new RouteData(this, this.RouteHandler); 85 if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest)) 86 { 87 return null; 88 } 89 foreach (KeyValuePair<string, object> current in routeValueDictionary) 90 { 91 routeData.Values.Add(current.Key, current.Value); 92 } 93 if (this.DataTokens != null) 94 { 95 foreach (KeyValuePair<string, object> current2 in this.DataTokens) 96 { 97 routeData.DataTokens[current2.Key] = current2.Value; 98 } 99 } 100 return routeData; 101 } 102 103 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 104 { 105 BoundUrl boundUrl = this._parsedRoute.Bind(requestContext.RouteData.Values, values, this.Defaults, this.Constraints); 106 if (boundUrl == null) 107 { 108 return null; 109 } 110 if (!this.ProcessConstraints(requestContext.HttpContext, boundUrl.Values, RouteDirection.UrlGeneration)) 111 { 112 return null; 113 } 114 VirtualPathData virtualPathData = new VirtualPathData(this, boundUrl.Url); 115 if (this.DataTokens != null) 116 { 117 foreach (KeyValuePair<string, object> current in this.DataTokens) 118 { 119 virtualPathData.DataTokens[current.Key] = current.Value; 120 } 121 } 122 return virtualPathData; 123 } 124 125 protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 126 { 127 IRouteConstraint routeConstraint = constraint as IRouteConstraint; 128 if (routeConstraint != null) 129 { 130 return routeConstraint.Match(httpContext, this, parameterName, values, routeDirection); 131 } 132 string text = constraint as string; 133 if (text == null) 134 { 135 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[] 136 { 137 parameterName, 138 this.Url 139 })); 140 } 141 object value; 142 values.TryGetValue(parameterName, out value); 143 string arg_7C_0 = Convert.ToString(value, CultureInfo.InvariantCulture); 144 string pattern = "^(" + text + ")$"; 145 return Regex.IsMatch(arg_7C_0, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); 146 } 147 148 private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection) 149 { 150 if (this.Constraints != null) 151 { 152 foreach (KeyValuePair<string, object> current in this.Constraints) 153 { 154 if (!this.ProcessConstraint(httpContext, current.Value, current.Key, values, routeDirection)) 155 { 156 return false; 157 } 158 } 159 return true; 160 } 161 return true; 162 } 163 }
其实代码不复杂,大家也应该看的懂,Route对象直接继承RouteBase对象的,而且是唯一一个这样的对象,既然是路由规则对象,肯定包括,地址模板,默认值,约束条件和一些附加的数据,Constraints保存的就是约束条件,Defaults保存的就是默认值,Url属性就是地址模板了。他一定要实现GetRouteData方法和GetVirtualPath方法
2、RouteData
我们有了路由规则了,也就是Route对象,我们也注册了,接下来就是路由解析,就是和Route对象的的Url进行比较,如果匹配就生成了RouteData对象,也就是Route对象GetRouteData方法返回结果了。大家一定要记住,RouteData是基于Route对象生成的,我们看看源码吧:
1 public class RouteData 2 { 3 private IRouteHandler _routeHandler; 4 5 private RouteValueDictionary _values = new RouteValueDictionary(); 6 7 private RouteValueDictionary _dataTokens = new RouteValueDictionary(); 8 9 public RouteValueDictionary DataTokens 10 { 11 get 12 { 13 return this._dataTokens; 14 } 15 } 16 17 public RouteBase Route 18 { 19 get; 20 set; 21 } 22 23 public IRouteHandler RouteHandler 24 { 25 get 26 { 27 return this._routeHandler; 28 } 29 set 30 { 31 this._routeHandler = value; 32 } 33 } 34 35 public RouteValueDictionary Values 36 { 37 get 38 { 39 return this._values; 40 } 41 } 42 43 public RouteData() 44 { 45 } 46 47 public RouteData(RouteBase route, IRouteHandler routeHandler) 48 { 49 this.Route = route; 50 this.RouteHandler = routeHandler; 51 } 52 53 public string GetRequiredString(string valueName) 54 { 55 object obj; 56 if (this.Values.TryGetValue(valueName, out obj)) 57 { 58 string text = obj as string; 59 if (!string.IsNullOrEmpty(text)) 60 { 61 return text; 62 } 63 } 64 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("RouteData_RequiredValue"), new object[] 65 { 66 valueName 67 })); 68 } 69 }
RouteData对象是规则匹配所要生成的东西,根据Url解析获得数据存在Values属性里面,DataTokens属性表示一些附加的数据,并且这个数据来源于Route对象的DataTokens属性,RouteData对象RouteHandler属性的值也是来源于Route对象的RouteHandler属性,这个RouteHandler在ASP.NET路由系统就是PageRouteHandler,在ASP.NET MVC中就是MvcRouteHandler,用于提供最终处理请求的HttpHandler。RouteData对象还有一个Route属性,此属性表示在路由解析的时候匹配的那个Route路由规则对象。
之所以说RouteData是基于Route对象产生了,因为RouteData对象里面的很多值来源于Routed对象,Route对象是基础。
3、RouteTable
当我们了有了路由规则Route对象的时候,这些路由对象放在什么地方呢?答案就是放在了路由表RouteTable对象中,我们先看看他的源码吧:
1 public class RouteTable 2 { 3 private static RouteCollection _instance = new RouteCollection(); 4 5 public static RouteCollection Routes 6 { 7 get 8 { 9 return RouteTable._instance; 10 } 11 } 12 }
RouteTable有一个静态属性是Routes,此属性的类型是RouteCollection,字面意思Route的Collection,就是路由对象的集合,类型如下:
1 public class RouteCollection : Collection<RouteBase> 2 { 3 private class ReadLockDisposable : IDisposable 4 { 5 private ReaderWriterLockSlim _rwLock; 6 7 public ReadLockDisposable(ReaderWriterLockSlim rwLock) 8 { 9 this._rwLock = rwLock; 10 } 11 12 void IDisposable.Dispose() 13 { 14 this._rwLock.ExitReadLock(); 15 } 16 } 17 18 private class WriteLockDisposable : IDisposable 19 { 20 private ReaderWriterLockSlim _rwLock; 21 22 public WriteLockDisposable(ReaderWriterLockSlim rwLock) 23 { 24 this._rwLock = rwLock; 25 } 26 27 void IDisposable.Dispose() 28 { 29 this._rwLock.ExitWriteLock(); 30 } 31 } 32 33 private sealed class IgnoreRouteInternal : Route 34 { 35 public IgnoreRouteInternal(string url) : base(url, new StopRoutingHandler()) 36 { 37 } 38 39 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues) 40 { 41 return null; 42 } 43 } 44 45 private Dictionary<string, RouteBase> _namedMap = new Dictionary<string, RouteBase>(StringComparer.OrdinalIgnoreCase); 46 47 private VirtualPathProvider _vpp; 48 49 private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); 50 51 public bool AppendTrailingSlash 52 { 53 get; 54 set; 55 } 56 57 public bool LowercaseUrls 58 { 59 get; 60 set; 61 } 62 63 public bool RouteExistingFiles 64 { 65 get; 66 set; 67 } 68 69 private VirtualPathProvider VPP 70 { 71 get 72 { 73 if (this._vpp == null) 74 { 75 return HostingEnvironment.VirtualPathProvider; 76 } 77 return this._vpp; 78 } 79 set 80 { 81 this._vpp = value; 82 } 83 } 84 85 public RouteBase this[string name] 86 { 87 get 88 { 89 if (string.IsNullOrEmpty(name)) 90 { 91 return null; 92 } 93 RouteBase result; 94 if (this._namedMap.TryGetValue(name, out result)) 95 { 96 return result; 97 } 98 return null; 99 } 100 } 101 102 public RouteCollection() 103 { 104 } 105 106 public RouteCollection(VirtualPathProvider virtualPathProvider) 107 { 108 this.VPP = virtualPathProvider; 109 } 110 111 public void Add(string name, RouteBase item) 112 { 113 if (item == null) 114 { 115 throw new ArgumentNullException("item"); 116 } 117 if (!string.IsNullOrEmpty(name) && this._namedMap.ContainsKey(name)) 118 { 119 throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("RouteCollection_DuplicateName"), new object[] 120 { 121 name 122 }), "name"); 123 } 124 base.Add(item); 125 if (!string.IsNullOrEmpty(name)) 126 { 127 this._namedMap[name] = item; 128 } 129 } 130 131 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile) 132 { 133 return this.MapPageRoute(routeName, routeUrl, physicalFile, true, null, null, null); 134 } 135 136 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess) 137 { 138 return this.MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, null, null, null); 139 } 140 141 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults) 142 { 143 return this.MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, null, null); 144 } 145 146 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints) 147 { 148 return this.MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, constraints, null); 149 } 150 151 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) 152 { 153 if (routeUrl == null) 154 { 155 throw new ArgumentNullException("routeUrl"); 156 } 157 Route route = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess)); 158 this.Add(routeName, route); 159 return route; 160 } 161 162 protected override void ClearItems() 163 { 164 this._namedMap.Clear(); 165 base.ClearItems(); 166 } 167 168 public IDisposable GetReadLock() 169 { 170 this._rwLock.EnterReadLock(); 171 return new RouteCollection.ReadLockDisposable(this._rwLock); 172 } 173 174 private RequestContext GetRequestContext(RequestContext requestContext) 175 { 176 if (requestContext != null) 177 { 178 return requestContext; 179 } 180 HttpContext expr_0A = HttpContext.Current; 181 if (expr_0A == null) 182 { 183 throw new InvalidOperationException(SR.GetString("RouteCollection_RequiresContext")); 184 } 185 return new RequestContext(new HttpContextWrapper(expr_0A), new RouteData()); 186 } 187 188 private bool IsRouteToExistingFile(HttpContextBase httpContext) 189 { 190 string appRelativeCurrentExecutionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath; 191 return appRelativeCurrentExecutionFilePath != "~/" && this.VPP != null && (this.VPP.FileExists(appRelativeCurrentExecutionFilePath) || this.VPP.DirectoryExists(appRelativeCurrentExecutionFilePath)); 192 } 193 194 public RouteData GetRouteData(HttpContextBase httpContext) 195 { 196 if (httpContext == null) 197 { 198 throw new ArgumentNullException("httpContext"); 199 } 200 if (httpContext.Request == null) 201 { 202 throw new ArgumentException(SR.GetString("RouteTable_ContextMissingRequest"), "httpContext"); 203 } 204 if (base.Count == 0) 205 { 206 return null; 207 } 208 bool flag = false; 209 bool flag2 = false; 210 if (!this.RouteExistingFiles) 211 { 212 flag = this.IsRouteToExistingFile(httpContext); 213 flag2 = true; 214 if (flag) 215 { 216 return null; 217 } 218 } 219 using (this.GetReadLock()) 220 { 221 foreach (RouteBase current in this) 222 { 223 RouteData routeData = current.GetRouteData(httpContext); 224 if (routeData != null) 225 { 226 RouteData result; 227 if (!current.RouteExistingFiles) 228 { 229 if (!flag2) 230 { 231 flag = this.IsRouteToExistingFile(httpContext); 232 } 233 if (flag) 234 { 235 result = null; 236 return result; 237 } 238 } 239 result = routeData; 240 return result; 241 } 242 } 243 } 244 return null; 245 } 246 247 private string NormalizeVirtualPath(RequestContext requestContext, string virtualPath) 248 { 249 string text = Util.GetUrlWithApplicationPath(requestContext.HttpContext, virtualPath); 250 if (this.LowercaseUrls || this.AppendTrailingSlash) 251 { 252 int num = text.IndexOfAny(new char[] 253 { 254 '?', 255 '#' 256 }); 257 string text2; 258 string str; 259 if (num >= 0) 260 { 261 text2 = text.Substring(0, num); 262 str = text.Substring(num); 263 } 264 else 265 { 266 text2 = text; 267 str = ""; 268 } 269 if (this.LowercaseUrls) 270 { 271 text2 = text2.ToLowerInvariant(); 272 } 273 if (this.AppendTrailingSlash && !text2.EndsWith("/")) 274 { 275 text2 += "/"; 276 } 277 text = text2 + str; 278 } 279 return text; 280 } 281 282 public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 283 { 284 requestContext = this.GetRequestContext(requestContext); 285 using (this.GetReadLock()) 286 { 287 using (IEnumerator<RouteBase> enumerator = base.GetEnumerator()) 288 { 289 while (enumerator.MoveNext()) 290 { 291 VirtualPathData virtualPath = enumerator.Current.GetVirtualPath(requestContext, values); 292 if (virtualPath != null) 293 { 294 virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath); 295 return virtualPath; 296 } 297 } 298 } 299 } 300 return null; 301 } 302 303 public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values) 304 { 305 requestContext = this.GetRequestContext(requestContext); 306 if (string.IsNullOrEmpty(name)) 307 { 308 return this.GetVirtualPath(requestContext, values); 309 } 310 RouteBase routeBase; 311 bool flag; 312 using (this.GetReadLock()) 313 { 314 flag = this._namedMap.TryGetValue(name, out routeBase); 315 } 316 if (!flag) 317 { 318 throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("RouteCollection_NameNotFound"), new object[] 319 { 320 name 321 }), "name"); 322 } 323 VirtualPathData virtualPath = routeBase.GetVirtualPath(requestContext, values); 324 if (virtualPath != null) 325 { 326 virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath); 327 return virtualPath; 328 } 329 return null; 330 } 331 332 public IDisposable GetWriteLock() 333 { 334 this._rwLock.EnterWriteLock(); 335 return new RouteCollection.WriteLockDisposable(this._rwLock); 336 } 337 338 public void Ignore(string url) 339 { 340 this.Ignore(url, null); 341 } 342 343 public void Ignore(string url, object constraints) 344 { 345 if (url == null) 346 { 347 throw new ArgumentNullException("url"); 348 } 349 RouteCollection.IgnoreRouteInternal item = new RouteCollection.IgnoreRouteInternal(url) 350 { 351 Constraints = new RouteValueDictionary(constraints) 352 }; 353 base.Add(item); 354 } 355 356 protected override void InsertItem(int index, RouteBase item) 357 { 358 if (item == null) 359 { 360 throw new ArgumentNullException("item"); 361 } 362 if (base.Contains(item)) 363 { 364 throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.GetString("RouteCollection_DuplicateEntry"), new object[0]), "item"); 365 } 366 base.InsertItem(index, item); 367 } 368 369 protected override void RemoveItem(int index) 370 { 371 this.RemoveRouteName(index); 372 base.RemoveItem(index); 373 } 374 375 private void RemoveRouteName(int index) 376 { 377 RouteBase routeBase = base[index]; 378 foreach (KeyValuePair<string, RouteBase> current in this._namedMap) 379 { 380 if (current.Value == routeBase) 381 { 382 this._namedMap.Remove(current.Key); 383 break; 384 } 385 } 386 } 387 388 protected override void SetItem(int index, RouteBase item) 389 { 390 if (item == null) 391 { 392 throw new ArgumentNullException("item"); 393 } 394 if (base.Contains(item)) 395 { 396 throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.GetString("RouteCollection_DuplicateEntry"), new object[0]), "item"); 397 } 398 this.RemoveRouteName(index); 399 base.SetItem(index, item); 400 } 401 }
RouteCollection类型直接继承Collection<RouteBase>,这个关系很明显,他就是用于存放Route路由规则对象的,用于注册Route路由对象的方法就是MapPageRoute方法,基于篇幅,其他方法就不细说了,大家可以细看。
4、RouteHandler
到此,我们有了路由规则对象Route,也通过RouteTable的Routes属性注册好了,系统启动了,我们要截获请求,截获请求的类文件就是UrlRoutingModule,在上面我已经贴出该类的全部源码了,这里就不写了,截获请求后,开始和RouteTable里面的每一个Route对象进行比较,如果匹配就获得RouteData对象了,有了RouteData对象,其实我们是为了获得RouteHandler的值,有了他的值我们才可以继续,我说了这么多就是这个方法所实现的:
public virtual void PostResolveRequestCache(HttpContextBase context) { RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData == null) { return; } 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) { return; } RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext; IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() })); } if (!(httpHandler is UrlAuthFailureHandler)) { context.RemapHandler(httpHandler); return; } if (FormsAuthenticationModule.FormsAuthRequired) { UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); return; } throw new HttpException(401, SR.GetString("Assess_Denied_Description3")); }
有了RouteData对象,在和HttpContext对象一起封装为RequestContext对象,
RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext;
然后我们根据RouteData对象的RouteHandler属性获取HttpHandler,我们调用GetHttpHandler方法的时候我们需要RequestContext类型作为参数,代码如下:
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
RouoteHandler属性表示的类型必须实现了如下接口:
public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }
最后,我们重新路由HttpHandler,在ASP.NET MVC终究是MvcHandler,他开始结果整个请求,进行Controlller激活和Action方法的执行。
context.RemapHandler(httpHandler);
四、结论
好了,做个总结吧,总体来说不是很难,只要大家理顺了,就好了,这些类的设计都是有因果关系的,理解这种因果关系,再把我前后顺序,理解起来就简单了。
今天就到这里,写的有点长,大家慢慢看,欢迎讨论,我要去赶火车了。