5

このファイル (MVC-ControllerTypeCache.xml) に関していくつか質問があります。

1)このファイルがいつ、どのように生成されるか教えてもらえますか?

コントローラーが呼び出されたときに必要な反射の量を減らすために、フレームワークによって生成されることを私は知っています。

また、MVC ソースには、それを操作するための内部クラスがいくつかあることも知っています。コントローラー ファクトリGetControllerTypeはそれらを利用します。

2)アプリケーションでそれを操作する方法はありますか?

たとえば、アプリケーション内のすべてのコントローラーを一覧表示する場合、このファイルを使用すると、リフレクションを介して自分でコントローラーをすべて見つける必要がなくなります。

GetControllerType(requestContext, controllerName);メソッドがこのファイルで見つけたものに基づいてコントローラーのタイプを返すため、更新される方法/時期を知ることも価値があります。

いつ更新されるかを知り、それを信頼できるかどうかを知ることで、独自のアセンブリにあるプラグイン/モジュールからコントローラーを登録する方法が変わる可能性があります。

私は主に興味本位で質問しています。

4

2 に答える 2

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
}

ご覧のとおり、最初に 2 つのことを行います。初期化と .xml からのコントローラー タイプの取得ですControllerTypeCache

このEnsureInitializedメソッドは、ダブル チェック ロックを備えたシングルトンを使用して、アプリケーションの存続期間全体で初期化が 1 回だけ実行されるようにします。

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

_cachenull の場合、フィールドが 1 回だけ初期化されることに注意してください。これは、アプリケーションが 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. タイプがキャッシュに保存されるため、次回はキャッシュからロードされます。

プロセスについても説明しているブログ投稿は次のとおりです

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 のソース コードをざっと見てみたところ、何かが初めてキャッシュ ファイルを読み取ろうとしたときに、キャッシュ ファイルが作成されることがわかりました。これは通常、アプリケーションの開始時に発生します。

ControllerTypeCacheASP.NET MVC には、ファイル名を含む定数文字列を持つ内部クラスが含まれています。ソース コードで「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;
}

ご覧のとおり、キャッシュ ファイルから読み取ろうとします。読み取りが失敗した場合 (ファイルがまだ存在しないなどの理由で)、controllertypes の新しいリストが生成され、新しいバージョンのキャッシュ ファイルに保存されます。

メソッドは次の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;
}

ご覧のとおり、 a を使用しBuildManagerてキャッシュされたファイルを読み取ります。

これは、キャッシュ ファイルを読み取ったり作成したりすることができる唯一の場所です。呼び出し階層をナビゲートすると、DefaultControllerFactoryクラスまたはクラスのいずれかからそのメソッドに行き着きAreaRegistrationます。

したがって、これらのクラスのいずれかがアプリケーションでコントローラーのリストを初めて必要とするときGetFilteredTypesFromAssembliesに、クラスのメソッドTypeCacheUtilが呼び出されることになると思います。ファイルを読み取れない場合にのみ、キャッシュ ファイルを生成します。このクラスは aBuildManagerを使用して読み取るため、ファイルが破損している、見つからない、またはアプリケーションが再起動されたときにのみ生成されると思います。

Web を閲覧した後、MVC-ControllerTypeCache.xml ファイルを再生成するには、再起動をトリガーするために Global.asax または Web.Config ファイルを変更して保存するなどの方法でうまくいくと報告しているようです。

独自のコードでこのファイルを利用できますか? おそらくできますが、そうすべきではありません。リフレクションを使用するだけです。そのアプローチが原因でパフォーマンスの問題に直面したことがある場合は、キャッシングについて考え始めることができます。その場合、独自のシリアル化されたコントローラーのリストを作成し、.NET の組み込みのキャッシング メカニズムを使用してキャッシュする方が「安全」だと思います。 MVC-ControllerTypeCache.xml を再利用しようとするのではなく、それらを使用してください。それは私が推測する理由で内部です。

于 2012-08-24T08:46:19.390 に答える