区域路由的注册机制

这些天一直在学习MVC的源码,深刻学习后,发现本身不懂的真的是愈来愈多,为何会有上一篇博客呢?在学习DefaultControllerFactory提供控制器的过程当中,先是被路由中的MS_DirectRouteMatches这个Toeken值困惑,我知道他表示一个特性路由,但我想它是哪里来的呢?因而就有了上一篇博文,可是这两天就是和路由干上了,我知道还有一个区域路由的注册工做,仍是Application_Start的的第一行代码,因而乎就有了这一篇博文。小程序

AreaRegistration.RegisterAllAreas()

 咱们新建一个名称为Admin的Area,VS生成下面的代码。缓存

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 }
        );
    }
}

咱们先来看AreaRegistration这个抽象类,实际上,它只有一个核心功能,就是RegisterAllAreas,获取全部继承它的子类类型,而后建立它,在为他建立一个AreaRegistrationContext,在调用它的RegisterArea方法。app

public abstract class AreaRegistration
{
    private const string TypeCacheName = "MVC-AreaRegistrationTypeCache.xml";
    public abstract string AreaName { get; }
    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);
        }
    }
    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);
    }
    public abstract void RegisterArea(AreaRegistrationContext context);
}

为何要有AreaRegistrationContext这个类型呢?假如没有它,AreaRegistration子类建立完成时,就能够直接注册了,咱们的AdminAreaRegistration的RegisterArea方法彻底能够经过RouteCollection再重载一个MapRoute方法用于Area路由的注册。像下面这个样子。ide

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

 这样不是很好么?跟随着源码,详细瞧一瞧这个AreaRegistrationContext性能

AreaRegistrationContext

 这个类本质上只有一个属性,那就是命名空间。学习

public class AreaRegistrationContext
{
    private readonly HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

    public AreaRegistrationContext(string areaName, RouteCollection routes, object state)
    { }

    public string AreaName { get; private set; }

    public ICollection<string> Namespaces
    {
        get { return _namespaces; }
    }

    public RouteCollection Routes { get; private set; }
    public object State { get; private set; }
    public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
    { }
}

 咱们回到核心的RegisterAllAreas方法中。测试

private static bool IsAreaRegistrationType(Type type)
{
    return
        typeof(AreaRegistration).IsAssignableFrom(type) &&
        type.GetConstructor(Type.EmptyTypes) != null;
}
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);
    }
}

经过TypeCacheUtil.GetFilteredTypesFromAssemblies获取出来的类型必须符合IsAreaRegistrationType委托,(AreaRegistration).IsAssignableFrom(type)不难理解,必须是AreaRegistration的子类,那type.GetConstructor(Type.EmptyTypes)呢?其实一开始我也不明白它是什么意思,后来经过Console写了个小程序测试了下。ui

class TA
{
    TA()
    {}
}
class TB
{
    TB()
    {}
    TB(int i)
    {}
}
class TC
{}
class Program
{
    static void Main(string[] args)
    {
        Type ta = typeof(TA);
        var tac = ta.GetConstructor(Type.EmptyTypes);

        Type tb = typeof(TB);
        var tbc = tb.GetConstructor(Type.EmptyTypes);

        Type tc = typeof(TC);
        var tcc = tc.GetConstructor(Type.EmptyTypes);

        Console.WriteLine("类TA :" + (tac != null));
        Console.WriteLine("类TB :" + (tbc != null));
        Console.WriteLine("类TC :" + (tcc != null));
    }
}
View Code

 输出:this

类TA :Falseurl

类TB :False

类TC :True

请按任意键继续. . .

咱们能够明白了,也就是咱们的AdminAreaRegistration不能有构造器(Visual Studio生成的确实没有构造器)。可是这里为何要这样约定呢?确实想不通,咱们先继续回到刚刚的TypeCacheUtil.GetFilteredTypesFromAssemblies方法。首先,会尝试从缓存中获取类型,与往常不一样的是,这里缓存的格式是xml文件,缓存的缘由应该很容易理解,频繁反射会形成性能的影响,改良反射的方式有多种,这里咱们学到了一种,缓存。关于TypeCacheSerializer如何工做和ReadTypesFromCache具体是如何实现的这里就不去看了,主要就是一些关于Stream和XmlDocument这两个类的操做。可是有必要提一下IBuildManager这个接口。在MVC中的实现者是BuildManagerWrapper,内部实际使用的是BuildManager(位于System.Web.Compilation),关于它的详细资料少之又少,只知道主要负责站点的动态编译和程序集的管理。咱们知道能够经过AppDomain来获取应用程序相关的程序集,但这里为何用BuilderManager呢?想必必有什么不一样!

private static IEnumerable<Type> FilterTypesInAssemblies(IBuildManager buildManager, Predicate<Type> predicate)
{
    // Go through all assemblies referenced by the application and search for types matching a predicate
    IEnumerable<Type> typesSoFar = Type.EmptyTypes;

    ICollection assemblies = buildManager.GetReferencedAssemblies();
    foreach (Assembly assembly in assemblies)
    {
        Type[] typesInAsm;
        try
        {
            typesInAsm = assembly.GetTypes();
        }
        catch (ReflectionTypeLoadException ex)
        {
            typesInAsm = ex.Types;
        }
        typesSoFar = typesSoFar.Concat(typesInAsm);
    }
    return typesSoFar.Where(type => TypeIsPublicClass(type) && predicate(type));
}

咱们看到这里用它获取全部的应用程序集。在foreach前打一个断点。借助即时窗口咱们能够和AppDomain获取的程序集进行一个比较。

string[] Arr1 = assemblies.Cast().Select(a=>a.FullName).ToArray();

已计算表达式,表达式没有值

string[] Arr2 = AppDomain.CurrentDomain.GetAssemblies().Select(a=>a.FullName).ToArray();

已计算表达式,表达式没有值

Arr1.Length

36

Arr2.Length

42

string[] Arr3 = Arr2.Except(Arr1).ToArray();

已计算表达式,表达式没有值

Arr3

{string[6]}

    [0]: "System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    [1]: "Microsoft.Build.Utilities.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    [2]: "Microsoft.JScript, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    [3]: "Microsoft.VisualStudio.Web.PageInspector.Runtime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    [4]: "Microsoft.VisualStudio.Web.PageInspector.Tracing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    [5]: "Microsoft.VisualStudio.Debugger.Runtime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

这里列出的几个命名空间我也不熟悉,可是大体能够了解,使用AppDomain返回的程序集是当前AppDomain下全部程序中显示使用过的类型所在的程序集(若是你对AppDomain有了解,但愿不要被我误解),而BuildManager返回的是和程序运行环境甚至配置(调试)相关的程序集,咱们能够这么理解,BuildManager提供更强大的功能,能够负责站点的动态编译和程序集的管理。关于AreaRegistration类型的缓存咱们基本已经了解,拿到全部的AreaRegistration类型后,咱们针对每个进行一次路由配置工做。

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);
    }
}

具体的

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);
}

咱们来思考一下,这个thisNamespace会是什么值呢?因为这里的GetType目标是AdminAreaRegistration,(在我这里)因此是Mvc_Web.Areas.Admin,而后会被添加到这里的AreaRegistrationContext的Namespace属性中,而后调用子类重写的RegisterArea方法,最终添加到RouteCollection中,咱们看最后调用的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[RouteDataTokenKeys.Area] = AreaName;

    bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
    route.DataTokens[RouteDataTokenKeys.UseNamespaceFallback] = useNamespaceFallback;

    return route;
}

最重要的是倒数第二行和倒数第三行,他和控制器的匹配有关,其实根据UseNamespaceFallback这个也很容易理解,若是咱们的AdminAreaRegistration没有命名空间,那就容许它退回(到其余地方找)。

小结

路由这块终于结束了,任重道远啊,鼓励一下本身,加油!!!

相关文章
相关标签/搜索