白話ASP.NET MVC之一:Url 路由


       好久沒有寫關於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);

 

四、結論

    好了,做個總結吧,總體來說不是很難,只要大家理順了,就好了,這些類的設計都是有因果關系的,理解這種因果關系,再把我前后順序,理解起來就簡單了。

   今天就到這里,寫的有點長,大家慢慢看,歡迎討論,我要去趕火車了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM