UrlRoutingModule的功能
在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
事件。
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错误。
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中获取IHttpHandler13 ????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 ????????else46 ????????{47 ????????????throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3));48 ????????}49 ????}50 51 ????// Remap IIS7 to our handler52 ????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 taking13 ????// 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.config17 ????// 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 write20 ????// 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 routes36 ????????????// 如果文件存在,但是路由并没有匹配上,则停止继续处理当前请求。37 ????????????return null;38 ????????}39 ????}40 41 ????// Go through all the configured routes and find the first one that returns a match42 ????// 遍历所有已配置的路由并且返回第一个与之匹配的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 routes51 ????????????????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 // 如果当前请求的是一个存在的文件,则返回true2 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 }
如果文中有表述不正确或有疑问的可以在评论中指出,一起学习一起进步!!
【源码】进入ASP.NET MVC流程的大门 - UrlRoutingModule
原文地址:https://www.cnblogs.com/xhb-bky-blog/p/9235086.html