5

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

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

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

我也知道 MVC 源代码中有一些内部类可以使用它,控制器工厂GetControllerType使用它们。

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

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

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

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

不过,我主要是出于兴趣而问。

4

2 回答 2

6

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

在每个请求上调用的DefaultControllerFactory.GetControllerTypewhich 调用该 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类从缓存中读取它们。在内部,此序列化程序将文件加载到 a 中XmlDocument,然后操作其元素以提取类型。
  2. FilterTypesInAssemblies如果在缓存中找不到任何内容,则会对将使用反射从所有引用的程序集中检索控制器类型的方法进行昂贵的调用。
  3. 它将类型保存到缓存中,以便下次从缓存中加载它们。

这是一篇博客文章,它也描述了该过程: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); 将根据在此文件中找到的内容返回您的控制器类型。

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

于 2012-08-24T09:21:00.923 回答
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;
}

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

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

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

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

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

于 2012-08-24T08:46:19.390 回答