好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

路由模块如何工作

路由模块如何工作

摘要: 上文分析了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方法的,下文再介绍。

 

分类:  Web开发

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于路由模块如何工作的详细内容...

  阅读:41次