文章内容

根据对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(, SR.GetString(SR.Assess_Denied_Description3));
}
} // Remap IIS7 to our handler
context.RemapHandler(httpHandler);
}

我已经加粗了4行重要的代码,第一行是通过传递HttpContext参数,从RouteCollection找到对应的静态属性RouteData( GetRouteData方法里会先判断真实文件是否存在,如果不存在才去找RouteData),第二行然后从RouteData的属性RouteHandler获取一个IRouteHandler的实例,第三行是从该实例里获取对应的IHttpHandler实例,第4行是调用HttpContext的RemapHandler方法重新map新的handler(这行代码的注释虽然说是remap IIS7,其实IIS6也是用了,只不过判断该方法里对IIS7集成模式多了一点特殊处理而已),然后可以通过HttpContext. RemapHandlerInstance属性来得到这个实例。

关于Route/RouteData/RouteCollection/IRouteHandler的作用主要就是定义URL匹配到指定的IHttpHandler,然后注册进去,具体实现我们稍后再讲,现在先看一下Http Pipeline里是如何找到这个IHttpHandler实例的,由于IIS6和IIS7集成模式是差不多的,前面的文章我们提到了都是最终调用到IHttpHandlerFactory的实例,然后从中获取IHttpHandler,所以我们这里只分析IIS6和IIS7经典模式的实现。

先来看BuildSteps里查找HttpHandler的方法MapHandlerExecutionStep的代码,只有几行代码,最重要的是:

context.Handler = _application.MapHttpHandler(
context,
request.RequestType,
request.FilePathObject,
request.PhysicalPathInternal,
false /*useAppConfig*/);

MapHttpHandler就是我们要查找Handler的方法了,来仔细看看代码:

internal IHttpHandler MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, bool useAppConfig) {
// Don't use remap handler when HttpServerUtility.Execute called
IHttpHandler handler = (context.ServerExecuteDepth == 0) ? context.RemapHandlerInstance : null; using (new ApplicationImpersonationContext()) {
// Use remap handler if possible
if (handler != null){
return handler;
} // Map new handler
HttpHandlerAction mapping = GetHandlerMapping(context, requestType, path, useAppConfig); // If a page developer has removed the default mappings with <httpHandlers><clear>
// without replacing them then we need to give a more descriptive error than
// a null parameter exception.
if (mapping == null) {
PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_NOT_FOUND);
PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_FAILED);
throw new HttpException(SR.GetString(SR.Http_handler_not_found_for_request_type, requestType));
} // Get factory from the mapping
IHttpHandlerFactory factory = GetFactory(mapping); // Get factory from the mapping
try {
// Check if it supports the more efficient GetHandler call that can avoid
// a VirtualPath object creation.
IHttpHandlerFactory2 factory2 = factory as IHttpHandlerFactory2; if (factory2 != null) {
handler = factory2.GetHandler(context, requestType, path, pathTranslated);
}
else {
handler = factory.GetHandler(context, requestType, path.VirtualPathString, pathTranslated);
}
}
catch (FileNotFoundException e) {
if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated))
throw new HttpException(, null, e);
else
throw new HttpException(, null);
}
catch (DirectoryNotFoundException e) {
if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated))
throw new HttpException(, null, e);
else
throw new HttpException(, null);
}
catch (PathTooLongException e) {
if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated))
throw new HttpException(, null, e);
else
throw new HttpException(, null);
} // Remember for recycling
if (_handlerRecycleList == null)
_handlerRecycleList = new ArrayList();
_handlerRecycleList.Add(new HandlerWithFactory(handler, factory));
} return handler;
}

从代码可以看出,首先如果当前页面使用了HttpServerUtility.Execute进行页面内跳转,就不使用我们通过路由设置的HttpHandler(也就是HttpContent.RemapHandlerInstance属性),如果没有跳转,就使用,并且优先级是第一的,只有当不设置任何基于Route的HttpHandler,才走剩余的匹配规则(也就是之前ASP.NET默认的按照扩展名类匹配的,这部分和我们关系不大就不做详细分析了)。

好了,知道了UrlRouteModuel的大概机制,我们再回头看看如何通过Route/RouteData/RouteCollection/IRouteHandler这几个类来实现动态注册Route规则的,先来看Route的代码:

[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public class Route : RouteBase
{
public Route(string url, IRouteHandler routeHandler)
{
Url = url;
RouteHandler = routeHandler;
} public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) {
Url = url;
Defaults = defaults;
Constraints = constraints;
RouteHandler = routeHandler;
} //省略部分代码
public override RouteData GetRouteData(HttpContextBase httpContext)
{
// Parse incoming URL (we trim off the first two chars since they're always "~/")
string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring() + httpContext.Request.PathInfo; RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults); if (values == null)
{
// If we got back a null value set, that means the URL did not match
return null;
} RouteData routeData = new RouteData(this, RouteHandler); // Validate the values
if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) {
return null;
} // Copy the matched values
foreach (var value in values) {
routeData.Values.Add(value.Key, value.Value);
} // Copy the DataTokens from the Route to the RouteData
if (DataTokens != null) {
foreach (var prop in DataTokens) {
routeData.DataTokens[prop.Key] = prop.Value;
}
}
return routeData;
}
}

Route代码提供了一系列的构造函数重载(我们这里只列出了两个),构造函数主要是传入URL和对应的IRouteHandler实例以及约束规则(比如正则等),然后提供了一个最重要的GetRouteData方法,用于将Route自身和IRouteHandler组装成RouteData,然后返回(中途也会验证相应的约束条件,比如是否符合某个正则表达式),RouteData类本身没有什么逻辑,只是暴露了Route和RouteHandler属性。

我们再来看RouteCollection,该类保存了所有的Route规则(即URL和对应的IRouteHandler),通过静态属性RouteTable.Routes来获取RouteCollection实例,通过UrlRoutingModule里暴露的RouteCollection属性我们可以验证这一点:

public RouteCollection RouteCollection {
get {
if (_routeCollection == null) {
_routeCollection = RouteTable.Routes;
}
return _routeCollection;
}
set {
_routeCollection = value;
}
}

还有一个需要注意的,RouteHandler继承的IRouteHandler的代码:

public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}

该代码只提供了一个GetHttpHandler方法,所有实现这个接口的类需要实现这个方法,MVCHandler就是这么实现的(下一章节我们再细看)。

至此,我们应该有一个清晰的认识了,我们通过全局静态属性集合(RouteTable.Routes)去添加各种各样的Route(但应该在HttpModule初始化周期之前),然后通过UrlRoutingModule负责注册Route以及对应的IRouteHandler实例(IRouteHandler实例可以通过GetHttpHandler获取IHttpHandler),最终实现根据不同URL来接管不同的HttpHandler。

MVC正是利用HttpApplication创建的周期(Application_Start方法)来添加了我们所需要的Route规则,当然在添加规则的时候带上了MVCHandler这个重要的HttpHandler,

代码如下:

protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
} public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

MapRoute方法是一个扩展方法,通过该扩展方法注册Route是个不错的方法,下一章节,我们讲讲解MVC是如何注册自己的MVCRouteHandler实例以及如何实现MVCHandler的调用的。

参考资料:

http://www.cnblogs.com/me-sa/archive/2009/06/01/MVCLifecycle.html

http://www.cnblogs.com/zhaoyang/archive/2011/11/16/2251200.html

同步与推荐

本文已同步至目录索引:MVC之前的那点事儿系列

MVC之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

MVC之前的那点事儿系列(8):UrlRouting的理解的更多相关文章

  1. MVC之前的那点事儿系列(10):MVC为什么不再需要注册通配符(*.*)了?

    文章内容 很多教程里都提到了,在部署MVC程序的时候要配置通配符映射(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前确实应该这么多,但是.NET4.0之后已经不要再费事了 ...

  2. MVC之前的那点事儿系列(6):动态注册HttpModule

    文章内容 通过前面的章节,我们知道HttpApplication在初始化的时候会初始化所有配置文件里注册的HttpModules,那么有一个疑问,能否初始化之前动态加载HttpModule,而不是只从 ...

  3. MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的?

    文章内容 上个章节我们讲到了,可以在HttpModules初始化之前动态添加Route的方式来自定义自己的HttpHandler,最终接管请求的,那MVC是这么实现的么?本章节我们就来分析一下相关的M ...

  4. MVC之前的那点事儿系列(7):WebActivator的实现原理详解

    文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在Http ...

  5. MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)

    文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class ...

  6. MVC之前的那点事儿系列(4):Http Pipeline详细分析(上)

    文章内容 继续上一章节的内容,通过HttpApplicationFactory的GetApplicationInstance静态方法获取实例,然后执行该实例的BeginProcessRequest方法 ...

  7. MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)

    文章内容 话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口 ...

  8. MVC之前的那点事儿系列(2):HttpRuntime详解分析(上)

    文章内容 从上章文章都知道,asp.net是运行在HttpRuntime里的,但是从CLR如何进入HttpRuntime的,可能大家都不太清晰.本章节就是通过深入分析.Net4的源码来展示其中的重要步 ...

  9. MVC之前的那点事儿系列(1):进入CLR

    MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...

随机推荐

  1. 【单页应用】view与model相关梳理

    前情回顾 根据之前的学习,我们形成了一个view与一个messageCenterview这块来说又内建了一套mvc的东西,我们这里来理一下首先View一层由三部分组成:① view② dataAdpt ...

  2. Bootstrap之栅格系统

    bootstrap 移动优先 中文官网  http://www.bootcss.com/ 1.基本模板 <!DOCTYPE html> <html lang="en&quo ...

  3. [python]初试页面抓取——抓取沪深股市交易龙虎榜数据

    [python]抓取沪深股市交易龙虎榜数据 python 3.5.0下运行 没做自动建立files文件夹,需要手动在py文件目录下建立files文件夹后运行 #coding=utf-8 import ...

  4. JavaScript时间处理之几个月前或几个月后的指定日期

    在平常项目开发过程中,经常会遇到需要在JavaScript中处理时间的情况,无非两种(1,逻辑处理  2,格式转换处理).当然要说相关技术博,园子里闭着眼睛都能抓一把,但是我要做的是:既然有幸被我碰到 ...

  5. ArcSDE安装注意事项(二)

    上次安装的arcsde9.3后尝试往本机sde数据库中导入一个区局的数据库,但是每次都是导到一半就卡死不动,也没有报任何的错误,因此安装了arcsde9.3 sp2补丁,安装补丁后连接sde(非直连方 ...

  6. 在UTF-8中,一个汉字为什么需要三个字节?

    UNICODE是万能编码,包含了所有符号的编码,它规定了所有符号在计算机底层的二进制的表示顺序.有关Unicode为什么会出现就不叙述了,Unicode是针对所有计算机的使用者定义一套统一的编码规范, ...

  7. 学习Maven之Maven Enforcer Plugin

    1.Maven Enforcer plugin是什么鬼? 在说这个插件是什么前我们先思考这么一个问题:当我们开发人员进入项目组进行开发前,要准备开发环境,而领导总是会强调工具的统一,编译环境的统一.比 ...

  8. Android 四大组件之再论service

    service常见的有2种方式,本地service以及remote service. 这2种的生命周期,同activity的通信方式等,都不相同. 关于这2种service如何使用,这里不做介绍,只是 ...

  9. SqlServer--delete、truncate 、Drop删除表的区别

    --删除表中的全部数据,自动编号不清零. --1.delete from Biao --删除表中的全部数据,自动编号清零. --2.truncate table Biao --truncate特点: ...

  10. 监控mysql各种选项

    安装mysql之后,需要对mysql服务进行监控.   nagios开源自带的check_mysql 对 mysql 的slave 机监控倒是不错.但是对数据库主机监控就略显不足了.   使用一个监控 ...