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

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

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

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

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

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

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

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

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

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

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

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

 private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet namespaces) { ControllerTypeCache.EnsureInitialized(BuildManager); ICollection 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 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字段为null,它将如何仅初始化一次。 这将发生在IIS启动应用程序后第一个访问您的站点的请求上。

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

 public static List GetFilteredTypesFromAssemblies(string cacheName, Predicate predicate, IBuildManager buildManager) { TypeCacheSerializer serializer = new TypeCacheSerializer(); // first, try reading from the cache on disk List 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方法进行昂贵的调用,该方法将使用reflection从所有引用的程序FilterTypesInAssemblies检索控制器类型。
  3. 它将类型保存到缓存中,以便下次从缓存加载它们。

这是一篇博客文章,其中也描述了这个过程: http : //www.beletsky.net/2011/12/inside-aspnet-mvc-instantiation-of.html

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

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

我同意,尽管能够利用我们的代码中的这个function来提高昂贵的reflection代码的性能本来是很好的。 我希望框架的作者公开这个API。

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

 public static class ControllerTypeCache { private static object _syncRoot = new object(); private static Type[] _cache; public static IEnumerable GetControllerTypes() { if (_cache == null) { lock (_syncRoot) { if (_cache == null) { _cache = GetControllerTypesWithReflection(); } } } return new ReadOnlyCollection(_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); 将根据它在此文件中找到的内容返回您的控制器类型。

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

我已经快速浏览了一下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 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 GetFilteredTypesFromAssemblies(string cacheName, Predicate predicate, IBuildManager buildManager) { TypeCacheSerializer serializer = new TypeCacheSerializer(); // first, try reading from the cache on disk List 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 ReadTypesFromCache(string cacheName, Predicate predicate, IBuildManager buildManager, TypeCacheSerializer serializer) { try { Stream stream = buildManager.ReadCachedFile(cacheName); if (stream != null) { using (StreamReader reader = new StreamReader(stream)) { List 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类中以该方法结束。

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

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

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