ASPを分析する.NET Core MVC(Part 1)- AddMvcCore

21157 ワード

原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore発表:2017年3月環境:ASP.NET Core 1.1
新しいシリーズの第1部を読むことを歓迎して、私はMVCソースコードを剖析して、みんなに表面の下に隠れている仕事のメカニズムを展示します.このシリーズではMVCの内部を分析し、退屈なら読むのを止める.個人的には、最後にASPを理解するまで繰り返し読んだり、デバッグしたり、狂ったりしました.NET MVCのソースコード(あるいは自分で理解していると思っている)は、そこから利益を得ています.フレームワークの動作メカニズムを理解することで、それらをよりよく使用することができ、問題を解決しやすくなります.
私は全力を尽くして皆さんにソースの理解を説明して、私は自分の理解と解釈が100%正しいことを保証することができなくて、しかし私は全力を尽くします.簡潔で明確なコードを説明するのは難しいことを知っておく必要があります.私は小さなブロックコードを通じてMVCソースコードを展示し、ソースファイルのリンクを添付して、みんなが追跡するのに便利です.読んだ後も理解していない場合は、ソースコードを読むのに時間を費やし、必要に応じて自分でデバッグすることをお勧めします.私はこのシリーズが私のように根掘り葉掘り聞くのが好きな人の興味を引き起こすことを望んでいます.

AddMvcCore


本稿では、AddMvcCoreが私たちのために何をしてくれたのかを分析し、ApplicationPartManagerのようなクラスに注目します.本明細書で使用するproject.jsonはrel/1.1.2ソースコードに基づいてMVC Sandboxプロジェクトを実行してデバッグを行う.
注:MvcSandboxはASP.Net Core MVCソースコードのカラムアイテム.
バージョンが更新されているため、特に内部でクラスやメソッドが変更される場合があります.常にGitHubの最新コードを参照してください.MvcSandbox ConfigureServicesについて更新しました.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore();
}
AddMvcCoreはIServiceCollectionの拡張方法です.通常、関連するサービスのセットをサービスセット(services collection)に登録する場合、拡張メソッドというモードが使用されます.MVCアプリケーションを構築する際には、MVCサービス(MVC Services)を登録するための2つの拡張方法があり、AddMvcCoreはそのうちの1つです.AddMvcメソッドに比べて、AddMvcCoreは少ないサービスサブセットを提供します.MVCのすべての特性を使用する必要のない簡単なプログラムの中には、AddMvcCoreを使用することができます.例えばREST APIsを構築するにはRazor関連コンポーネントは必要ありませんが、私は一般的にAddMvcCoreを使用しています.AddMvcCoreの後に手動で追加のサービスを追加したり、より多くのAddMvcを直接使用したりすることもできます.AddMvcCoreの実装:
public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    var partManager = GetApplicationPartManager(services);
    services.TryAddSingleton(partManager);

    ConfigureDefaultFeatureProviders(partManager);
    ConfigureDefaultServices(services);
    AddMvcCoreServices(services);

    var builder = new MvcCoreBuilder(services, partManager);

    return builder;
}
AddMvcCoreが最初に行ったことは、GetApplicationPartManagerの静的な方法でApplicationPartManagerを取得し、IServiceCollectionをパラメータとしてGetApplicationPartManagerに渡すことです.GetApplicationPartManagerの実装:
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
{
    var manager = GetServiceFromCollection(services);
    if (manager == null)
    {
        manager = new ApplicationPartManager();

        var environment = GetServiceFromCollection(services);
        if (string.IsNullOrEmpty(environment?.ApplicationName))
        {
            return manager;
        }

        var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName);
        foreach (var part in parts)
        {
            manager.ApplicationParts.Add(part);
        }
    }

    return manager;
}
この方法では、まず、現在登録されているサービスにApplicationPartManagerがあるかどうかを確認します.通常はありませんが、AddMvcCoreを呼び出す前に他のサービスを登録することはめったにありません.ApplicationPartManagerが存在しない場合は、新しいものを作成します.
次のコードは、ApplicationPartManagerのApplicationParts属性値の計算です.まずサービス集合(services collection)からIHostingEnvironmentを取得する.IHostingEnvironmentインスタンスを取得できる場合は、プログラム名またはパッケージ名(アプリケーション/assem bly name)が取得され、静的メソッドD e f a ultAssemblyPartDiscoveryProviderに渡され、DiscoverAssemblyPartsメソッドはIEnumerableに戻ります.
public static IEnumerable DiscoverAssemblyParts(string entryPointAssemblyName)
{
    var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName));
    var context = DependencyContext.Load(Assembly.Load(new AssemblyName(entryPointAssemblyName)));

    return GetCandidateAssemblies(entryAssembly, context).Select(p => new AssemblyPart(p));
}
DiscoverAssemblyPartsは、まずパッケージング名(assembly name)によりパッケージングオブジェクト(Assembly Object)とDependencyContexを取得する.この例のパッケージ名は「MvcSandbox」です.これらの値をGetCandidateAssembliesメソッドに渡し、GetCandidateAssembliesメソッドを呼び出します.
internal static IEnumerable GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext)
{
    if (dependencyContext == null)
    {
        // Use the entry assembly as the sole candidate.
        return new[] { entryAssembly };
    }

    return GetCandidateLibraries(dependencyContext)
        .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext))
        .Select(Assembly.Load);
}

internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext)
{
    if (ReferenceAssemblies == null)
    {
        return Enumerable.Empty();
    }

    var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies);
    return candidatesResolver.GetCandidates();
}
GetCandidateLibrariesが何をしたか説明します.
で参照されているプログラムセットのリストを返します.デフォルトは、プロジェクト自体のプログラムセットを含まない主なMVCプログラムセットです.
より明確に、得られたプログラムセットのリストには、私たちのソリューションで参照されているすべてのMVCプログラムセットが含まれています.
ReferenceAssemblies(ReferenceAssemblies)は、13個のMVCのデフォルトのプログラムセットを含むD e f a u t AssemblyPartDiscoveryProviderクラスで定義された静的HashSetです.
internal static HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
    "Microsoft.AspNetCore.Mvc",
    "Microsoft.AspNetCore.Mvc.Abstractions",
    "Microsoft.AspNetCore.Mvc.ApiExplorer",
    "Microsoft.AspNetCore.Mvc.Core",
    "Microsoft.AspNetCore.Mvc.Cors",
    "Microsoft.AspNetCore.Mvc.DataAnnotations",
    "Microsoft.AspNetCore.Mvc.Formatters.Json",
    "Microsoft.AspNetCore.Mvc.Formatters.Xml",
    "Microsoft.AspNetCore.Mvc.Localization",
    "Microsoft.AspNetCore.Mvc.Razor",
    "Microsoft.AspNetCore.Mvc.Razor.Host",
    "Microsoft.AspNetCore.Mvc.TagHelpers",
    "Microsoft.AspNetCore.Mvc.ViewFeatures"
};
GetCandidateLibrariesは、CandidateResolverクラスを使用して位置を特定し、「候補者」に戻ります.CandidateResolverは、ランタイムオブジェクト(RuntimeLibrary objects)とReferenceAssembliesによって構築されます.各ランタイムオブジェクトは順次反復され、辞書に追加され、追加中に依存名(dependency name)が一意であるかどうかを確認し、一意でない場合は例外を放出します.
public CandidateResolver(IReadOnlyList dependencies, ISet<string> referenceAssemblies)
{
    var dependenciesWithNoDuplicates = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase);
    foreach (var dependency in dependencies)
    {
        if (dependenciesWithNoDuplicates.ContainsKey(dependency.Name))
        {
            throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(dependency.Name));
        }
        dependenciesWithNoDuplicates.Add(dependency.Name, CreateDependency(dependency, referenceAssemblies));
    }

    _dependencies = dependenciesWithNoDuplicates;
}
各依存オブジェクト(RuntimeLibrary)は、新しい依存オブジェクトとして辞書に格納されます.これらのオブジェクトには、必要なlibraries(candidates)をフィルタするDependencyClassificationプロパティが含まれています.DependencyClassificationは列挙タイプです.
private enum DependencyClassification
{
    Unknown = 0,
    Candidate = 1,
    NotCandidate = 2,
    MvcReference = 3
}
Dependencyを作成する場合、ReferenceAssemblies HashSetと一致する場合はMvcReference、残りはUnknownと表記します.
private Dependency CreateDependency(RuntimeLibrary library, ISet<string> referenceAssemblies)
{
    var classification = DependencyClassification.Unknown;
    if (referenceAssemblies.Contains(library.Name))
    {
        classification = DependencyClassification.MvcReference;
    }

    return new Dependency(library, classification);
}
そうすればGetCandidatesメソッドが呼び出されると、ComputeClassificationメソッドと組み合わせて依存オブジェクトツリー全体を巡回します.各依存オブジェクトは、親依存がCandidateタイプとしてマークされるまで、CandidateまたはMvcReferenceに一致するまで、すべての子をチェックします.ループが終了すると、identified candidatesを含むIEnumerableが返されます.たとえば、この例では、MvcSandboxプログラムセットのみがCandidateとしてマークされます.
public IEnumerable GetCandidates()
{
    foreach (var dependency in _dependencies)
    {
        if (ComputeClassification(dependency.Key) == DependencyClassification.Candidate)
        {
            yield return dependency.Value.Library;
        }
    }
}

private DependencyClassification ComputeClassification(string dependency)
{
    Debug.Assert(_dependencies.ContainsKey(dependency));

    var candidateEntry = _dependencies[dependency];
    if (candidateEntry.Classification != DependencyClassification.Unknown)
    {
        return candidateEntry.Classification;
    }
    else
    {
        var classification = DependencyClassification.NotCandidate;
        foreach (var candidateDependency in candidateEntry.Library.Dependencies)
        {
            var dependencyClassification = ComputeClassification(candidateDependency.Name);
            if (dependencyClassification == DependencyClassification.Candidate ||
                dependencyClassification == DependencyClassification.MvcReference)
            {
                classification = DependencyClassification.Candidate;
                break;
            }
        }

        candidateEntry.Classification = classification;

        return classification;
    }
}
DiscoverAssemblyPartsは、返されるcandidatesを新しいAssemblyPartに変換します.このオブジェクトは、名前、タイプなどの主要なパッケージ属性のみを含むプログラムセットの単純なパッケージです.後で私は単独でこのような分析を書くかもしれません.
最後に、GetApplicationPartManagerにより、AssemblyPartsがApplicationPartManagerに組み込まれます.
      var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName);
      foreach (var part in parts)
      {
          manager.ApplicationParts.Add(part);
      }
  }

  return manager;
返されたApplicationPartManagerインスタンスは、AddMvcCore拡張メソッドを使用してservices collectionに追加されます.
次にAddMvcCoreは、ApplicationPartManagerをパラメータとして静的C o n f i g u r e DefaultFeatureProvidersメソッドを呼び出し、ApplicationPartManagerのFeatureProvidersにControllerFeatureProviderを追加します.
private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager)
{
    if (!manager.FeatureProviders.OfType().Any())
    {
        manager.FeatureProviders.Add(new ControllerFeatureProvider());
    }
}
ControllerFeatureProviderは、ApplicationParのインスタンスでcontrollerrsを発見するために使用されます.ControllerFeatureProviderについては、後述のブログでご紹介します.今、AddMovCoreの最後の一歩を研究し続けます.(ApplicationPartManagerが更新されました)
まずMicrosoftを介してプライベートメソッドを呼び出します.AspNetCore.Routingが提供するAddRouting拡張メソッドはrouting機能をオンにします.routing機能を有効にするために必要なサービスと構成を提供します.この文書では詳細には説明しません.
AddMvcCoreは次に、フレームワークオプション、actionの発見、選択、呼び出し、controllerファクトリ、モデルバインド、認証など、MVCコアサービスの登録を担当する別のプライベートメソッドAddMvcCoreServicesを呼び出します.
最後にAddMvcCoreはservices collectionとApplicationPartManagerを通じて、新しいMvcCoreBuilderオブジェクトを構築します.これは次のように呼ばれます.
細粒度を許容する構成MVCインフラストラクチャ(Allows fine grained configuration of essential MVC services.)
AddMvcCore拡張メソッドは、IserviceCollectionおよびApplicationPartManagerのプロパティを含むMvcCoreBuilderを返します.MvcCoreBuilderとその拡張方法は、AddMvcCoreの初期化後に追加の構成を行うために使用されます.実際には、AddMvcメソッドで最初に呼び出されたのはAddMvcCoreであり、その後、MvcCoreBuilderを使用して追加のサービスを構成します.

小結


上記のすべての問題をはっきり説明するのは容易なことではないので、簡単にまとめてみましょう.本稿ではMVCの下位コードを解析し,主にMVCsに必要なサービスをIserviceCollectionに追加することを実現した.ApplicationPartManagerの作成プロセスを追跡することで、MVCが内部アプリケーションモデル(ApplicationModel)を一歩一歩作成する方法について説明します.実際の機能は多く見られませんでしたが、startupの追跡分析を通じて多くの面白いものを発見し、後続の分析に基礎を築きました.以上が私のAddMvcCoreに対する初歩的な探究であり、46の付加項目がIservceCollectionに登録されています.次はAddMvc拡張方法をさらに分析します.