本文转自:http://www.cnblogs.com/yinzixin/archive/2012/11/05/2754483.html

摘要: 上文分析了UrlRouting模块何时会被触发,本文重点分析路由模块是如何工作,以及如何利用路由模块实现Area。

先看路由模块的PostResolveRequestCache事件中被触发的方法:

        public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData == null)
{
return;
}
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
}
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("UrlRoutingModule_NoHttpHandler"), new object[]
{
routeHandler.GetType()
}));
}
if (!(httpHandler is UrlAuthFailureHandler))
{
context.RemapHandler(httpHandler);
return;
}
if (FormsAuthenticationModule.FormsAuthRequired)
{
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
return;
}
throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
}

这个方法做的工作还是比较清晰的,首先从RouteCollection中获得RouteData,从RouteData中获得RouteHandler,从RouteHandler获得httpHandler,最后调用RemapHandler将控制权交给httpHandler。UrlRoutingModule是System.Web中的通用的路由模块,并不仅限于给ASP.NET MVC使用,这里处理的今本都是针对抽象接口来处理的。后文会介绍ASP.NET MVC是如何利用这个模块实现了URL到controller/action的映射的。

RouteCollection是一张路由表,里面包括了很多路由规则(RouteBase),RouteData则是解析好的路由,里面包括了Key-Value对的路由信息,一个routehandler。RouteCollection的GetRouteData方法,是找到符合当前请求的路由规则的RouteData,其内部实现就是遍历所有的路由规则,调用RouteBase的GetRouteData方法,返回第一个非空的RouteData。IRouteHandler 只有一个方法,

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

但是他是从路由模块完成最重要的工作之一,等到其他模块执行完毕之后,将由这个IHttpHandler(如果其他模块没有更改这个handler)来完成接下来的请求。得到合适的HttpHandler之后,调用了HttpContext的RemapHandler方法,这个方法的核心是

this._remapHandler = handler;

此时尚没有真正的转交控制权,当前的handler还是global.asax中的类,在上文中有介绍到在初始化一个请求的时候,由一个StepManager来初始化需要触发的step,其中有:

 HttpApplication.IExecutionStep step = new HttpApplication.MaterializeHandlerExecutionStep(application);
application.AddEventMapping("ManagedPipelineHandler", RequestNotification.MapRequestHandler, false, step);

因此在MapRequestHandler的时候,会触发MaterializeHandlerExecutionStep的Execute方法,其中主要的代码是:

if (context.RemapHandlerInstance != null)
{
IIS7WorkerRequest.SetScriptMapForRemapHandler();
context.Handler = context.RemapHandlerInstance;
}

在MapRequestHandler之后调用RemapHandler会导致异常,原因是改变handler的时机是在MapRequestHandler。

下面看ASP.MVC框架是如何使用这个routing module的。MVC在RouteCollectionExtensions这个类中定义了一系列扩展方法,扩展了原有的RouteCollection类。RouteCollection类是支持ASP.NET Webform的路由模块,可以用MapPageRoute方法将url映射到aspx文件上。RouteCollectionExtensions的核心方法是:

        public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
//…
            Route route = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
if ((namespaces != null) && (namespaces.Length > 0)) {
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}

注意Route构造函数中的第二个参数,这是一个MvcRouteHandler,这个handler也就是RouteData中的RouteHandler的值,其实现为:

        protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}

暂时忽略这里的SessionStateBehavior,可以看到最终的httpHandler是MvcHandler。

综上,routing module在PostResolveRequestCache被触发,获得RouteCollection中的Route(System.Web.Routing),解析Route数据获得RouteData,MVC框架设置RouteData中的RouteHandler为MvcRouteHandler,MvcRouteHandler的GetHttpHandler方法返回的是一个MvcHandler,这个handler最终在MapRequestHandler事件触发的时候接过request处理流程,开始处理请求,它将利用RouteData中解析好的值去触发controller/action,这个下文再介绍。ASP.NET MVC的路由模块主要是用的.NET中的System.Web.Routing.Route类来实现的,Route类的主要工作是解析路由规则,得到一个Key-Value对的序列。这是一个单纯而又复杂的过程,这里不分析其实现。

下面简单介绍下Route类所表示的路由规则。根据上面的代码(Route的构造函数)可以看到,一条路由规则包括4个部分:Url模式,一组默认值,约束,和DataToken,DataToken是一些额外的信息,Route本身不会使用他们,但是可以提供给其他代码使用,下面会说明。 Url模式是一个字符串,包括一些固定的字符字面量和占位符,占位符由一对花括号表示{ } 。例如:

Route definition

Example of matching URL

{controller}/{action}/{id}

/Products/show/beverages

{table}/Details.aspx

/Products/Details.aspx

blog/{action}/{entry}

/blog/show/123

{reporttype}/{year}/{month}/{day}

/sales/2008/1/5

{locale}/{action}

/US/show

{language}-{country}/{action}

/en-US/show

当一个url满足一个url模式的时候,Route模块会将其解析后的数据放在RouteData.Value中,例如第一条url解析之后就是如下的K-V对序列:

controller:Products

action:show

id:beverages

默认值不讨论了。

约束是一个匿名对象,例如

routes.MapRoute(
"BlogArchive",
"Archive/{entryDate}",
new { controller = "Blog", action = "Archive" },
new { entryDate = @"d{2}-d{2}-d{4}" }
);

表示对entryDate添加约束。属性的值如果是一个字符串,则代表一个正则表达式,除此之外,还可以是实现IRouteConstraint接口的对象,从而实现自定义的约束。

DataToken可以放一些自定义的数据,例如ASP.NET MVC就在其中放入了Namespace.Namespace用来区分同名的Controller,常用在Area中。Area可以将一个大型的网站划分为相对独立的区域。MSDN上的这篇文章介绍了如何创建一个Area。VS在创建一个Area的时候创建了如下结构的目录和文件:

其中Model-Controller-View的结构是和整个站点一致的,还有一个独立的Web.config文件。关键还多了一个AdminAreaRegistration文件,这个自动生成的类是用来注册Area的路由的:

        public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}

这个方法和普通的注册路由非常相似,看下AreaRegistrationContext.MapRoute的实现:

        public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces) {
if (namespaces == null && Namespaces != null) {
namespaces = Namespaces.ToArray();
} Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces);
route.DataTokens["area"] = AreaName; // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
// controllers belonging to other areas
bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback; return route;
}

最终还是和普通的MapRoute一样,创建了一个Route对象,不同的只是给DataToken添加了area和UseNamespaceFallback属性。下面再看看这里的RegisterArea是如何被调用的,以及参数context是什么。注意到在global.asax中的Application_Start方法中第一行代码就是:

AreaRegistration.RegisterAllAreas();

看这个方法的实现:

internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state) {
List<Type> areaRegistrationTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(_typeCacheName, IsAreaRegistrationType, buildManager);
foreach (Type areaRegistrationType in areaRegistrationTypes) {
AreaRegistration registration = (AreaRegistration)Activator.CreateInstance(areaRegistrationType);
registration.CreateContextAndRegister(routes, state);
}
}

首先枚举出当前AppDomain中所有的AreaRegistration的子类,关于TypeCacheUtil,下文还会出现,暂不作介绍。然后调用CreateContextAndRegister方法,这个方法的代码如下:

        internal void CreateContextAndRegister(RouteCollection routes, object state) {
AreaRegistrationContext context = new AreaRegistrationContext(AreaName, routes, state);
string thisNamespace = GetType().Namespace;
if (thisNamespace != null) {
context.Namespaces.Add(thisNamespace + ".*");
}
RegisterArea(context);
}

主要做了两部分事情,首先创建了AreaRegistrationContext,并且把AreaRegistration子类的命名空间加入到context的Namesapces属性中,最终调用了我们一开始谈到的自动生成的RegisterArea方法。因此,在Application_Start一开始,每个AreaRegistration的子类的RegisterArea方法都会被调用,这个方法的效果是在全局路由表中添加一条Area的路由规则,Area的路由规则默认的把当前的AreaRegistration子类的命名空间加到DataToken中,另外还有area和UseNamespaceFallback属性也加入了DataToken。至于这些值如何被使用,MVC框架是如何实现调用合适的Controller的Action方法的,下文再介绍。

[转]深入ASP.NET MVC之二:路由模块如何工作的更多相关文章

  1. ASP.NET MVC 视图(二)

    ASP.NET MVC 视图(二) 前言 上篇中对于视图引擎只是做了简单的演示,对于真正的理解视图引擎的工作过程可能还有点模糊,本篇将会对由MVC框架提供给我们的Razor视图引擎的整个执行过程做一个 ...

  2. ASP.NET MVC 路由(二)

     ASP.NET MVC路由(二) 前言 在上一篇中,提及了Route.RouteCollection对象的一些信息,以及它们的结构所对应的关系.按照处理流程走下来还有遗留的疑问没有解决这个篇幅就来讲 ...

  3. ASP.NET MVC异步验证是如何工作的03,jquery.validate.unobtrusive.js是如何工作的

    在上一篇"ASP.NET MVC异步验证是如何工作的02,异步验证表单元素的创建"中了解了ASP.NET异步验证是如何创建表单元素的,本篇体验jquery.validate.uno ...

  4. ASP.NET MVC异步验证是如何工作的02,异步验证表单元素的创建

    在上一篇"ASP.NET MVC异步验证是如何工作的01,jQuery的验证方式.错误信息提示.validate方法的背后"中,了解了jQuery如何验证,如何显示错误信息,本篇要 ...

  5. ASP.NET MVC进阶二

    一.数据验证 数据验证的步骤 在模型类中添加与验证相关的特性标记 在客户端导入与验证相关的js文件和css文件 使用与验证相关的Html辅助方法 在服务器端判断是否通过服务器端验证 常用的验证标记 R ...

  6. 白话ASP.NET MVC之二:Controller激活系统的概览

    前文简介:我们抽象类路由规则的对象,RouteBase是路由对象的抽象基类,ASP.NET 的路由系统中有唯一一个从RouteBase继承的路由对象,那就是Route类型了.我们注册了路由对象Rout ...

  7. ASP.NET MVC教程二:ASP.NET MVC应用程序结构详解

    在上一篇文章中,讲解了一些MVC的概念,并且创建了第一个ASP.NET MVC项目,这篇文章将讲解ASP.NET MVC程序中的代码解构,新创建的MVC应用程序解构如下图所示: 一.App_Data ...

  8. 跟我学ASP.NET MVC之二:第一个ASP.NET MVC程序

    摘要: 本篇文章带你一步一步创建一个简单的ASP.NET MVC程序.  创建新ASP.NET MVC工程 点击“OK”按钮后,打开下面的窗口: 这里选择“Empty”模板以及“MVC”选项.这次不创 ...

  9. ASP.NET MVC 4 (二)控制器

    MVC中控制器负责处理请求,由它操作数据模型,最后返回视图给用户. IController接口 所有的控制器类以Controller结尾,必须实现System.Web.Mvc.IController接 ...

随机推荐

  1. python之模块随笔记-os

    操作系统模块:import os os.remove() 删除文件 os.unlink() 删除链接文件 os.rename() 重命名文件 os.listdir() 列出指定目录下所有文件 os.c ...

  2. 易碎的鸟蛋 概率DP

    1007: 易碎的鸟蛋 时间限制: 1 Sec  内存限制: 128 MB提交: 396  解决: 80[提交][状态][讨论版] 题目描述 你们知道吗?西电的跳楼塔上面有一个鸟巢.某SXBK的教授对 ...

  3. POJ 1064_Cable master

    题意: 给定一系列长度的电缆,将他们切成K条相等长度的电缆,问每条最长有多长? 分析: 对长度进行二分,判断能否满足K条. 二分搜索: 在求解最大化或最小化问题中,能够比较简单的判断条件是否满足,那么 ...

  4. sql语句执行的很慢

    一个 SQL 执行的很慢,我们要分两种情况讨论: 1.大多数情况下很正常,偶尔很慢,则有如下原因 (1).数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘. (2).执行的时候,遇到锁, ...

  5. 洛谷 P1318 积水面积

    P1318 积水面积 题目描述 一组正整数,分别表示由正方体迭起的柱子的高度.若某高度值为x,表示由x个正立方的方块迭起(如下图,0<=x<=5000).找出所有可能积水的地方(图中蓝色部 ...

  6. JDBC的数据类型

    以下内容引用自http://wiki.jikexueyuan.com/project/jdbc/data-types.html: JDBC驱动程序在将Java数据类型发送到数据库之前,会将其转换为相应 ...

  7. C# 图片识别(支持21种语言)转

    来自:http://www.cnblogs.com/stone_w/archive/2011/10/08/2202397.html 图片识别的技术到几天已经很成熟了,只是相关的资料很少,为了方便在此汇 ...

  8. golang 查询数据库操作

    SQL.Open only creates the DB object, but dies not open any connections to the database. If you want ...

  9. Windows Server2008 R2 设置NAT 让Hyper-V连接Internet

    1.添加虚拟网卡,设置为内部,并且固定IP地址192.168.1.1 255.255.255.0 此为内网网卡 2.添加服务器角色:DHCP服务器,DNS服务器,网络策略和访问服务 3."网 ...

  10. 用vhd挂载并安装win7且建立分差vhd

    准备:硬盘分区激活第一个分区; imagex.exe; install.wim; winpe boot pc 1.cmd命令下,创建主vhd      (1)diskpart       (打开dis ...