如何以及何时填充MVC-ControllerTypeCache.xml

时间:2012-08-21 14:13:01

标签: c# asp.net-mvc-3

我有几个与此文件相关的问题(MVC-ControllerTypeCache.xml)。

1)有人能告诉我这个文件的生成时间和方式吗?

我知道它是由框架生成的,以减少调用控制器时所需的反射量。

我也知道MVC源中有一些内部类用于处理它,控制器工厂GetControllerType使用它们。

2)有没有办法在应用程序中使用它?

例如,如果我想列出应用程序中的所有控制器,使用此文件意味着我不必通过反射自己找到它们。

因为方法GetControllerType(requestContext, controllerName);将根据它在此文件中找到的内容返回您的控制器类型,所以还应该知道它是如何/何时更新的。

知道何时更新以及是否可以依赖它可能会改变从驻留在自己的程序集中的插件/模块注册控制器的方式。

我主要是出于兴趣而主要问。

2 个答案:

答案 0 :(得分:6)

  

1)有谁能告诉我这个文件何时以及如何生成?

在每个请求上调用的DefaultControllerFactory.GetControllerType调用GetControllerTypeWithinNamespaces方法来检索可用控制器类型列表:

private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces) {
    ControllerTypeCache.EnsureInitialized(BuildManager);
    ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);

    ... more code removed for brevity
}

正如您所看到的,它在开始时做了两件事:从ControllerTypeCache初始化和检索控制器类型。

EnsureInitialized方法使用具有双重检查锁定的单例,以确保在应用程序的整个生命周期内仅执行一次初始化:

public void EnsureInitialized(IBuildManager buildManager) {
    if (_cache == null) {
        lock (_lockObj) {
            if (_cache == null) {
                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);
            }
        }
    }
}

注意_cache字段如果为空,将仅初始化一次。这将发生在IIS启动应用程序后第一个访问您的站点的请求上。

使用TypeCacheUtil.GetFilteredTypesFromAssemblies方法检索控制器类型。让我们来看看它:

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

    // first, try reading from the cache on disk
    List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
    if (matchingTypes != null) {
        return matchingTypes;
    }

    // if reading from the cache failed, enumerate over every assembly looking for a matching type
    matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();

    // finally, save the cache back to disk
    SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);

    return matchingTypes;
}

代码非常自我解释:

  1. 它使用TypeCacheSerializer类从缓存中读取它们。在内部,此序列化程序将文件加载到XmlDocument,然后操纵其元素以提取类型。
  2. 如果在缓存中找不到任何内容,则会对FilterTypesInAssemblies方法进行昂贵的调用,该方法将使用反射从所有引用的程序集中检索控制器类型。
  3. 它将类型保存到缓存中,以便下次从缓存中加载它们。
  4. 这是一篇博文,其中还介绍了该过程:http://www.beletsky.net/2011/12/inside-aspnet-mvc-instantiation-of.html

      

    2)有没有办法在应用程序中使用它?

    您不应该直接使用代码中的XML文件,因为它的内容和格式可能会在以后的版本中发生变化,这会破坏您的代码。

    我同意,尽管能够利用我们的代码中的这个功能来提高昂贵的反射代码的性能本身会很不错。我希望框架的作者公开这个API。

    不幸的是他们没有,所以我们可以推出自己的:

    public static class ControllerTypeCache
    {
        private static object _syncRoot = new object();
        private static Type[] _cache;
    
        public static IEnumerable<Type> GetControllerTypes()
        {
            if (_cache == null)
            {
                lock (_syncRoot)
                {
                    if (_cache == null)
                    {
                        _cache = GetControllerTypesWithReflection();
                    }
                }
            }
            return new ReadOnlyCollection<Type>(_cache);
        }
    
        private static Type[] GetControllerTypesWithReflection()
        {
            var typesSoFar = Type.EmptyTypes;
            var assemblies = BuildManager.GetReferencedAssemblies();
            foreach (Assembly assembly in assemblies) 
            {
                Type[] typesInAsm;
                try 
                {
                    typesInAsm = assembly.GetTypes();
                }
                catch (ReflectionTypeLoadException ex) 
                {
                    typesInAsm = ex.Types;
                }
                typesSoFar = typesSoFar.Concat(typesInAsm).ToArray();
            }
    
            return typesSoFar
                .Where(t => t != null && 
                            t.IsPublic && 
                            !t.IsAbstract && 
                            typeof(IController).IsAssignableFrom(t)
                )
                .ToArray();
        }
    }
    
      

    还应该知道如何/何时更新为   方法GetControllerType(requestContext,controllerName);将返回   您的控制器类型基于它在此文件中找到的内容。

    此文件在应用程序的整个生命周期内永远不会更新。如前所述,它在应用程序启动时创建一次。

答案 1 :(得分:2)

我已经快速浏览了一下ASP.NET MVC源代码,据我所知,缓存文件是在第一次尝试读取时创建的。这通常会在应用程序启动期间发生。

ASP.NET MVC包含一个内部类ControllerTypeCache,其中包含一个包含文件名的常量字符串。这是源代码中唯一出现的“MVC-ControllerTypeCache.xml”。

internal sealed class ControllerTypeCache
{
    private const string TypeCacheName = "MVC-ControllerTypeCache.xml";
    ...
}

此常量仅用于ControllerTypeCache类的此方法:

public void EnsureInitialized(IBuildManager buildManager)
{
    if (_cache == null)
    {
        lock (_lockObj)
        {
            if (_cache == null)
            {
                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.GetFilteresTypesFromAssemblies。这就是该方法的样子:

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

    // first, try reading from the cache on disk
    List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
    if (matchingTypes != null)
    {
        return matchingTypes;
    }

    // if reading from the cache failed, enumerate over every assembly looking for a matching type
    matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();

    // finally, save the cache back to disk
    SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);

    return matchingTypes;
}

如您所见,它尝试从缓存文件中读取。如果读取失败(例如因为文件尚不存在),则会生成一个新的控制类型列表并保存在新版本的缓存文件中。

ReadTypesFromCache方法如下所示:

internal static List<Type> ReadTypesFromCache(string cacheName, Predicate<Type> predicate, IBuildManager buildManager, TypeCacheSerializer serializer)
{
    try
    {
        Stream stream = buildManager.ReadCachedFile(cacheName);
        if (stream != null)
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                List<Type> deserializedTypes = serializer.DeserializeTypes(reader);
                if (deserializedTypes != null && deserializedTypes.All(type => TypeIsPublicClass(type) && predicate(type)))
                {
                    // If all read types still match the predicate, success!
                    return deserializedTypes;
                }
            }
        }
    }
    catch
    {
    }

    return null;
}

如您所见,它使用BuildManager来读取缓存的文件。

这是我能找到的唯一可以读取或创建缓存文件的地方。在导航通过调用层次结构时,我从DefaultControllerFactory类或AreaRegistration类中以该方法结束。

所以我猜这些类中的任何一个第一次需要应用程序中的控制器列表时,GetFilteredTypesFromAssemblies类的TypeCacheUtil方法最终被调用。它只在无法读取文件时生成缓存文件。由于此类使用BuildManager来读取它,我相信它只会在文件损坏,丢失或重新启动应用程序时生成它。

在浏览网页后,人们似乎报告说,为了重新生成MVC-ControllerTypeCache.xml文件,更改和保存Global.asax或Web.Config文件以触发重启就像这样。< / p>

您可以在自己的代码中利用此文件吗?你可能可以,但你可能不应该。我只是用反射。如果您因为这种方法而遇到性能问题,那么然后就可以开始考虑缓存了,在这种情况下,我打赌它更“安全”来构建自己的序列化控制器列表并使用内置的.NET的缓存机制来缓存它们而不是尝试重用MVC-ControllerTypeCache.xml。这是我认为的内部原因。