MVC 源碼系列之控制器激活(二)之GetControllerType和GetcontrollerInstance

GetControllerType和GetcontrollerInstance

GetControllerType

protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName)
{
    if (requestContext == null)
    {
        throw new ArgumentNullException("requestContext");
    }
    
    if (String.IsNullOrEmpty(controllerName) &&
        (requestContext.RouteData == null || !requestContext.RouteData.HasDirectRouteMatch()))
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
    }

    RouteData routeData = requestContext.RouteData;
    if (routeData != null && routeData.HasDirectRouteMatch())
    {
        return GetControllerTypeFromDirectRoute(routeData);
    }

    // first search in the current route's namespace collection
    object routeNamespacesObj;
    Type match;
    if (routeData.DataTokens.TryGetValue(RouteDataTokenKeys.Namespaces, out routeNamespacesObj))
    {
        IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
        if (routeNamespaces != null && routeNamespaces.Any())
        {
            HashSet<string> namespaceHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
            match = GetControllerTypeWithinNamespaces。
            (routeData.Route, controllerName, namespaceHash);

            // the UseNamespaceFallback key might not exist, in which case its value is implicitly "true"
            if (match != null || false.Equals(routeData.DataTokens[RouteDataTokenKeys.UseNamespaceFallback]))
            {
                // got a match or the route requested we stop looking
                return match;
            }
        }
    }

    // then search in the application's default namespace collection
    if (ControllerBuilder.DefaultNamespaces.Count > 0)
    {
        HashSet<string> namespaceDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
        match = GetControllerTypeWithinNamespaces(routeData.Route, controllerName, namespaceDefaults);
        if (match != null)
        {
            return match;
        }
    }

    // if all else fails, search every namespace
    return GetControllerTypeWithinNamespaces(routeData.Route, controllerName, null /* namespaces */);
}

首先進行了入口的檢查,HasDirectRouteMatch這個方法是用來判斷這個RouteData是否是特性路由?應該是這個做用。如今還沒知道,之後說WebApi的時候可能會說到。全部默認狀況是不會去執行GetControllerTypeFromDirectRoute的方法的。在Route中獲取命名空間,若是設置了NameSpace的話便在這邊取出,而後轉化爲List ,調用了GetControllerTypeWithinNamespaces。若是ControllerBuilder的DefaultNamespaces不爲空,取出來調用GetControllerTypeWithinNamespaces,若是都沒有的話直接調用GetControllerTypeWithinNamespaces,只是namespaces的部分爲空。簡單說,就是將DataTokens中的namespace和ControllerBuilder的DefaultNamespaces取出來調用GetControllerTypeWithinNamespaces。 緩存

private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces)
{
    // Once the master list of controllers has been created we can quickly index into it
    ControllerTypeCache.EnsureInitialized(BuildManager);

    ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
    switch (matchingTypes.Count)
    {
        case 0:
            // no matching types
            return null;

        case 1:
            // single matching type
            return matchingTypes.First();

        default:
            // multiple matching types
            throw CreateAmbiguousControllerException(route, controllerName, matchingTypes);
    }
}

GetControllerTypeWithinNamespaces裏面就就兩句,經過BuildManager初始化,ControllerTypeCache看這個名字也知道是控制器類型緩存,而後經過名字和namesapces得到Type,若是0個返回null,1個正確,2個便報錯。mvc

主要看一下EnsureInitialized和GetControllerTypes這兩個方法。app

EnsureInitialized

public void EnsureInitialized(IBuildManager buildManager)
{
    if (_cache == null)
    {
        lock (_lockObj)
        {
            if (_cache == null)
            {
                //TypeCaheName='MVC-ControllerTypeCache.xml'
                List<Type> controllerTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(TypeCacheName, IsControllerType, buildManager);
                var groupedByName = controllerTypes.GroupBy(
                    t => t.Name.Substring(0, t.Name.Length - "Controller".Length),
                    StringComparer.OrdinalIgnoreCase);
                _cache = groupedByName.ToDictionary(
                    g => g.Key,
                    g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
                    StringComparer.OrdinalIgnoreCase);
            }
        }
    }
}

裏面又調用了TypeCacheUtil的GetFilteredTypesFromAssemblies的方法。獲得以後,對控制器的名字進行截取,取控制前的名字進行分組(GroupBy)。而後封裝成一個字典_cache。而後咱們看看GetFilteredTypesFromAssemblies這個方法裏面發生了什麼。ui

//predicate  == IsControllerType
public static List<Type> GetFilteredTypesFromAssemblies(string cacheName, Predicate<Type> predicate, IBuildManager buildManager)
{
    TypeCacheSerializer serializer = new TypeCacheSerializer();

    // 首先從磁盤文件中讀取數據 cacheName='MVC-ControllerTypeCache.xml'
    List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
    if (matchingTypes != null)
    {
        return matchingTypes;
    }

    // 若是讀到數據,將每一個數據進行對比
    matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();

    // 最後保存會磁盤中
    SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);

    return matchingTypes;
}

在c盤應該能夠找到和這個文件,這就是用來進行緩存mvc和Action的類型。裏面的細節的邏輯就不看。主要看這個方法FilterTypesInAssemblies,方法的意思應該就是在程序集中過濾Type。spa

//predicate  == IsControllerType
private static IEnumerable<Type> FilterTypesInAssemblies(IBuildManager buildManager, Predicate<Type> 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));
}

定義了一個空的類型,經過buildManager.GetReferencedAssemblies()得到引用的程序集。循環,獲取每一個程序集的type,在最後的時候進行條件過濾,在最前面標註了predicate的值是IsControllerType。這個值是定義在ControllerTypeCache中的。code

internal static bool IsControllerType(Type t)
{
    return
        t != null &&
        t.IsPublic &&
        t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
        !t.IsAbstract &&
        typeof(IController).IsAssignableFrom(t);
}

就是說這個Type是否是public,是否是已Controller結尾,要不是抽象類,是否是繼承IController。經過的Type放到typesSoFar。而後返回matchingTypes,而後賦給controllerTypes。而後賦值給_cache。 最後進行路由匹配。orm

public ICollection<Type> GetControllerTypes(string controllerName, HashSet<string> namespaces)
{
    HashSet<Type> matchingTypes = new HashSet<Type>();

    ILookup<string, Type> namespaceLookup;
    if (_cache.TryGetValue(controllerName, out namespaceLookup))
    {
        // 若是命名空間不爲空的話,循環Type比較Type的命名空間是否和設置的一致。
        if (namespaces != null)
        {
            foreach (string requestedNamespace in namespaces)
            {
                foreach (var targetNamespaceGrouping in namespaceLookup)
                {
                    if (IsNamespaceMatch(requestedNamespace, targetNamespaceGrouping.Key))
                    {
                        matchingTypes.UnionWith(targetNamespaceGrouping);
                    }
                }
            }
        }
        else
        {
            // 若是命名空間爲空的話,搜索全部的namespace
            foreach (var namespaceGroup in namespaceLookup)
            {
                matchingTypes.UnionWith(namespaceGroup);
            }
        }
    }

    return matchingTypes;
}

調用了GetController的方法。這裏纔是真正的比對控制器的。首先創建了一個HashSet 泛型 的對象。經過controllerName的值在_cache中獲取,有值,判斷命名空間又沒有,有的話,循環每一個命名空間。比對分完組的Controller集合。調用UnionWith的方法。這個是HashSet裏面的方法。在集合中進行比較。若是沒有重複就放進去。因此到最後返回的是一個Type的集合。
理一下上面的邏輯。xml

  • GetControllerType 方法 經過不一樣的設置namespace的狀況分別調用GetControllerTypeWithinNamespaces。
    • GetControllerTypeWithinNamespaces 方法內的 EnsureInitialized
      • 首先讀取緩存文件
      • 而後循環全部的引用程序集,經過IsControllerType過濾類型
      • 保存會程序集中
      • 將返回的符合的程序集經過名字分組,賦給_cache屬性
    • GetControllerTypeWithinNamespaces方法 GetControllerTypes 方法
      • 若是有命名空間,經過比對_cache中的Type的命名空間比對,天璣道machtingType中
      • 沒有的話,比對全部的Type

GetControllerInstance

protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        throw new HttpException(404,
                                String.Format(
                                    CultureInfo.CurrentCulture,
                                    MvcResources.DefaultControllerFactory_NoControllerFound,
                                    requestContext.HttpContext.Request.Path));
    }
    if (!typeof(IController).IsAssignableFrom(controllerType))
    {
        throw new ArgumentException(
            String.Format(
                CultureInfo.CurrentCulture,
                MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
                controllerType),
            "controllerType");
    }
    return ControllerActivator.Create(requestContext, controllerType);
}

再次判斷一下Type。其實最後起做用的只有最後一句。首先要看一下ControllerActivator這個屬性是什麼樣的。對象

internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver)
{
    if (controllerActivator != null)
    {
        _controllerActivator = controllerActivator;
    }
    else
    {
        _activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>(
                                                      () => null,
                                                      new DefaultControllerActivator(dependencyResolver),
                                                      "DefaultControllerFactory constructor");
    }
}

private IControllerActivator ControllerActivator
{
    get
    {
        if (_controllerActivator != null)
        {
            return _controllerActivator;
        }
        _controllerActivator = _activatorResolver.Current;
        return _controllerActivator;
    }
}

能夠看出是_activatorResolver.Current. _activatorResolver這個類型是何時初始化的呢?是在DefaultControllerFactory建立的時候,能夠看到又是使用了SingleServiceResolver,這個是ControllerFactory的同樣的套路,若是DependencyResolver中註冊了對應的Resolver能夠返回IControllerActivator就會首先返回這個。若是沒有設置的話也就是默認狀態的話會使用DefaultControllerActivator。繼承

public IController Create(RequestContext requestContext, Type controllerType)
{
    try
    {
        return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException(
            String.Format(
                CultureInfo.CurrentCulture,
                MvcResources.DefaultControllerFactory_ErrorCreatingController,
                controllerType),
            ex);
    }
}

若是構造DefaultControllerActivator的參數dependencyResolver爲空,那就經過反射進行構造返回就好了。

相關文章
相關標籤/搜索