众所周知,ASP.NET MVC有一套自己的路由系统。这套路由系统是在原来的ASP.NET 路由系统上扩展过来的。今天这篇文章就来聊聊MVC路由系统中非常关键的一些对象。
ASP.NET MVC路由系统主要由以下几个核心对象:
1.RouteCollection(RouteCollextionExtentions)
2.RouteTable
3.RouteData
4.Route:RouteBase
5.URLRouteMoudle
下面我们就来一一介绍这些对象
RouteCollection:这个是提供ASP.NET路由的路由集合,而MVC的路由类RouteCollectionExtentions中的方法是从RouteCollection扩展过来的扩展方法,它负责MVC的路由工作。下面是这个类的代码。
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:路由表。顾名思义,这个类存储了我们注册的路由。而且这个对象在整个网站中只能有一个。接下来我们看一下这个类的代码(这是我用反编译工具编译出来的代码)。
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; } }
可以看得出来,RouteTable维护了一个RouteCollection类型的静态字段。这个字段中存储了我们网站的所有路由。然后它有一个静态构造函数和一个实例构造函数。最后是一个返回值为RouteCollection的Routes属性,它实际上是对_instance字段的封装。
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对象,所以网站启动的时候,最先注册的路由是最先被拿去做匹配的。