asp.net mvc源码分析 - 路由注册


前言:

mvc在beta版时就开始接触了,通过博客园里很多人的分享很学到很多,在这里非常感谢他们,mvc很灵活扩展点很多。但如果没有深入了解其源码实现过程,只通一些扩展点文章了解如何扩展,会存在盲区,就是不知道为什么可以这样做。想要加深了解,读熟源码是非常重要的,只有通过其源码了解来龙去脉,才能方便的用自己的方式去扩展,以下是我以前的一次读mvc源码过程记录,很乱,这回算是整理并回顾下。

此文适合己了解asp.net mvc基本流程,想加深认识asp.net mvc的同志,是基于mvc 2.0 的,比较早了,但我觉得很多东东在现在还是差不多的,可供学习参考。如果有讲的不对的地欢迎给我指正。

 

初用mvc的朋友是否对添加路由规则有点迷茫,他具体是怎么来映射到控制器的。又是怎么来生成url的,要怎么合理的添加路由规则。
mvc默认给了我们很多约定,比如:你的视图文件必须放在 Views/xxx/ 文件夹里面,建一个 Area ,默认给建一个 Areas/xxx/..目录。
然后 AreaRegistration.cs 成为该area的路由注册,这里注册路由是怎么被 Global.asax 里的 AreaRegistration.RegisterAllAreas() 执行的。
是否可以把所有area路由集中放到一个地方按你定制的代码注册。是否可以把 Controller 放到别的地方去,是否想把所有 area 里面的 view 拿出来放一个专门存放 view 的项目中去?...此处省略109.5个字。
在框架各种约定下,有时会觉得不爽,不能自己随意组识自己的文件,但如果你细读源码,就发会现,其实所有的一切,都可以自己定制的。

 


必备 Reflector 等源码查看工具。

 

Routing - 路由注册(严格说Routing不只属于mvc,但我这里当作是mvc源码一部分来讲了)
mvc所有的请求都是通过路由规则去映射的,所以mvc的头等大事就是路由规则的注册,也是asp.net mvc第一件要做的事,因为规则确定了,才能知道当前请求应该映射到哪个Controller的哪个Action。

 

规则的注册是在 Global.asax 的 Application_Start 事件里注册,以下是默认的路由注册代码:

View Code
     public  class MvcApplication : System.Web.HttpApplication
    {
         public  static  void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute( " {resource}.axd/{*pathInfo} ");

            routes.MapRoute(
                 " Default "//  Route name
                 " {controller}/{action}/{id} "//  URL with parameters
                 new { controller =  " Home ", action =  " Index ", id = UrlParameter.Optional }  //  Parameter defaults
            );
        }

         protected  void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
        }
    }

默认在Application_Start里调用了两个方法。AreaRegistration.RegisterAllAreas() 和 RegisterRoutes(RouteTable.Routes)
前者是注册所有的Area路由规则,area规则通常在添加area后自动产生,如Areas/xxx/xxxAreaRegistration.cs,后者是调用了当前的静态方法,注册了当前非area的路由,可以通过源码看下RegisterRoutes(RouteTable.Routes),了解mvc路由的注册。
这个方法传入了 RouteTable.Routes 这个属性,我们可以通过 Reflector 去查看 RouteTable 类。在 System.Web.Routing.dll 里面, 

View Code
public  class RouteTable
{
     //  Fields
     private  static RouteCollection _instance;

     //  Methods
     static RouteTable();
     public RouteTable();

     //  Properties
     public  static RouteCollection Routes {  get; }
}

静态构造及Routes属性。

static RouteTable()
{
    _instance = new RouteCollection();
}
public static RouteCollection Routes
{
    get
    {
        return _instance;
    }
}

通过上面的源码可以看到, RouteTable ,只有一个职责,就是构建一个单例静态 RouteCollection 类,这个类是用来保存路由规则(Route)的集合。RouteTable.Routes 属性就是指向这个静态 RouteCollection 实例,上面就是传入了这个实例,并调用其MapRoute方法添加路由规则。

View Code
public  static  void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute( " {resource}.axd/{*pathInfo} ");

            routes.MapRoute(
                 " Default "//  Route name
                 " {controller}/{action}/{id} "//  URL with parameters
                 new { controller =  " Home ", action =  " Index ", id = UrlParameter.Optional }  //  Parameter defaults
            );
        }

我们再查看 RouteCollection.MapRoute(... 添加规则。当看到这个类的源码时,并没有发现有 MapRoute 这些注册方法。
通过vs里跟综可以发现他是在 System.Web.Mvc.RouteCollectionExtensions 这个类的扩展方法里的。可能是 Routing 现在作为一个单独的组件,考滤到可以在不同的需求里扩展不同的注册规则方式。

View Code
public  static  class RouteCollectionExtensions
{
     //  Methods
     private  static RouteCollection FilterRouteCollectionByArea(RouteCollection routes,  string areaName,  out  bool usingAreas);
     public  static VirtualPathData GetVirtualPathForArea( this RouteCollection routes, RequestContext requestContext, RouteValueDictionary values);
     public  static VirtualPathData GetVirtualPathForArea( this RouteCollection routes, RequestContext requestContext,  string name, RouteValueDictionary 

values);
     internal  static VirtualPathData GetVirtualPathForArea( this RouteCollection routes, RequestContext requestContext,  string name, RouteValueDictionary 

values,  out  bool usingAreas);
     public  static  void IgnoreRoute( this RouteCollection routes,  string url);
     public  static  void IgnoreRoute( this RouteCollection routes,  string url,  object constraints);
     public  static Route MapRoute( this RouteCollection routes,  string name,  string url);
     public  static Route MapRoute( this RouteCollection routes,  string name,  string url,  object defaults);
     public  static Route MapRoute( this RouteCollection routes,  string name,  string url,  string[] namespaces);
     public  static Route MapRoute( this RouteCollection routes,  string name,  string url,  object defaults,  object constraints);
     public  static Route MapRoute( this RouteCollection routes,  string name,  string url,  object defaults,  string[] namespaces);
     public  static Route MapRoute( this RouteCollection routes,  string name,  string url,  object defaults,  object constraints,  string[] namespaces);

     //  Nested Types
     private  sealed  class IgnoreRouteInternal : Route
    {
         //  Methods
         public IgnoreRouteInternal( string url);
         public  override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues);
    }
}

上面可以看到有IgnoreRoute 方法,默认的第一条路由就是通过这个方法注册,如: routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 这个。
这也是一个注册路由方法,这样的规则是用来干嘛?这里先不作分析,等下几篇会详细再讲。可以看到上面己经有MapRoute
这个注册方法了,都是一些重载,我们点开最后一个看具体实现。

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

上面的代创建了一个 Route ,Route就是一条具体的路由,下次再单独详细Route分析这个类,因为Route是路由映射中一个很重要的成员。这里我们只要知道Route就是具体的一条路由规则,通过包装传入的参数后被添加到了RouteTable.Routes(全局路由集合),这是在整个网站的生命周期里一直随时可以访问路由集合。为后面的请求或是生成url起着重要的作用。这些后续会去分析。

 

我们可以在Application_BeginRequest打印所有己注册的规则url,同时以后我们可以这样去测试url的有效性及匹配情况。这些后续会提到。

         protected  void Application_BeginRequest()
        {
             RouteCollection routes = RouteTable.Routes;
             foreach (Route route  in routes)
            {
                Response.Write(route.Url +  " <br /> ");
            }
        }

 

上面就是默认非area的路由注册,我们再看下area的注册又是如何进行的。

自从asp.net mvc2.0 就引入了area,可以让我们把不同的模块分格开来,我们默认添加一个 area,自动在 Areas 文件下创建。然后又为每个area添加了一个注册规则的文件,如:Areas/Admin/AdminAreaRegistration.cs

那么这里面的规则又是如何被注册到的呢,这里的又和前面的有什么区别,可以把area的路由注册放到 Application_Start 里去注册吗?

我们在Application_Start里看到先是调用了AreaRegistration.RegisterAllAreas(),这个就是先注册所有area的路由。我们去看下这个方法。

 

AreaRegistration 这个类是在 System.Web.Mvc.dll ,你可以通过下载mvc的源码查看,我这里是用 Reflector 查看,这个类被定义为abstract。实际我们创建的Areas里的xxxAreaRegistration.cs,就是继承这个类,并实现了RegisterArea(AreaRegistrationContext context)  , AreaName { get; }

 AreaRegistration 类

View Code
public  abstract  class AreaRegistration
{
     //  Fields
     private  const  string _typeCacheName =  " MVC-AreaRegistrationTypeCache.xml ";

     //  Methods
     protected AreaRegistration();
     internal  void CreateContextAndRegister(RouteCollection routes,  object state);
     private  static  bool IsAreaRegistrationType(Type type);
     public  static  void RegisterAllAreas();
     public  static  void RegisterAllAreas( object state);
     internal  static  void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager,  object state);
     public  abstract  void RegisterArea(AreaRegistrationContext context);

     //  Properties
     public  abstract  string AreaName {  get; }
}

首先看下他的静态方法先后调用 

View Code
public  static  void RegisterAllAreas()
{
    RegisterAllAreas( null);
}
public  static  void RegisterAllAreas( object state)
{
    RegisterAllAreas(RouteTable.Routes,  new BuildManagerWrapper(), state);
}
internal  static  void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager,  object state)
{
     foreach (Type type  in TypeCacheUtil.GetFilteredTypesFromAssemblies( " MVC-AreaRegistrationTypeCache.xml "new Predicate<Type>(AreaRegistration.IsAreaRegistrationType), buildManager))
    {
        ((AreaRegistration) Activator.CreateInstance(type)).CreateContextAndRegister(routes, state);
    }
}

可以看到最终是是通过TypeCacheUtil.GetFilteredTypesFromAssemblies过滤找所有MVC-AreaRegistrationTypeCache.xml这样一个常量标识的类,而上面的AreaRegistration 里恰好有这个值为 MVC-AreaRegistrationTypeCache.xml 常量,可能是做为识别标识,这部分比较复杂,而且也没太大必要去细做研究。我们只要大概猜到,这里就是取所有继承了AreaRegistration的类(就是我们创建area后自带的注册路由的类)。并创建实例,然后调用 CreateContextAndRegister(routes, state); routes是前讲到的全局路由集合,state,这里上面是传入了null。我们再去看下AreaRegistration的这个CreateContextAndRegister方法。

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

首先是创建了一个 AreaRegistrationContext,这个类我们可以理解为一个打包。就是把 AreaName Routes state这些东东打包,以下构造方法。

View Code
public AreaRegistrationContext( string areaName, RouteCollection routes,  object state)
{
     this._namespaces =  new HashSet< string>(StringComparer.OrdinalIgnoreCase);
     if ( string.IsNullOrEmpty(areaName))
    {
         throw Error.ParameterCannotBeNullOrEmpty( " areaName ");
    }
     if (routes ==  null)
    {
         throw  new ArgumentNullException( " routes ");
    }
     this.AreaName = areaName;
     this.Routes = routes;
     this.State = state;
}

然后如果非null就把空间命名也加入到AreaRegistrationContext的Namespaces属性集合中。
最后把包装好的数据传入调用了 this.RegisterArea(context); 这就是我们 Areas/xxx/xxxAreaRegistration.cs 里实现了AreaRegistration的方法,完成了当前area的路由规则注册。

View Code
     public  class AdminAreaRegistration : AreaRegistration
    {
         public  override  string AreaName
        {
             get
            {
                 return  " Admin ";
            }
        }

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

再看一下这里的RegisterArea里的注册是通过 AreaRegistrationContext 类的 MapRoute 方法了,而不是上篇非area注时册的RouteCollection扩展MapRoute方法进行注册了,我们去看下 AreaRegistrationContext 这个类的MapRoute方法是怎样的。

View Code
public Route MapRoute( string name,  string url,  object defaults,  object constraints,  string[] namespaces)
{
     if ((namespaces ==  null) && ( this.Namespaces !=  null))
    {
        namespaces =  this.Namespaces.ToArray< string>();
    }
     Route route =  this .Routes.MapRoute(name, url, defaults, constraints, namespaces);
    route.DataTokens[ " area "] =  this.AreaName;
     bool flag = (namespaces ==  null) || (namespaces.Length ==  0);
    route.DataTokens[ " UseNamespaceFallback "] = flag;
     return route;
}

看标注的那行。this.Routes ,就是前打包 AreaRegistrationContext 的值,实际还是 RouteTable.Routes,也就是最后还是和非area一样。只是后面多了些操作。就是往 route.DataTokens 属性里,加了两项值。这也是与非area规注注册的一点区别。

route.DataTokens[ " area "] =  this.AreaName;
bool flag = (namespaces ==  null) || (namespaces.Length ==  0);
route.DataTokens[ " UseNamespaceFallback "] = flag;

这里我们基本可以确定,只要 Route 的 DataTokens 属性里,有 area 这项值,就是一个area规则。
UseNamespaceFallback 这项值我以前的记录里没有,我记得以前整个mvc流程完了,好像也没太看到这个属性用在哪,这里先不管,不加也一样。分析到再说吧。我们现在可以试一下,把area 里的 AdminAreaRegistration.cs 删了。把area的路由规则放在Application_Start()里去注册,如下:

View Code
         public  static  void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute( " {resource}.axd/{*pathInfo} ");

             // 这里是area
            Route route = routes.MapRoute(
                 " Admin_default ",
                 " Admin/{controller}/{action}/{id} ",
                 new { controller =  " Home ", action =  " Index ", id = UrlParameter.Optional },
                 new  string[] {  " MVCTest.Areas.Admin.Controllers " }
            );
            route.DataTokens[ " area "] =  " Admin ";
             // 这里是area

            routes.MapRoute(
                 " Default "//  Route name
                 " {controller}/{action}/{id} "//  URL with parameters
                 new { controller =  " Home ", action =  " Index ", id = UrlParameter.Optional },  //  Parameter defaults
                 new  string[] {  " MVCTest.Controllers " }
            );
        }

         protected  void Application_Start()
        {
             // AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
        }

然后访问 /admin/home 我们可以发现可以正常请求area的。
这样如果喜欢把规则放到一起注册的朋友可以这样了,不必再在每个 xxxAreaRegistration.cs 文件里注册了。
或者用你自己的方式,反正简单的说,注册路由规则,就是往 RouteCollection 集合里添加 Route,而area的注册,就是 Route的DataTokens["area"] = "Admin"; 指定其area名称。而当 DataTokens 含有 area 值,mvc后面生成url和指定view都会用到这个值,后续我们会看到。

 

结尾语:
本文会跟着mvc的整个执行流程依次把多数源码分析下去。其中结合了自己的一些见解,如果有什么不正确的地方还请指出。
转载请注明出处,谢谢。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2024 CODEPRJ.COM