好久沒有寫關於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);
四、結論
好了,做個總結吧,總體來說不是很難,只要大家理順了,就好了,這些類的設計都是有因果關系的,理解這種因果關系,再把我前后順序,理解起來就簡單了。
今天就到這里,寫的有點長,大家慢慢看,歡迎討論,我要去趕火車了。