在一個ASP.NET MVC應用來說,針對HTTP請求的處理和相應定義Controller類型的某個Action方法中,每個HTTP請求的目標對象不再像ASP .NET Web Form應用一樣是一個物理文件,而是某個Controller的某個Action。目標Controller和Action的名稱包含在HTTP請求中,而ASP.NET MVC的首要任務就是通過當前HTTP請求的解析得到正確的Controller和Action的名稱。這個過程是通過ASP.NET MVC的URL路由機制來實現的。
一、RouteData
ASP.NET定義了一個全局的路由表,路由表中的每個路由對象對應著一個將Controller和Action名稱作為站位符的URL模板。對於每一個抵達的HTTP請求,ASP.NET MVC會遍歷路由表找到一個URL模板的模式與請求地址相匹配的路有對象,並最終解析出以Controller和Action名稱為核心的路由數據。在我們自定義的ASP.NET MVC框架中,路由數據通過具有如下定義的RouteData類型表示。
1: public class RouteData
2: {
3: public IDictionary<string, object> Values { get; private set; }
4: public IDictionary<string, object> DataTokens { get; private set; }
5: public IRouteHandler RouteHandler { get; set; }
6: public RouteBase Route { get; set; }
7:
8: public RouteData()
9: {
10: this.Values = new Dictionary<string, object>();
11: this.DataTokens = new Dictionary<string, object>();
12: this.DataTokens.Add("namespaces", new List<string>());
13: }
14: public string Controller
15: {
16: get
17: {
18: object controllerName = string.Empty;
19: this.Values.TryGetValue("controller", out controllerName);
20: return controllerName.ToString();
21: }
22: }
23: public string ActionName
24: {
25: get
26: {
27: object actionName = string.Empty;
28: this.Values.TryGetValue("action", out actionName);
29: return actionName.ToString();
30: }
31: }
32: public IEnumerable<string> Namespaces
33: {
34: get
35: {
36: return (IEnumerable<string>)this.DataTokens["namespaces"];
37: }
38: }
39: }
從上面的代碼片斷所示,RouteData定義了兩個字典類型的屬性Values和DataTokens,前者代表直接從請求地址解析出來的變量,後者代表其他類型的變量。表示Controller和Action名稱的同名屬性直接從Values字典中提取,對應的Key分別為controller和action。屬性Namespaces表示輔助Controller類型的解析而設置的命名空間列表,該屬性值從DataTokens字典中提取,對應的Key為namespaces。
我們之前已經提到過ASP.NET MVC本質上是兩個自定義的ASP.NET組件來實現的,一個是自定義的HttpModule,另一個是自定義的HttpHandler,而後者從RouteData的RouteHandler屬性獲得。RouteData的RouteHandler屬性類型為IRouteHandler接口,如下面的代碼片斷所示,該接口具有一個唯一的GetHttpHandler用於返回真正用於處理HTTP請求的HttpHandler對象。
1: public interface IRouteHandler
2: {
3: IHttpHandler GetHttpHandler(RequestContext requestContext);
4: }
IRouteHandler接口的GetHttpHandler方法接受一個類型為RequestContext的參數。顧名思義,RequestContext表示當前(HTTP)請求的上下文,其核心就是對當前HttpContext和RouteData的封裝,這可以通過如下的代碼片斷看出來。
1: public class RequestContext
2: {
3: public virtual HttpContextBase HttpContext { get; set; }
4: public virtual RouteData RouteData { get; set; }
5: }
二、Route和RouteTable
RouteData具有一個類型為RouteBase的Route屬性,表示當前路由表中與當前請求匹配的路由對象。換句話說,當前的RouteData就是通過該路由對象針對當前HTTP請求進行解析獲得的。RouteBase是一個抽象類,如下面的代碼片斷所示,它僅僅包含一個GetRouteData方法,該方法通過對以HttpContextBase對象表示的當前HTTP上下文進行解析從而獲取一個RouteData對象。
1: public abstract class RouteBase
2: {
3: public abstract RouteData GetRouteData(HttpContextBase httpContext);
4: }
ASP.NET MVC提供的基於URL模板的路由機制是通過具有如下定義的Route類型實現的。Route是RouteBase的子類,字符串類型的Url屬性代表定義的URL模板 。在實現的GetRouteData方法中,通過HttpContextBase獲取相對請求地址,如果該地址與定義在模板中的URL模式相匹配則創建一個RouteData返回;否則返回Null。對於返回的RouteData對象,其Values屬性表示的字典包含直接通過地址解析出來的變量,而對於DataTokens字典和RouteHandler屬性,則直接取自Route對象的同名屬性。
1: public class Route : RouteBase
2: {
3: public IRouteHandler RouteHandler { get; set; }
4: public Route()
5: {
6: this.DataTokens = new Dictionary<string, object>();
7: this.RouteHandler = new MvcRouteHandler();
8: }
9: public override RouteData GetRouteData(HttpContextBase httpContext)
10: {
11: IDictionary<string, object> variables;
12: if (this.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2), out variables))
13: {
14: RouteData routeData = new RouteData();
15: foreach (var item in variables)
16: {
17: routeData.Values.Add(item.Key, item.Value);
18: }
19: foreach (var item in DataTokens)
20: {
21: routeData.DataTokens.Add(item.Key, item.Value);
22: }
23: routeData.RouteHandler = this.RouteHandler;
24: return routeData;
25: }
26: return null;
27: }
28: public string Url { get; set; }
29: public IDictionary<string, object> DataTokens { get; set; }
30: protected bool Match(string requestUrl, out IDictionary<string,object> variables)
31: {
32: variables = new Dictionary<string,object>();
33: string[] strArray1 = requestUrl.Split('/');
34: string[] strArray2 = this.Url.Split('/');
35: if (strArray1.Length != strArray2.Length)
36: {
37: return false;
38: }
39:
40: for (int i = 0; i < strArray2.Length; i++)
41: {
42: if(strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}"))
43: {
44: variables.Add(strArray2[i].Trim("{}".ToCharArray()),strArray1[i]);
45: }
46: }
47: return true;
48: }
49: }