在ASP.NET MVC的請求過程當中,UrlRoutingModule
的做用是攔截當前的請求URL,經過URL來解析出RouteData
,爲後續的一系列流程提供所需的數據,好比Controller
、Action
等等。其實,UrlRoutingModule
和咱們自定義的HttpModule
都是實現了IHttpModule
接口的兩個核心方法,Init
方法和Dispose
方法。下面是MVC中實現UrlRoutingModule
代碼。首先,在初始化的方法中檢查該Module是否被加入到了當前請求的請求管道,而後註冊了管道事件中的PostResolveRequestCache
事件。其實最理想的註冊事件應該是MapRequestHandler
事件,可是爲了考慮到兼容性(IIS 6 和 IIS 7 ISAPI模式下不可用),微軟選擇了緊鄰MapRequestHandler
事件以前的PostResolveRequestCache
事件。web
1 protected virtual void Init(HttpApplication application) 2 { 3 // 檢查 UrlRoutingModule 是否已經被加入到請求管道中 4 if (application.Context.Items[_contextKey] != null) 5 { 6 // 已經添加到請求管道則直接返回 7 return; 8 } 9 application.Context.Items[_contextKey] = _contextKey; 10 11 // 理想狀況下, 咱們應該註冊 MapRequestHandler 事件。可是,MapRequestHandler事件在 12 // II6 或 IIS7 ISAPI 模式下是不可用的,因此咱們註冊在 MapRequestHandler 以前執行的事件, 13 // 該事件適用於全部的IIS版本。 14 application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; 15 }
在註冊事件中,將HttpApplication
的請求上下文HttpContext
作了一個封裝,由於HttpContext
是沒有基類的,也不是Virtual的,因此沒辦法作單元測試,也就是不可Mock的,因此針對HttpContext
作了一個封裝。HttpContextBase
是HttpContextWrapper
的基類,真正封裝HttpContext
的就是HttpContextWrapper
,因此三者之間的關係就是這樣的。完成封裝之後開始執行PostResolveRequestCache
方法,並將封裝好的請求上下文做爲參數傳入。服務器
1 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) 2 { 3 //HttpContextWrapper繼承自HttpContextBase,用於替換HttpContext以實現可測試的接口 4 HttpApplication app = (HttpApplication)sender; 5 HttpContextBase context = new HttpContextWrapper(app.Context); 6 PostResolveRequestCache(context); 7 }
進入PostResolveRequestCache
事件後,UrlRoutingModule
開始真正的工做,該方法是處理URL的核心方法。根據當前請求的上下文,去匹配路由表是否存在與之匹配的URL,若是匹配則從路由信息中獲取RouteData
,以及IRouteHandler
。拿到IRouteHandler
後,要進行一些列的判斷,好比要判斷是不是StopRoutingHandler
,在Global.asax文件中有一段RouteConfig.RegisterRoutes(RouteTable.Routes);
代碼,在這個RegisterRoutes
方法中有一句routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
表示須要過濾掉的路由,而這個IgnoreRoute
路由的Handler就是StopRoutingHandler
,因此在這裏作了判斷,Handler是StopRoutingHandler
則不往下執行,直接return,再也不處理這條請求,若是是則路由模塊會中止繼續處理請求,若是不是則繼續處理,並根據RequestContext
來獲取IHttpHandler
,拿到IHttpHandler
後還要繼續驗證是否是UrlAuthFailureHandler
,UrlAuthFailureHandler
也實現了IHttpHandler
,噹噹前請求無權限時,用於返回401錯誤。app
1 public virtual void PostResolveRequestCache(HttpContextBase context) 2 { 3 // 匹配傳入的URL,檢查路由表中是否存在與之匹配的URL 4 RouteData routeData = RouteCollection.GetRouteData(context); 5 6 // 若是沒有找到匹配的路由信息,直接返回 7 if (routeData == null) 8 { 9 return; 10 } 11 12 // 若是找到的匹配的路由,則從路由信息的RouteHandler中獲取IHttpHandler 13 IRouteHandler routeHandler = routeData.RouteHandler; 14 if (routeHandler == null) 15 { 16 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.UrlRoutingModule_NoRouteHandler))); 17 } 18 19 // 若是該IRouteHandler是StopRoutingHandler,路由模塊會中止繼續處理該請求 20 // routes and to let the fallback handler handle the request. 21 if (routeHandler is StopRoutingHandler) 22 { 23 return; 24 } 25 26 RequestContext requestContext = new RequestContext(context, routeData); 27 28 // 將路由信息添加到請求上下文 29 context.Request.RequestContext = requestContext; 30 31 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 32 if (httpHandler == null) 33 { 34 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, SR.GetString(SR.UrlRoutingModule_NoHttpHandler), routeHandler.GetType())); 35 } 36 37 // 若是該IHttpHandler是認證失敗的IHttpHandler,返回401權限不足錯誤 38 if (httpHandler is UrlAuthFailureHandler) 39 { 40 if (FormsAuthenticationModule.FormsAuthRequired) 41 { 42 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); 43 return; 44 } 45 else 46 { 47 throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3)); 48 } 49 } 50 51 // Remap IIS7 to our handler 52 context.RemapHandler(httpHandler); 53 }
若是請求認證失敗,返回401錯誤,而且調用CompleteRequest
方法,顯式地完成當前請求。性能
1 internal static void ReportUrlAuthorizationFailure(HttpContext context, object webEventSource) 2 { 3 // 拒絕訪問 4 context.Response.StatusCode = 401; 5 WriteErrorMessage(context); 6 7 if (context.User != null && context.User.Identity.IsAuthenticated) { 8 // 這裏AuditUrlAuthorizationFailure指示在Web請求過程當中URL受權失敗的事件代碼 9 WebBaseEvent.RaiseSystemEvent(webEventSource, WebEventCodes.AuditUrlAuthorizationFailure); 10 } 11 context.ApplicationInstance.CompleteRequest(); 12 }
方法GetRouteData
的做用是根據當前請求的上下文來獲取路由數據,在匹配RouteCollection
集合以前,會檢查當前的請求是不是靜態文件,若是請求的是存在於服務器上的靜態文件則直接返回,不然繼續處理當前請求。單元測試
1 public RouteData GetRouteData(HttpContextBase httpContext) 2 { 3 if (httpContext == null) 4 { 5 throw new ArgumentNullException("httpContext"); 6 } 7 if (httpContext.Request == null) 8 { 9 throw new ArgumentException(SR.GetString(SR.RouteTable_ContextMissingRequest), "httpContext"); 10 } 11 12 // Optimize performance when the route collection is empty.當路由集合是空的的時候優化性能. The main improvement is that we avoid taking 13 // a read lock when the collection is empty.主要的改進是當集合爲空的時候避免添加只讀鎖。 Without this check, the UrlRoutingModule causes a 25%-50% 14 // 沒有這個檢查的話,UrlRoutingModule 性能會由於鎖的緣故而降低25%-50% 15 // regression in HelloWorld RPS due to lock contention. The UrlRoutingModule is now in the root web.config, 16 // UrlRoutingModule目前被配置在根目錄的web.config 17 // so we need to ensure the module is performant, especially when you are not using routing. 18 // 因此咱們應該確認下這個module是不是高效的,尤爲是當沒有使用路由的時候。 19 // This check does introduce a slight bug, in that if a writer clears the collection as part of a write 20 // transaction, a reader may see the collection when it's empty, which the read lock is supposed to prevent. 21 // We will investigate a better fix in Dev10 Beta2. The Beta1 bug is Dev10 652986. 22 if (Count == 0) { 23 return null; 24 } 25 26 bool isRouteToExistingFile = false; 27 // 這裏只檢查一次 28 bool doneRouteCheck = false; 29 if (!RouteExistingFiles) 30 { 31 isRouteToExistingFile = IsRouteToExistingFile(httpContext); 32 doneRouteCheck = true; 33 if (isRouteToExistingFile) 34 { 35 // If we're not routing existing files and the file exists, we stop processing routes 36 // 若是文件存在,可是路由並無匹配上,則中止繼續處理當前請求。 37 return null; 38 } 39 } 40 41 // Go through all the configured routes and find the first one that returns a match 42 // 遍歷全部已配置的路由而且返回第一個與之匹配的 43 using (GetReadLock()) 44 { 45 foreach (RouteBase route in this) 46 { 47 RouteData routeData = route.GetRouteData(httpContext); 48 if (routeData != null) 49 { 50 // If we're not routing existing files on this route and the file exists, we also stop processing routes 51 if (!route.RouteExistingFiles) 52 { 53 if (!doneRouteCheck) 54 { 55 isRouteToExistingFile = IsRouteToExistingFile(httpContext); 56 doneRouteCheck = true; 57 } 58 if (isRouteToExistingFile) 59 { 60 return null; 61 } 62 } 63 return routeData; 64 } 65 } 66 } 67 return null; 68 }
下面這段代碼就是獲取相對路徑來檢測文件夾和文件是否存在,存在返回true
,不然返回false
。學習
1 // 若是當前請求的是一個存在的文件,則返回true 2 private bool IsRouteToExistingFile(HttpContextBase httpContext) 3 { 4 string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath; 5 return ((requestPath != "~/") && 6 (VPP != null) && 7 (VPP.FileExists(requestPath) || 8 VPP.DirectoryExists(requestPath))); 9 }
若是文中有表述不正確或有疑問的能夠在評論中指出,一塊兒學習一塊兒進步!!測試