根據對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); }