引言
对于ASP.NET MVC的路由系统相信大家肯定不陌生。今天我们就深入ASP.NET的框架内部来看一下路由系统到底是怎么通过我们给出的地址(例如:/Home/Index)解析出Controller和Action。今天的这一篇文章我们就深入框架内部,看看里面的流程。
UrlRouteModule介绍
ASP.NET MVC本质上是通过IHttpModule和IHttpHandler两个组件对ASP.NET框架进行扩展来实现的。ASP.NET 请求处理过程是基于管道模型的,这个管道模型是由多个HttpModule和HttpHandler组成,ASP.NET 把http请求依次传递给管道中各个HttpModule,最终被HttpHandler处理,处理完成后,再次经过管道中的HTTP模块,把结果返回给客户端。我们可以在每个HttpModule中都可以干预请求的处理过程。
ASP.NET MVC就是通过一个自定义IHttpModule将Http请求成功ASP.NET处理管道中接管到MVC框架的。微软自己实现了这个自定义的IHttpModule,这就是我们今天要介绍的UrlRouteModule。这个类是在System.Web.Routing.dll中的。我们通过ILSpy来查看其源码。源码如下(源码经过适当筛选):
public class UrlRoutingModule : IHttpModule
{
private static readonly object _contextKey = new object();
private RouteCollection _routeCollection;
public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
set
{
this._routeCollection = value;
}
}
public UrlRoutingModule()
{
} protected virtual void Init(HttpApplication application)
{
if (application.Context.Items[UrlRoutingModule._contextKey] != null)
{
return;
}
application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
} public virtual void PostResolveRequestCache(HttpContextBase context)
36 {
37 RouteData routeData = this.RouteCollection.GetRouteData(context);
38 if (routeData == null)
39 {
40 return;
41 }
42 IRouteHandler routeHandler = routeData.RouteHandler;
43 if (routeHandler == null)
44 {
45 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
46 }
47 if (routeHandler is StopRoutingHandler)
48 {
49 return;
50 }
51 RequestContext requestContext = new RequestContext(context, routeData);
52 context.Request.RequestContext = requestContext;
53 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
54 if (httpHandler == null)
55 {
56 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
57 {
58 routeHandler.GetType()
59 }));
60 }
61 if (!(httpHandler is UrlAuthFailureHandler))
62 {
63 context.RemapHandler(httpHandler);
64 return;
65 }
66 if (FormsAuthenticationModule.FormsAuthRequired)
67 {
68 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
69 return;
70 }
71 throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
72 }
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpApplication httpApplication = (HttpApplication)sender;
HttpContextBase context = new HttpContextWrapper(httpApplication.Context);
this.PostResolveRequestCache(context);
} protected virtual void Dispose()
{
}
}
我们看到UrlRouteModule继承自IHttpModule接口。这个接口非常简单,只有Init方法和Dispose方法。我们知道ASP.NET处理管线在处理请求过程中,会有19个事件可以让程序员自定义扩展,以便请求在执行完某一个步骤后,可以进行相关操作。UrlRouteModule通过Init方法注册PostResolveRequestCache事件处理函数,让管线处理到PostResolveRequestCache这一步时,调用我们的回调函数OnApplicationPostResolveRequestCache。下面我们来好好分析这个回调函数(代码的73行开始)。
分析PostResolveRequestCache方法
我们看到源代码中的红色部分就是回调函数的主体。第一行代码如下:
RouteData routeData = this.RouteCollection.GetRouteData(context);
我们看到它根据当前请求的上下文,来获取RouteData对象。我们进入GetRouteData看看里面的逻辑是什么。请看源码:
public RouteData GetRouteData(HttpContextBase httpContext)
{
using (this.GetReadLock())
{
foreach (RouteBase current in this)
{
RouteData routeData = current.GetRouteData(httpContext);
if (routeData != null)
{
RouteData result;
if (!current.RouteExistingFiles)
{
if (!flag2)
{
flag = this.IsRouteToExistingFile(httpContext);
}
if (flag)
{
result = null;
return result;
}
}
result = routeData;
return result;
}
}
}
return null;
}
我们看到该方法内部使用循环来依次调用GetRouteData方法(代码的第7行)。很显然这边的this指的就是我们在程序中配置的路由表了。还记得PostResolveRequestCache方法中的this.RouteCollection吗?回到第一个代码片段第11行,我们看到如下代码(注意红色部分):
public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
set
{
this._routeCollection = value;
}
}
看到这,我们应该明白RouteCollection里面存储的是什么了吧!里面存储的就是配置的所有的路由。我们继续往下探索,从current.GetRouteData(httpContext)开始,我们再次进入current对象(类型是RouteBase)的GetRouteData对象。我们看到了RouteBase的源码:
public abstract class RouteBase
{
private bool _routeExistingFiles = true;
public bool RouteExistingFiles
{
get
{
return this._routeExistingFiles;
}
set
{
this._routeExistingFiles = value;
}
}
public abstract RouteData GetRouteData(HttpContextBase httpContext);
public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
}
RouteBase是一个抽象类,GetRouteData方法的实现是根据具体的继承类型的实现为基础的。那么我们不禁要问哪一个类型继承自RouteBase呢?我们想想看,既然RouteTable.Routes里面存储的都是路由对象,那么我们添加路由对象时,添加的应该就是继承自RouteBase类型的派生类。想必这个大家一定不陌生。我们添加路由的时候除了使用MapRoute方法也可以使用Add方法。如下所示:
routes.MapRoute("StaticRoute", "Content/CustomerJS.js",
new { controller = "Home", action = "Index" },
new string[] { "MyFirstMvcProject.Controllers" }); routes.Add("first", new Route("{controller}/{action}", new MvcRouteHandler()));
我们看到MVC框架内部就是使用Route来继承RouteBase的。那么很显然,current.GetRouteData调用的肯定是派生类的方法,我们去Route对象中看看这个方法的具体实现。源码如下:
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring() + httpContext.Request.PathInfo;
RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults);
if (routeValueDictionary == null)
{
return null;
}
RouteData routeData = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest))
{
return null;
}
foreach (KeyValuePair<string, object> current in routeValueDictionary)
{
routeData.Values.Add(current.Key, current.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> current2 in this.DataTokens)
{
routeData.DataTokens[current2.Key] = current2.Value;
}
}
return routeData;
}
我们看到在这个方法中,创建了RouteData对象,并且对URL路径进行了解析(this._parsedRoute.Match(virtualPath, this.Defaults)这一句)。并且后续还验证了路由规则的正则表达式和命名空间。同时我们也看到RouteData很多属性值都是在这里添加的。RouteData的RouteHandler也是取自Route的RouteHandler属性。
到此为止,我们终于看清楚RouteData对象是如何创建的,属性值是如何进行赋值的。我们还是回到UrlRouteModule。我们看下面的代码:
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[]));
}
if (routeHandler is StopRoutingHandler)
{
return;
}
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
我们看到下一步的操作就是获取RouteData中保存的RouteHandler,通过RouteHandler来找到下一步处理请求的Handler。我们这次通过route.MapRoute方法来探索。下面我们一起来看下我们注册路由时经常使用的MapRoute的内部实现是怎么样的?请看源码:
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
} Route route = new Route(url, new MvcRouteHandler())
{
Defaults = CreateRouteValueDictionary(defaults),
Constraints = CreateRouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
}; if ((namespaces != null) && (namespaces.Length > ))
{
route.DataTokens["Namespaces"] = namespaces;
} routes.Add(name, route); return route;
}
我们看到我们调用的MapRoute创建了Route对象(原来RouteTable.Routes里面存储的都是Route类型的实例(Route继承自RouteBase对象)),当然IHttpHandler设置为MvcRouteHandler。并且把默认值default、约束、命名空间的值都存储为RouteValueDictionary类型。那么显然RouteData的RouteHandler就是MvcRouteHandler。
我们知道RouteData的RouteHandler是IRouteHandler类型的,MvcRouteHandler是IRouteHandler的具体实现。我们来看下MvcRouteHandler的源代码:
public class MvcRouteHandler : IRouteHandler
{
private IControllerFactory _controllerFactory; public MvcRouteHandler()
{
} public MvcRouteHandler(IControllerFactory controllerFactory)
{
_controllerFactory = controllerFactory;
} protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
} }
通过源代码我们看到MvcRouteHandler实现了IRouteHandler接口的GetHttpHandler方法。我们在UrlRouteModule中通过RouteData的RouteHandler属性获取HttpHandler其实调用的就是MvcRouteHandler的GetHttpHandler方法。我们看到最终返回的是MvcHandler类型。到此为止,我们就知道最终返回的IHttpHandler类型就是MvcHandler。请求的后续操作就交给这个HttpHandler处理了。
相关总结
1、我们经常使用的MapRoute并不是RouteCollection自带的方法,而是在MVC源码里提供的一个扩展方法。扩展类名是:RouteCollectionExtensions。
2、Route继承自RouteBase抽象类。在获取RouteData的方法中,遍历RouteTable.Routes集合,将当前的请求的URL和路由模板进行匹配,这一过程实质调用的是Route类型的GetRouteData方法。
3、RouteData的相关属性和RouteHandler都是从Route对象获取的。
4、路由系统最终返回的IHttpHandler类型是MvcHandler类型,请求的后续操作就交给这个HttpHandler处理了。