最近有一段时间都在看ASP.NET MVC框架的源代码。原来的想法是,如果有相应ASP.NET MVC的文档,就直接看文档,学学怎么使用这个框架。可惜都是视频教程,而又感觉看那本电子书有点无趣,索性就直接看源代码了。这样一来反而收获了很多的东西。
在ASP.NET MVC框架里面,用到了URL Routing组件。URL Routing组件干什么用呢?根据你配置的URL 模式来提取数据或者生成相应的URL 。但我有一个疑问:URL Routing组件是如何同ASP.NET MVC框架关联起来的?
由于这个组件没有开放源代码,所以我们查看代码的方式也就只有反编译了。我们可以看到Route类型。这个类表示一条路由,包括URL模式,路由数据,HTTP处理程序等。我们可以看到它的构造器如下:
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);
Route的初始化时需要一个实现IRouteHandler接口的类的实例的。IRouteHandler的定义如下:
public interface IRouteHandler{IHttpHandler GetHttpHandler(RequestContext requestContext);}
IRouteHandler只定义了一个方法——GetHttpHandler,返回一个IHttpHandler。IHttpHandler其实就是ASP.NET管线事件的终点,所有的HTTP请求最后都是由IHttpHandler的实现来处理的。
沿着这个思路,我们继续往下找,看能不能找到一个实现这个接口的类型。一开始看到一个UrlRoutingHandler,以为找到要找的类型了,不过仔细一开类型的定义,是抽象类型,表示Routing的终点,因为这个抽象类实现了IHttpHandler接口。查看它的ProcessRequest方法的代码,如下:
protected virtual void ProcessRequest(HttpContextBase httpContext){RouteData routeData = this.RouteCollection.GetRouteData(httpContext);
if (routeData == null){throw new HttpException(0x194, SR.GetString("UrlRoutingHandler_NoRouteMatches"));}IRouteHandler routeHandler = routeData.RouteHandler;if (routeHandler == null){throw new InvalidOperationException(SR.GetString("UrlRoutingModule_NoRouteHandler"));}RequestContext requestContext = new RequestContext(httpContext, routeData);
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);if (httpHandler == null){throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() }));}this.VerifyAndProcessRequest(httpHandler, httpContext);
}
可以看到,实际上这个方法首先使用Route的实例返回一个IRouteHandler接口实现类的实例对象,调用IRouteHandler.GetHttpHandler()方法返回一个IHttpHandler实现类的实例对象,最后调用了this.VerifyAndProcessRequest(httpHandler, httpContext)方法进行处理。VerifyAndProcessRequest(httpHandler, httpContext)定义如下:
protected abstract void VerifyAndProcessRequest(
IHttpHandler httpHandler,
HttpContextBase httpContext);
这是一个抽象方法。URL Routing组件最后一定映射到一个处理程序来相应HTTP请求,显然,必定有一个实现类继承并且实现了这个VerifyAndProcessRequest方法。我们在这个组件里面接着找,还是没有找到相应的实现类型,但是我们发现了一个UrlRoutingModule类。这个类实现了IHttpModule,是一个ASP.NET模块。那么这个模块做了什么?我们可以查看这个模块的Init(HttpApplication application)方法,定义如下:
protected virtual void Init(HttpApplication application){if (application.Context.Items[_contextKey] == null){application.Context.Items[_contextKey] = _contextKey;application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);}}
它订阅了ASP.NET的PostResolveRequestCache管线事件,并且这个方法重新映射了HTTP 处理程序,其实这个类型在.NET 3.5以及.NET 4.0中还是有所不同的。(this.OnApplicationPostResolveRequestCache方法代码如下:
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.CurrentUICulture, 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);
}
}
}
}
这个方法从Route实例取得RouteData,并且从RouteData获得相应的IHttpHandler处理程序来处理请求,这样,URL Routing最终把HTTP请求映射到一个处理程序处理。但是这里有个问题,就是这个处理程序的实例是什么时候初始化,又是如何初始化的?并且,URL Routing是怎么和ASP.NET MVC框架关联起来的?
带着这个问题去看ASP.NET MVC框架的源代码,我们找到两个引人注意的类型:MvcHttpHandler以及MvcRouteHandler。MvcHttpHandler继承了UrlRoutingHandler, IHttpAsyncHandler, IRequiresSessionState,是一个HttpHandler。看来这个类型并不是我们要寻找的答案。于是,我想到是不是利用配置文件来把这两个组件关联起来的?于是我去看了Oxite的配置文件,注意到有这么一段配置:
<httpHandlers><remove verb="*" path="*.asmx"/><add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/><add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/></httpHandlers><httpModules><add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/></httpModules>
这段配置还是没有解决我们的疑问。因为我们知道ASP.NET MVC框架必定在某一个地方创建了一个MvcHttpHandler的实例并且将这个实例传递给URL Routing组件,URL Routing最终将请求映射到这个处理程序,然后相应请求。联想到我们每一个路由都是有一个Route类型表示的,那么我们如何实例化这个类型?一般都是在Application_Start事件里初始化并且添加到路由表里面的吧?OK,也许问题的答案就在这个地方。我们添加路由的时候,记得都是用MapRoute这个拓展方法吧,这个方法非常方便,但是这个方法被定义在哪里?在找遍了URL Routing组件,我并没有发现有这样的这样的拓展方法。于是我想到可能又是放在了ASP.NET MVC框架里了。于是我在框架里发现了RouteCollectionExtensions这个类型,这个类型为RouteCollection定义了非常多的拓展方法,其中就包括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 = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};if ((namespaces != null) && (namespaces.Length > 0)) {route.DataTokens["Namespaces"] = namespaces;
}routes.Add(name, route);return route;
}
在兜了一圈,终于回来了。我们非常清楚的看到,是在这里,把MvcHttpHandler注入,也就将URL Routing和ASP.NET MVC关联起来了。
总结
这里只是整理我在看ASP.NET MVC框架源代码中,忽然想到的框架是如可和URL Routing组件关联起来这个问题的解题思路,绕了一圈才忽然明白,因此我将我的思路记录了下来。其实一句话,在初始化Route类型的时候就将IHttpHandler实例注入,这样就完成了关联了。