[转]深入ASP.NET MVC之二:路由模块如何工作

本文转自: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之二:路由模块如何工作

时间: 2024-08-27 03:14:34

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

ASP.NET MVC 视图(二)

ASP.NET MVC 视图(二) 前言 上篇中对于视图引擎只是做了简单的演示,对于真正的理解视图引擎的工作过程可能还有点模糊,本篇将会对由MVC框架提供给我们的Razor视图引擎的整个执行过程做一个粗略的讲解,目的在于让大家对Razor视图引擎的执行过程留个印象以便联想的思考到视图引擎的作用以及视图在MVC框架中的表示. ASP.NET MVC 视图 自定义视图引擎 Razor视图引擎执行过程 Razor视图的依赖注入.自定义视图辅助器 分段.分部视图的使用 Razor语法.视图辅助器 Raz

ASP.NET MVC路由(二)

 ASP.NET MVC路由(二) 前言 在上一篇中,提及了Route.RouteCollection对象的一些信息,以及它们的结构所对应的关系.按照处理流程走下来还有遗留的疑问没有解决这个篇幅就来讲解一下. URL规则的生成 Url规则看名字挺吓唬人的,其实就是根据我们自定义的Url来解析出一个模式,然后等待请求的Url来的时候,跟我们定义的模式进行匹配(如下图).这是后续的内容. 在上篇中说到URL规则的定义是在Route对象中的,下面来讲解在Route对象中怎么根据用户注册的URL转变成U

ASP.NET MVC 控制器(二)

ASP.NET MVC 控制器激活(二) 前言 在之前的篇幅中,用文字和图像来表示了控制器的激活过程,描述的角度都是从框架默认实现的角度去进行描述的,这样也使得大家都可以清楚的知道激活的过程以及其中涉及到的对象模型,今天的篇幅就是在激活的过程中,框架提供了哪些可注入点,站在一个使用者的角度来进行描述. 激活控制器-注入点入口 如上图,这是上个篇幅中描述的控制器激活过程图,这里引用过来是怕有的朋友忘记了前面的所说和没看过前面篇幅的朋友. 就从默认控制器工厂的实现来看,在CreateControll

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

在上一篇"ASP.NET MVC异步验证是如何工作的02,异步验证表单元素的创建"中了解了ASP.NET异步验证是如何创建表单元素的,本篇体验jquery.validate.unobtrusive.js异步验证的全过程. 在jquery.validate.unobtrusive.js文件的尾部看到了如下的一个调用: $(function () { $jQval.unobtrusive.parse(document); }); 可见,通过把当前页的document对象传给$jQval.u

ASP.NET MVC进阶二

一.数据验证 数据验证的步骤 在模型类中添加与验证相关的特性标记 在客户端导入与验证相关的js文件和css文件 使用与验证相关的Html辅助方法 在服务器端判断是否通过服务器端验证 常用的验证标记 Required:非空验证 StringLength:验证字符串的长度 RegularExpression:正则表达式验证 Compare:比较两个字段的值是否相等 Range:范围验证 Remote:服务器验证(需要在controller中编写返回值为JsonResult的Action) 自定义验证

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

前文简介:我们抽象类路由规则的对象,RouteBase是路由对象的抽象基类,ASP.NET 的路由系统中有唯一一个从RouteBase继承的路由对象,那就是Route类型了.我们注册了路由对象Route,UrlRoutingModule截获请求,把当前请求的Url地址和RouteTable路由表中注册的路由对象一个一个的比较,如果没有找到就返回Null,请求就到此终止了.如果有匹配的路由对象,选择第一个匹配的Route对象,并且根据该Route对象,生成了路由数据RouteData对象,本对象是

ASP.NET MVC学习二之 Controller

一:简单理解Controller       新建controller必须以命名+controller结尾,controller的职责是负责与Model进行交换并将数据传递给view视图显示给用户 简单的控制器: public class HomeController : Controller { // // GET: /Test/ public ActionResult Index() { return View(); } }   这里,对其进行简单的修改 public string Inde

ASP.NET MVC 路由(二)

position:static(静态定位) 当position属性定义为static时,可以将元素定义为静态位置,所谓静态位置就是各个元素在HTML文档流中应有的位置 podisition定位问题.所以当没有定义position属性时,并不说明该元素没有自己的位置,它会遵循默认显示为静态位置,在静态定位状态下无法通过坐标值(top,left,right,bottom)来改变它的位置. position:absolute(绝对定位) 当position属性定义为absolute时,元素会脱离文档流

ASP.NET MVC 视图(四)

ASP.NET MVC 视图(四) 前言 上篇对于利用IoC框架对视图的实现进行依赖注入,最后还简单的介绍一下自定义的视图辅助器是怎么定义和使用的,对于Razor语法的细节和辅助器的使用下篇会说讲到,本篇来讲解一下视图中的分段概念.和分部视图的使用. ASP.NET MVC 视图 自定义视图引擎 Razor视图引擎执行过程 Razor视图的依赖注入.自定义视图辅助器 分段.分部视图的使用 Razor语法.视图辅助器 分段.分部视图的使用 分段的使用 在ASP.NET MVC框架的Razor引擎中