本文转自: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. HDU——1179 Ollivanders: Makers of Fine Wands since 382 BC.

    Ollivanders: Makers of Fine Wands since 382 BC. Time Limit: 2000/1000 MS (Java/Others)    Memory Lim ...

  2. POJ3977 Subset 折半枚举

    题目大意是给定N个数的集合,从这个集合中找到一个非空子集,使得该子集元素和的绝对值最小.假设有多个答案,输出元素个数最少的那个. N最多为35,假设直接枚举显然是不行的. 可是假设我们将这些数分成两半 ...

  3. zipfile zip

    Python模块学习:zipfile zip文件操作 2015/05/27 · 系列教程 · zipfile 分享到:3 原文出处: DarkBull    最近在写一个网络客户端下载程序,用于下载服 ...

  4. msyql索引详解

    一.mysql查询表索引命令两种形式 1.mysql>SHOW INDEX FROM 'biaoming' 2.mysql>SHOW keys FROM 'biaoming' 运行结果如下 ...

  5. jeasyUI的treegrid批量删除多行

    看上去,javascript的变量类型,也可以分为值类型和引用类型.赋值操作中,值类型,各自独立,互不干涉:引用类型,指针而已,大家指向同一个对象. 为什么这样说呢? 我是从jeasyUI的treeg ...

  6. Spring中定时器实现

    在Spring 中使用Quartz,本文介绍Spring3.0以后自主开发的定时任务工具,spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需 ...

  7. go16---select

    package main /* Channel Channel 是 goroutine 沟通的桥梁, goroutine是通过通信来进行内存的共享, 而不是通过内存的共享来进行通信,通过Channel ...

  8. sql compare options

    sql compare project's options Add object existence checks Use DROP and CREATE instead of ALTER Ignor ...

  9. 在java中除去字符串(String)中的换行字符(\r \n \t)

    我们先来看几个例子: 例1: public class Test { public static void main(String[] args) { String s = "'sds gd ...

  10. 获取view宽高

    在oncreate()中利用view.getWidth()或是view.getHeiht()来获取view的宽和高,看似没有问题,其实他们去得值是0,并不是你想要的结果? 这是为什么呢? 在调用onc ...