🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

進入MVC原始碼世界 Route & RouteTable 原始碼解析 (第9天)

Agenda

前言

現在開始進入Asp.net MVC原始碼世界,我們從路由開始切入一步一步進入MVC核心.

我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.

如下面動畫

介紹Route

每個HTTP請求MVC使用路由的目標是ControllerAction,不像ASP.NET Web Form處理物理文件(.aspx文件),要執行ControllerAction名稱包含在HTTP請求中,ASP.NET MVC需要通過解析HTTP請求得到正確的ControllerAction的名稱。

使用Route處理物理文件有以下幾個優勢:

  • 靈活性:請求URL是對物理文件路徑,意味著如果物理文件的路徑發生了改變(比如改變了文件的目錄結構或者文件名),原來該文件連結將變得無效。
  • 可讀性:在很多情況下,URL不僅僅需要能夠訪問正確的網絡資源,也需要具有很好的可讀性,最好的URL應該讓我們一眼就能看出針對它訪問的目標資源是什麼。請求地址與物理文件緊密綁定讓我們完全失去了定義高可讀性URL的機會。
  • SEO優化:對於網站開發來說,為了迎合搜索引擎檢索的規則,我們需要對URL進行有效的設計使之能易於被主流的引擎檢索收錄。如果URL完全與物理地址關聯,這失去了SEO優化的能力。
  • 安全性:如接指向文件相對路徑無疑跟大家說你伺服器資料夾的結構,如果被有心人士(黑客)知道就可旁敲側擊攻擊您的伺服器.

RouteTable.Routes

在Global.cs檔案中,有一個RouteTable.RoutesRouteCollection類型的集合物件

我們通過RouteTable靜態屬性Routes得到一個全域的路由表,路由註冊的核心價值在此集合上添加路由設定。

1
RouteConfig.RegisterRoutes(RouteTable.Routes);

RouteCollection他是繼承Collection<RouteBase>的集合物件,可以對此集合添加一個繼承RouteBase物件.

在Mvc一般是透過MapRoute擴展方法來添加路由

1
2
3
4
5
6
7
8
9
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

MapRoute擴展方法

看一下MapRoute原始碼,這個方式是基於RouteCollection集合物件做的擴展方法,可看到最重要的部分是新增一個Route物件並加入集合中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
// 判斷...
Route route = new Route(url, new MvcRouteHandler())
{
Defaults = CreateRouteValueDictionaryUncached(defaults),
Constraints = CreateRouteValueDictionaryUncached(constraints),
DataTokens = new RouteValueDictionary()
};

ConstraintValidation.Validate(route);

if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens[RouteDataTokenKeys.Namespaces] = namespaces;
}
//加入註冊路由器
routes.Add(name, route);

return route;
}

Route物件

Route類別是繼承於RouteBase(這也就是為什麼可以把Route物件加入RouteCollection集合中)

下面我刪減一些此次不會介紹到的程式碼.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class Route : RouteBase
{
private const string HttpMethodParameterName = "httpMethod";
private string _url;
private ParsedRoute _parsedRoute;
/// <summary>
/// 使用指定的 URL 模式、預設參數值、條件約束、自訂值和處理常式類別,初始化 <see cref="T:System.Web.Routing.Route" /> 類別的新執行個體。
/// </summary>
/// <param name="url">路由的 URL 模式。</param>
/// <param name="defaults">URL 未包含所有參數時所要使用的值。</param>
/// <param name="constraints">指定 URL 參數之有效值的規則運算式。</param>
/// <param name="dataTokens">
/// 傳遞給路由處理常式的自訂值,但不會用來判斷路由是否符合特定 URL 模式。
/// 這些值會傳遞至路由處理常式,以用來處理要求。
/// </param>
/// <param name="routeHandler">處理路由要求的物件。</param>
public Route(
string url,
RouteValueDictionary defaults,
RouteValueDictionary constraints,
RouteValueDictionary dataTokens,
IRouteHandler routeHandler)
{
this.Url = url;
this.Defaults = defaults;
this.Constraints = constraints;
this.DataTokens = dataTokens;
this.RouteHandler = routeHandler;
}

/// <summary>取得或設定運算式的字典,這些運算式指定 URL 參數的有效值。</summary>
public RouteValueDictionary Constraints { get; set; }

/// <summary>取得或設定自訂值,這些自訂值會傳遞給路由處理常式,但不會用來判斷路由是否符合 URL 模式。</summary>
public RouteValueDictionary DataTokens { get; set; }

/// <summary>取得或設定 URL 未包含所有參數時所要使用的值。</summary>
public RouteValueDictionary Defaults { get; set; }

/// <summary>取得或設定處理路由要求的物件。</summary>
public IRouteHandler RouteHandler { get; set; }


/// <summary>取得或設定路由的 URL 模式。</summary>
public string Url
{
get
{
return this._url ?? string.Empty;
}
set
{
this._parsedRoute = RouteParser.Parse(value);
this._url = value;
}
}

/// <summary>傳回所要求路由的相關資訊。</summary>
/// <param name="httpContext">封裝 HTTP 要求相關資訊的物件。</param>
/// <returns>包含路由定義值的物件。</returns>
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteValueDictionary values = this._parsedRoute.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo, this.Defaults);
if (values == null)
return (RouteData) null;
RouteData routeData = new RouteData((RouteBase) this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
return (RouteData) null;
foreach (KeyValuePair<string, object> keyValuePair in values)
routeData.Values.Add(keyValuePair.Key, keyValuePair.Value);
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> dataToken in this.DataTokens)
routeData.DataTokens[dataToken.Key] = dataToken.Value;
}
return routeData;
}
}

Route類別中GetRouteData是個重要方法,藉由我們的路由設定去解析當前是否匹配到路由規則,如果有就回傳一個RouteData物件,否則回傳Null

上一篇有介紹UrlRoutingModule這個HttpModule會藉由RouteCollection.GetRouteData(context)動作取得一個RouteData並透過他拿到IHttpHander物件並給值到HttpContext.Handler

在裡面的實做是透過一個foreach去找尋匹配的Route物件,因為ADD路由是有順序性,所以在RegisterRoutes(RouteCollection routes)找尋路由會有第一個MapRoute到最後一個

Url這個屬性的set方法上做一個很有意思的動作,在設定值時除了賦值給_url字段,另外還將 設定template url Parse 取得一個ParsedRoute _parsedRoute物件.

  • ParsedRoute將我們注冊的template url用/分割存起來方便日後判斷執行的ActionContoller.

MapPageRoute 擴展方法

路由除了使用於取得調用ContollerAction資訊外,我們還可以通過MapPageRoute註冊URL樣板和某種文件的配對關係.

範例在:Asp.net MVC Debugger

本次使用幾個參數

  1. 路由名稱
  2. 樣版URL
  3. 指向實體aspx檔案路徑
  4. 此路由是否找尋實體路徑
  5. 樣版URL預設參數
1
2
3
4
5
6
7
8
routes.MapPageRoute(
"PhysicalFile",
"GetFile/{Name}",
"~/PhysicalFile.aspx", true,
new RouteValueDictionary()
{
{ "Name","PhysicalFile"}
});

下圖是我們專案建立一個新的.aspx檔案

裡面內容很簡單只是印出一段文字

1
Hello PhysicalFile.aspx

因為有加入MapPageRoute路由,在瀏覽器網址列輸入http:localhost:[your port]/GetFile,我們就可以將PhysicalFile.aspx檔案內容顯示出來.

在 Route中建立處理客製化HttpHandler

Route建構子中我們可以設定實現IRouteHandler物件,這個物件會有個方法可以返回IHttpHandlerasp.net請求使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class MyHandler : IHttpHandler
{
public bool IsReusable
{
get
{
return true;
}
}

public void ProcessRequest(HttpContext context)
{

context.Response.Write("Hello MyHandler!!");
}
}

public class MyHandlerRouter : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHandler();
}
}

我們可以建立MyHandlerRouterGetHttpHandler返回一個MyHandler物件,之後把MyHandlerRouter當作參數傳入Route物件中

Route加入全域路由集合中

1
routes.Add(new Route("Customer",new MyHandlerRouter()));

在瀏覽器輸入 http://localhost:[your port]/Customer 我們就會執行我們自己客製化的HttpHandler

小結:

路由封裝了Http請求路徑資訊可以讓我們找到相對應的ActionController並呼叫執行外,可以透過MapPageRoute來將請求教給.aspx實體檔案來處理請求.

Route甚至可以讓我們自己客製化處理HttpHandler在 Route中建立處理客製化HttpHandler可謂很有彈性

下篇介紹Route物件建立MvcRouteHandler物件如何取到IHttpHandler.

__此文作者__:Daniel Shih(石頭)
__此文地址__: https://isdaniel.github.io/Ithelp-day9/
__版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!

如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^