根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程序可以有多個HttpModuel,但是只能有一個HttpHandler,並且通過這個 HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前面的章節將到了再 MapHttpHandler這個周期將會根據請求的URL來查詢對應的HttpHandler,那麼它是如何查找的呢?
一起我們在做自定義HttpHandler的時候,需要執行URL以及擴展名匹配規則,然後查找HttpHandler的時候就是根據相應的規則來查找哪個HttpHandler可以使用。另一方面我們本系列教材講的MVC就是通過注冊路由(Route)來匹配到對應的Controller和 Action上的,例如Global.asax裡的代碼:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
但是在匹配這個之前,MVC首先要接管請求才能處理,也就是說我們要有對應MVC的HttpHandler(後面知道它的名字叫 MvcHandler)被MapRequestHandler周期的處理引擎查找到並且應用上才行,然後後面才能由 Controller/Action執行。另外一方面,由於該URL地址沒有擴展名,所以無法進入ASP.NET的RunTime,MVC2的實現方式是:注冊通配符(*.*)映射到aspnet_ISPAI.dll,然後通過一個自定義的UrlRoutingModuel來匹配Route規則,再繼續處理,但是MVC3的時候,匹配Route規則的處理機制集成到ASP.NET4.0裡了,也就是今天我們這篇文章所要講的主角(UrlRoutingModule)的處理機制。
先來看UrlRoutingModule的源碼,無容置疑地這個類是繼承於IHttpModule,首先看一下Init方法的代碼:
protected virtual void Init(HttpApplication application) {
//////////////////////////////////////////////////////////////////
// Check if this module has been already addded
if (application.Context.Items[_contextKey] != null) {
return; // already added to the pipeline
}
application.Context.Items[_contextKey] = _contextKey;
// Ideally we would use the MapRequestHandler event. However, MapRequestHandler is not available
// in II6 or IIS7 ISAPI Mode. Instead, we use PostResolveRequestCache, which is the event immediately
// before MapRequestHandler. This allows use to use one common codepath for all versions of IIS.
application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
}
該代碼在PostResolveRequestCache周期事件上添加了我們需要執行的方法,用於URL匹配規則的設置,但是為什麼要在這個周期點上添加事件呢?看了注釋,再結合我們前面對Pipeline的了解,釋然了,要像動態注冊自己的HttpHandler,那就需要在 MapRequestHandler之前進行注冊自己的規則(因為這個周期點就是做這個事情的),但由於IIS6不支持這個事件,所以為了能讓IIS6也能運行MVC3,所以我們需要在這個周期之前的PostResolveRequestCache的事件點上去注冊我們的規則,也許如果IIS6被微軟廢棄以後,就會將這個事件添加到真正的開始點MapRequestHandler上哦。
我們繼續來看注冊該事件的OnApplicationPostResolveRequestCache方法的代碼:
public virtual void PostResolveRequestCache(HttpContextBase context) {
// Match the incoming URL against the route table
RouteData routeData = RouteCollection.GetRouteData(context);
// Do nothing if no route found
if (routeData == null) {
return;
}
// If a route was found, get an IHttpHandler from the route's RouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
SR.GetString(SR.UrlRoutingModule_NoRouteHandler)));
}
// This is a special IRouteHandler that tells the routing module to stop processing
// routes and to let the fallback handler handle the request.
if (routeHandler is StopRoutingHandler) {
return;
}
RequestContext requestContext = new RequestContext(context, routeData);
// Dev10 766875 Adding RouteData to HttpContext
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
SR.GetString(SR.UrlRoutingModule_NoHttpHandler),
routeHandler.GetType()));
}
if (httpHandler is UrlAuthFailureHandler) {
if (FormsAuthenticationModule.FormsAuthRequired) {
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
return;
}
else {
throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3));
}
}
// Remap IIS7 to our handler
context.RemapHandler(httpHandler);
}