ASP.NET MVC路由系統的核心物件介紹
眾所周知,ASP.NET MVC有一套自己的路由系統。這套路由系統是在原來的ASP.NET 路由系統上擴充套件過來的。今天這篇文章就來聊聊MVC路由系統中非常關鍵的一些物件。
ASP.NET MVC路由系統主要由以下幾個核心物件:
1.RouteCollection(RouteCollextionExtentions)
2.RouteTable
3.RouteData
4.Route:RouteBase
5.URLRouteMoudle
下面我們就來一一介紹這些物件
RouteCollection
public static class RouteCollectionExtensions { public static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, RouteValueDictionary values); public static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary values); public static void IgnoreRoute(this RouteCollection routes, string url); public static void IgnoreRoute(this RouteCollection routes, string url, object constraints); public static Route MapRoute(this RouteCollection routes, string name, string url); public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults); public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces); public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints); public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces); public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces); }
從上面的定義可以看得出來,RouteCollectionExtentions類中的成員都是擴充套件成員。MapRoute方法是核心,該方法向路由表中註冊路由。IgnoreRoute向路由表中註冊忽略路由。
RouteTable:路由表。顧名思義,這個類儲存了我們註冊的路由。而且這個物件在整個網站中只能有一個。接下來我們看一下這個類的程式碼(這是我用反編譯工具編譯出來的程式碼)。
可以看得出來,RouteTable維護了一個RouteCollection型別的靜態欄位。這個欄位中儲存了我們網站的所有路由。然後它有一個靜態建構函式和一個例項建構函式。最後是一個返回值為RouteCollection的Routes屬性,它實際上是對_instance欄位的封裝。public class RouteTable { // Fields private static RouteCollection _instance; // Methods static RouteTable(); [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public RouteTable(); // Properties public static RouteCollection Routes { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; } }
RouteData:路由資料。這個物件儲存的是我們的路由資料。客戶端請求過來的相關引數會儲存在這個類裡面,比如客戶端請求的控制器名稱,Action名稱,QueryString等都可以儲存在這個物件裡面。
public class RouteData
{
// Fields
private RouteValueDictionary _dataTokens;
private IRouteHandler _routeHandler;
private RouteValueDictionary _values;
// Methods
public RouteData();
public RouteData(RouteBase route, IRouteHandler routeHandler);
public string GetRequiredString(string valueName);
// Properties
public RouteValueDictionary DataTokens { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; }
public RouteBase Route { [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
public IRouteHandler RouteHandler { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
public RouteValueDictionary Values { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; }
}
在這個類中,有兩個非常關鍵的欄位_dataTokens和_values,它們都是RouteValueDictionary型別的欄位,因為RouteValueDictionary實現了集合類介面,所以本質上他還是一個鍵值對型別的類。實際上,客戶端請求的控制器名,Action名,引數都會儲存在_values欄位中,而名稱空間則會儲存在_dataTokens中。
_routeHandler在MVC中具體指的是MVCRouteHandler,它是用來建立MVCHandler的物件,而MVCHandler是真正用來處理一次mvc請求的處理程式。Route屬性表示生成該RouteData物件的路由物件。其他的幾個屬性實際上是對前面欄位的封裝。
RouteBase:它是所有Route物件的基類,程式碼如下:
public abstract class RouteBase
{
// Fields
private bool _routeExistingFiles;
// Methods
protected RouteBase();
public abstract RouteData GetRouteData(HttpContextBase httpContext);
public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
// Properties
public bool RouteExistingFiles { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
}
它聲明瞭兩個非常關鍵的抽象方法:GetRouteData和GetVirtualPath。每個Route類都必須實現它。前者用來獲取路由資料,後者用來獲取虛擬路徑。
Route:它是真正用來匹配路由的類程式碼如下:
public class Route : RouteBase
{
// Fields
private ParsedRoute _parsedRoute;
private string _url;
private const string HttpMethodParameterName = "httpMethod";
// Methods
public Route(string url, IRouteHandler routeHandler);
public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler);
public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler);
public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler);
public override RouteData GetRouteData(HttpContextBase httpContext);
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection);
// Properties
public RouteValueDictionary Constraints { [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
public RouteValueDictionary DataTokens { [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
public RouteValueDictionary Defaults { [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
public IRouteHandler RouteHandler { [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get; [CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
public string Url { get; set; }
}
其中url欄位用來儲存路由模板,如"{controller}/{action}/{id}"就是一個路由模板。GetRouteData是核心,它根據當前請求上下文來判斷客戶端的本次請求是否和本路由規則匹配,如果匹配則返回一個RoutaData物件,RouteData的IRoute會被賦值,這個值會體現在RouteData的同名屬性上。而它的RouteHandler屬性會在MapRoute也就是註冊路由的時候被賦值,這個值就是MVCRouteHandler,然後這個值體現到RouteData的同名屬性上。
那麼MVC是如何進行路由匹配呢,這裡就不得不說UrlRoutingMoudle物件,它實現了IHttpMoudle物件,向ASP.NET 事件管道里面註冊事件,程式碼如下:
public class UrlRoutingModule : IHttpModule
{
// Fields
private static readonly object _contextKey;
private static readonly object _requestDataKey;
private RouteCollection _routeCollection;
// Methods
static UrlRoutingModule();
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public UrlRoutingModule();
protected virtual void Dispose();
protected virtual void Init(HttpApplication application);
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e);
[Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")]
public virtual void PostMapRequestHandler(HttpContextBase context);
public virtual void PostResolveRequestCache(HttpContextBase context);
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
void IHttpModule.Dispose();
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
void IHttpModule.Init(HttpApplication application);
// Properties
public RouteCollection RouteCollection { get; [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] set; }
}
這個Moudle向事件管道註冊的時間為PostResolveRequestCache。它的程式碼如下,相信大家看了以後會知道是怎麼回事。
public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
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))
{
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)
{
if (!FormsAuthenticationModule.FormsAuthRequired)
{
throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));
}
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
}
else
{
context.RemapHandler(httpHandler);
}
}
}
}
public RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (httpContext.Request == null)
{
throw new ArgumentException(SR.GetString("RouteTable_ContextMissingRequest"), "httpContext");
}
if (base.Count != 0)
{
bool flag = false;
bool flag2 = false;
if (!this.RouteExistingFiles)
{
flag = this.IsRouteToExistingFile(httpContext);
flag2 = true;
if (flag)
{
return null;
}
}
using (this.GetReadLock())
{
foreach (RouteBase base2 in this)
{
RouteData routeData = base2.GetRouteData(httpContext);
if (routeData != null)
{
if (!base2.RouteExistingFiles)
{
if (!flag2)
{
flag = this.IsRouteToExistingFile(httpContext);
flag2 = true;
}
if (flag)
{
return null;
}
}
return routeData;
}
}
}
}
return null;
}
從程式碼中我們可以清晰的看得出來,它遍歷路由表中所有的路由和當前請匹配,並且只返回第一個匹配到的Route物件,所以網站啟動的時候,最先註冊的路由是最先被拿去做匹配的。