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的整个执行流程依次把多数源码分析下去。其中结合了自己的一些见解,如果有什么不正确的地方还请指出。
转载请注明出处,谢谢。

 

posted @ 2012-08-29 10:53  lindaohui  阅读(3085)  评论(7编辑  收藏  举报