現在開始進入Asp.net MVC原始碼世界,我們從路由開始切入一步一步進入MVC核心.
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
如下面動畫
此篇同步發布在筆者Blog [Day09] 進入MVC原始碼世界 Route & RouteTable 原始碼解析
每個HTTP請求MVC
使用路由的目標是Controller
和Action
,不像ASP.NET Web Form
處理物理文件(.aspx
文件),要執行Controller
和Action
名稱包含在HTTP請求中,ASP.NET MVC
需要通過解析HTTP請求得到正確的Controller
和Action
的名稱。
使用Route
比處理物理文件有以下幾個優勢:
URL
是對物理文件路徑,意味著如果物理文件的路徑發生了改變(比如改變了文件的目錄結構或者文件名),原來該文件連結將變得無效。在Global.cs檔案中,有一個RouteTable.Routes
是RouteCollection
類型的集合物件
我們通過RouteTable
靜態屬性Routes
得到一個全域的路由表,路由註冊的核心價值在此集合上添加路由設定。
RouteConfig.RegisterRoutes(RouteTable.Routes);
RouteCollection
他是繼承Collection<RouteBase>
的集合物件,可以對此集合添加一個繼承RouteBase
物件.
在Mvc一般是透過MapRoute
擴展方法來添加路由
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
原始碼,這個方式是基於RouteCollection
集合物件做的擴展方法,可看到最重要的部分是新增一個Route
物件並加入集合中.
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
類別是繼承於RouteBase
(這也就是為什麼可以把Route
物件加入RouteCollection
集合中)
下面我刪減一些此次不會介紹到的程式碼.
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用/
分割存起來方便日後判斷執行的Action
和Contoller
.路由除了使用於取得調用Contoller
和Action
資訊外,我們還可以通過MapPageRoute
註冊URL樣板和某種文件的配對關係.
本次使用幾個參數
aspx
檔案路徑routes.MapPageRoute(
"PhysicalFile",
"GetFile/{Name}",
"~/PhysicalFile.aspx", true,
new RouteValueDictionary()
{
{ "Name","PhysicalFile"}
});
下圖是我們專案建立一個新的.aspx
檔案
裡面內容很簡單只是印出一段文字
Hello PhysicalFile.aspx
因為有加入MapPageRoute
路由,在瀏覽器網址列輸入http:localhost:[your port]/GetFile
,我們就可以將PhysicalFile.aspx
檔案內容顯示出來.
在Route
建構子中我們可以設定實現IRouteHandler
物件,這個物件會有個方法可以返回IHttpHandler
給asp.net
請求使用.
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();
}
}
我們可以建立MyHandlerRouter
在GetHttpHandler
返回一個MyHandler
物件,之後把MyHandlerRouter
當作參數傳入Route
物件中
把Route
加入全域路由集合中
routes.Add(new Route("Customer",new MyHandlerRouter()));
在瀏覽器輸入
http://localhost:[your port]/Customer
我們就會執行我們自己客製化的HttpHandler
路由封裝了Http請求路徑資訊可以讓我們找到相對應的Action
和Controller
並呼叫執行外,可以透過MapPageRoute
來將請求教給.aspx
實體檔案來處理請求.
Route
甚至可以讓我們自己客製化處理HttpHandler
如上面說的在 Route中建立處理客製化HttpHandler)可謂很有彈性
下篇介紹Route
物件建立MvcRouteHandler
物件如何取到IHttpHandler
.