众所周知,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);
}
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; }
}
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; }
}
那么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; }
}
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对象,所以网站启动的时候,最先注册的路由是最先被拿去做匹配的。