UrlRoutingModule的功能

在ASP.NET MVC的请求过程中,UrlRoutingModule的作用是拦截当前的请求URL,通过URL来解析出RouteData,为后续的一系列流程提供所需的数据,比如ControllerAction等等。其实,UrlRoutingModule和我们自定义的HttpModule都是实现了IHttpModule接口的两个核心方法,Init方法和Dispose方法。下面是MVC中实现UrlRoutingModule代码。首先,在初始化的方法中检查该Module是否被加入到了当前请求的请求管道,然后注册了管道事件中的PostResolveRequestCache事件。其实最理想的注册事件应该是MapRequestHandler事件,但是为了考虑到兼容性(IIS 6 和 IIS 7 ISAPI模式下不可用),微软选择了紧邻MapRequestHandler事件之前的PostResolveRequestCache事件。

 protected virtual void Init(HttpApplication application)
{
// 检查 UrlRoutingModule 是否已经被加入到请求管道中
if (application.Context.Items[_contextKey] != null)
{
// 已经添加到请求管道则直接返回
return;
}
application.Context.Items[_contextKey] = _contextKey; // 理想情况下, 我们应该注册 MapRequestHandler 事件。但是,MapRequestHandler事件在
// II6 或 IIS7 ISAPI 模式下是不可用的,所以我们注册在 MapRequestHandler 之前执行的事件,
// 该事件适用于所有的IIS版本。
application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
}

在注册事件中,将HttpApplication的请求上下文HttpContext做了一个封装,因为HttpContext是没有基类的,也不是Virtual的,所以没办法做单元测试,也就是不可Mock的,所以针对HttpContext做了一个封装。HttpContextBaseHttpContextWrapper的基类,真正封装HttpContext的就是HttpContextWrapper,所以三者之间的关系就是这样的。完成封装以后开始执行PostResolveRequestCache方法,并将封装好的请求上下文作为参数传入。

 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
//HttpContextWrapper继承自HttpContextBase,用于替换HttpContext以实现可测试的接口
HttpApplication app = (HttpApplication)sender;
HttpContextBase context = new HttpContextWrapper(app.Context);
PostResolveRequestCache(context);
}

进入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后还要继续验证是不是UrlAuthFailureHandlerUrlAuthFailureHandler也实现了IHttpHandler,当当前请求无权限时,用于返回401错误。

 public virtual void PostResolveRequestCache(HttpContextBase context)
{
// 匹配传入的URL,检查路由表中是否存在与之匹配的URL
RouteData routeData = RouteCollection.GetRouteData(context); // 如果没有找到匹配的路由信息,直接返回
if (routeData == null)
{
return;
} // 如果找到的匹配的路由,则从路由信息的RouteHandler中获取IHttpHandler
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.UrlRoutingModule_NoRouteHandler)));
} // 如果该IRouteHandler是StopRoutingHandler,路由模块会停止继续处理该请求
// routes and to let the fallback handler handle the request.
if (routeHandler is StopRoutingHandler)
{
return;
} 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(SR.UrlRoutingModule_NoHttpHandler), routeHandler.GetType()));
} // 如果该IHttpHandler是认证失败的IHttpHandler,返回401权限不足错误
if (httpHandler is UrlAuthFailureHandler)
{
if (FormsAuthenticationModule.FormsAuthRequired)
{
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
return;
}
else
{
throw new HttpException(, SR.GetString(SR.Assess_Denied_Description3));
}
} // Remap IIS7 to our handler
context.RemapHandler(httpHandler);
}

如果请求认证失败,返回401错误,并且调用CompleteRequest方法,显式地完成当前请求。

 internal static void ReportUrlAuthorizationFailure(HttpContext context, object webEventSource)
{
// 拒绝访问
context.Response.StatusCode = ;
WriteErrorMessage(context); if (context.User != null && context.User.Identity.IsAuthenticated) {
// 这里AuditUrlAuthorizationFailure指示在Web请求过程中URL授权失败的事件代码
WebBaseEvent.RaiseSystemEvent(webEventSource, WebEventCodes.AuditUrlAuthorizationFailure);
}
context.ApplicationInstance.CompleteRequest();
}

方法GetRouteData的作用是根据当前请求的上下文来获取路由数据,在匹配RouteCollection集合之前,会检查当前的请求是否是静态文件,如果请求的是存在于服务器上的静态文件则直接返回,否则继续处理当前请求。

 public RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (httpContext.Request == null)
{
throw new ArgumentException(SR.GetString(SR.RouteTable_ContextMissingRequest), "httpContext");
} // Optimize performance when the route collection is empty.当路由集合是空的的时候优化性能. The main improvement is that we avoid taking
// a read lock when the collection is empty.主要的改进是当集合为空的时候避免添加只读锁。 Without this check, the UrlRoutingModule causes a 25%-50%
// 没有这个检查的话,UrlRoutingModule 性能会因为锁的缘故而下降25%-50%
// regression in HelloWorld RPS due to lock contention. The UrlRoutingModule is now in the root web.config,
// UrlRoutingModule目前被配置在根目录的web.config
// so we need to ensure the module is performant, especially when you are not using routing.
// 所以我们应该确认下这个module是否是高效的,尤其是当没有使用路由的时候。
// This check does introduce a slight bug, in that if a writer clears the collection as part of a write
// transaction, a reader may see the collection when it's empty, which the read lock is supposed to prevent.
// We will investigate a better fix in Dev10 Beta2. The Beta1 bug is Dev10 652986.
if (Count == ) {
return null;
} bool isRouteToExistingFile = false;
// 这里只检查一次
bool doneRouteCheck = false;
if (!RouteExistingFiles)
{
isRouteToExistingFile = IsRouteToExistingFile(httpContext);
doneRouteCheck = true;
if (isRouteToExistingFile)
{
// If we're not routing existing files and the file exists, we stop processing routes
// 如果文件存在,但是路由并没有匹配上,则停止继续处理当前请求。
return null;
}
} // Go through all the configured routes and find the first one that returns a match
// 遍历所有已配置的路由并且返回第一个与之匹配的
using (GetReadLock())
{
foreach (RouteBase route in this)
{
RouteData routeData = route.GetRouteData(httpContext);
if (routeData != null)
{
// If we're not routing existing files on this route and the file exists, we also stop processing routes
if (!route.RouteExistingFiles)
{
if (!doneRouteCheck)
{
isRouteToExistingFile = IsRouteToExistingFile(httpContext);
doneRouteCheck = true;
}
if (isRouteToExistingFile)
{
return null;
}
}
return routeData;
}
}
}
return null;
}

下面这段代码就是获取相对路径来检测文件夹和文件是否存在,存在返回true,否则返回false

 // 如果当前请求的是一个存在的文件,则返回true
private bool IsRouteToExistingFile(HttpContextBase httpContext)
{
string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
return ((requestPath != "~/") &&
(VPP != null) &&
(VPP.FileExists(requestPath) ||
VPP.DirectoryExists(requestPath)));
}

如果文中有表述不正确或有疑问的可以在评论中指出,一起学习一起进步!!

【源码】进入ASP.NET MVC流程的大门 - UrlRoutingModule的更多相关文章

  1. 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”

    通过源码了解ASP.NET MVC 几种Filter的执行过程   一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...

  2. Solr4.8.0源码分析(5)之查询流程分析总述

    Solr4.8.0源码分析(5)之查询流程分析总述 前面已经写到,solr查询是通过http发送命令,solr servlet接受并进行处理.所以solr的查询流程从SolrDispatchsFilt ...

  3. EDKII Build Process:EDKII项目源码的配置、编译流程[三]

    <EDKII Build Process:EDKII项目源码的配置.编译流程[3]>博文目录: 3. EDKII Build Process(EDKII项目源码的配置.编译流程) -> ...

  4. 通过官方API结合源码,如何分析程序流程

    通过官方API结合源码,如何分析程序流程通过官方API找到我们关注的API的某个方法,然后把整个流程执行起来,然后在idea中,把我们关注的方法打上断点,然后通过Step Out,从内向外一层一层分析 ...

  5. Cocos2dx源码赏析(1)之启动流程与主循环

    Cocos2dx源码赏析(1)之启动流程与主循环 我们知道Cocos2dx是一款开源的跨平台游戏引擎,而学习开源项目一个较实用的办法就是读源码.所谓,"源码之前,了无秘密".而笔者 ...

  6. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  7. (转)linux内存源码分析 - 内存回收(整体流程)

    http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ...

  8. HDFS源码分析DataXceiver之整体流程

    在<HDFS源码分析之DataXceiverServer>一文中,我们了解到在DataNode中,有一个后台工作的线程DataXceiverServer.它被用于接收来自客户端或其他数据节 ...

  9. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

随机推荐

  1. 转:sql语句优化

    性能不理想的系统中除了一部分是因为应用程序的负载确实超过了服务器的实际处理能力外,更多的是因为系统存在大量的SQL语句需要优化. 为了获得稳定的执行性能,SQL语句越简单越好.对复杂的SQL语句,要设 ...

  2. 【转】Java学习---内存泄露与溢出的区别

    Java内存泄露与溢出的区别 Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽): 而Java内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于 ...

  3. 2.1Python基础语法(一)之注释与数据类型:

    返回总目录 目录: 1.注释 2.乱码 3.变量 4.数据类型 5.数据的转换 6.动态,静态和强类型,弱类型 (一)注释:(编译时是被编译器忽略的) 1.注释的分类: 2.特殊注释: (二)乱码:( ...

  4. Eclipse 中怎样自动格式化代码?

    首先 有一个 检查代码风格的工具叫checkstyle,具体怎么下载,请自行百度.. 当你在eclipse安装好 checkstyle后,对于使用google标准的人来说,选择一个项目,右键,点击ch ...

  5. 百度地图POI数据爬取,突破百度地图API爬取数目“400条“的限制11。

    1.POI爬取方法说明 1.1AK申请 登录百度账号,在百度地图开发者平台的API控制台申请一个服务端的ak,主要用到的是Place API.检校方式可设置成IP白名单,IP直接设置成了0.0.0.0 ...

  6. 基于汇编的 C/C++ 协程 - 切换上下文

    在前一篇文章<基于汇编的 C/C++ 协程 - 背景知识>中提到一个用于 C/C++ 的协程所需要实现的两大功能: 协程调度 上下文切换 其中调度,其实在技术实现上与其他的线程.进程调度没 ...

  7. 1、pyspider安装

    系统环境: centos6.6.python2.7 经测试,python2.6安装的pyspider会导致webui无法正常访问 参考博文: http://cuiqingcai.com/2443.ht ...

  8. gettimeofday()函数来得到时间

    gettimeofday()函数的使用方法: 1.简介: 在C语言中可以使用函数gettimeofday()函数来得到时间.它的精度可以达到微妙 2.函数原型: #include<sys/tim ...

  9. JAVA 第一周学习总结

    20175308 2018-2019-2 <Java程序设计>第一周学习总结 教材学习内容总结 1.关于java 2.java开发环境的配置 3.java编译.运行的简单实例 4.git的 ...

  10. Can't create component 'xxx.xxx.xxx' as it has dependencies to be satisfied

    问题描述: Can't create component 'xxx.xxx.xxx' as it has dependencies to be satisfied. 问题原由: 没有对新建的实体映射类 ...