web-dev-qa-db-ja.com

ASP.NET CoreとMVC6(Core)を使用したUnity

2018年9月8日更新
Unityは開発中です here が、ASP.NET Coreフレームワークでどのように動作するかをテストする時間がありませんでした。

2018年3月15日更新
このソリューションは、.NET Framework 4.5.2を使用中にUnityでASP.NET Core v1を使用する特定の問題に対応 ない .NET Core Framework。いくつかの.Net 4.5.2 DLLが必要だったため、このセットアップを使用する必要がありましたが、新たに始める人にはこのアプローチをお勧めしません。また、Unityは(私の知る限りでは)これ以上開発されていないため、新しいプロジェクトにはAutofacフレームワークを使用することをお勧めします。その方法の詳細については、こちら Post を参照してください。

イントロ
MVCでASP.NETを使用してWebアプリケーションを構築しています。このアプリケーションは、特定のサービス(WCFサービス、データストアサービスなど)に依存しています。物事を素敵で分離した状態に保つために、DI(Dependecy Injection)フレームワーク、特にUnityを使用したいと思います。

初期研究
この ブログ投稿 を見つけましたが、残念ながら機能していません。アイデアはしかしいいです。
基本的に、ServiceCollectionに登録されているすべてのサービスを独自のコンテナーに登録するのではなく、デフォルトのServiceProviderを参照するように指示しています。
そう。何かを解決する必要がある場合は、デフォルトのServiceProviderが呼び出され、解像度がない場合は、カスタムUnityContainerを使用してタイプが解決されます。

問題
MVCは常にデフォルトのServiceProviderでコントローラーを解決しようとします。
また、コントローラーが正しく解決されたとしても、依存関係を「混在」させることはできません。ここで、サービスの1つだけでなくASPからのIOptionsインターフェイスも使用したい場合、これらの2つのコンテナのどちらにも両方のタイプの解決策がないため、クラスを解決できません。

必要なもの
要約すると、次のものが必要です。

  • ASP.NET依存関係をUnityContainerにコピーする必要がないセットアップ
  • MVCコントローラーを解決できるコンテナー
  • 「混合」依存関係を解決できるコンテナー

編集:
では、これらのポイントをどのように達成できるのでしょうか?

環境
project.json:
enter image description here

20
D4rth B4n3

それで、いくつかの調査の後、私の問題に対する次の解決策を思いつきました。

ASPでUnityを使用
ASPでUnityを使用できるようにするには、カスタムIServiceProvider( ASP Documentation )が必要だったので、このようなIUnityContainerのラッパーを作成しました

public class UnityServiceProvider : IServiceProvider
{
    private IUnityContainer _container;

    public IUnityContainer UnityContainer => _container;

    public UnityServiceProvider()
    {
        _container = new UnityContainer();
    }

    #region Implementation of IServiceProvider

    /// <summary>Gets the service object of the specified type.</summary>
    /// <returns>A service object of type <paramref name="serviceType" />.-or- null if there is no service object of type <paramref name="serviceType" />.</returns>
    /// <param name="serviceType">An object that specifies the type of service object to get. </param>
    public object GetService(Type serviceType)
    {
        //Delegates the GetService to the Containers Resolve method
        return _container.Resolve(serviceType);
    }

    #endregion
}

また、StartupクラスのConfigureServicesメソッドのSignatureを次のように変更する必要がありました。

public void ConfigureServices(IServiceCollection services)

これに:

public IServiceProvider ConfigureServices(IServiceCollection services)

これで、カスタムIServiceProviderを返すことができ、デフォルトのIServiceProviderの代わりに使用されます。
完全なConfigureServicesメソッドは、下部の[Wire up]セクションに表示されます。

コントローラの解決
このブログ投稿を見つけました。それから、MVCはIControllerActivatorインターフェイスを使用してコントローラーのインスタンス化を処理することを学びました。だから私はこのように見える私自身を書いた:

public class UnityControllerActivator : IControllerActivator
{
    private IUnityContainer _unityContainer;

    public UnityControllerActivator(IUnityContainer container)
    {
        _unityContainer = container;
    }

    #region Implementation of IControllerActivator

    public object Create(ControllerContext context)
    {
        return _unityContainer.Resolve(context.ActionDescriptor.ControllerTypeInfo.AsType());
    }


    public void Release(ControllerContext context, object controller)
    {
        //ignored
    }

    #endregion
}

Controllerクラスがアクティブ化されると、UnityContainerでインスタンス化されます。したがって、私のUnityContainerは、コントローラーを解決する方法を知っている必要があります!

次の問題:デフォルトのIServiceProviderを使用します
MvcなどのサービスをASP.NETに登録すると、通常は次のようになります。

services.AddMvc();

UnityContainerを使用する場合、すべてのMVC依存関係は登録されていないため解決できません。したがって、それらを登録する(AutoFacなど)か、UnityContainerExtensionを作成できます。拡張機能を選択し、次の2つのケースを考え出しました。
UnityFallbackProviderExtension

public class UnityFallbackProviderExtension : UnityContainerExtension
{
    #region Const

    ///Used for Resolving the Default Container inside the UnityFallbackProviderStrategy class
    public const string FALLBACK_PROVIDER_NAME = "UnityFallbackProvider";

    #endregion

    #region Vars

    // The default Service Provider so I can Register it to the IUnityContainer
    private IServiceProvider _defaultServiceProvider;

    #endregion

    #region Constructors

    /// <summary>
    /// Creates a new instance of the UnityFallbackProviderExtension class
    /// </summary>
    /// <param name="defaultServiceProvider">The default Provider used to fall back to</param>
    public UnityFallbackProviderExtension(IServiceProvider defaultServiceProvider)
    {
        _defaultServiceProvider = defaultServiceProvider;
    }

    #endregion

    #region Overrides of UnityContainerExtension

    /// <summary>
    /// Initializes the container with this extension's functionality.
    /// </summary>
    /// <remarks>
    /// When overridden in a derived class, this method will modify the given
    /// <see cref="T:Microsoft.Practices.Unity.ExtensionContext" /> by adding strategies, policies, etc. to
    /// install it's functions into the container.</remarks>
    protected override void Initialize()
    {
        // Register the default IServiceProvider with a name.
        // Now the UnityFallbackProviderStrategy can Resolve the default Provider if needed
        Context.Container.RegisterInstance(FALLBACK_PROVIDER_NAME, _defaultServiceProvider);

        // Create the UnityFallbackProviderStrategy with our UnityContainer
        var strategy = new UnityFallbackProviderStrategy(Context.Container);

        // Adding the UnityFallbackProviderStrategy to be executed with the PreCreation LifeCycleHook
        // PreCreation because if it isnt registerd with the IUnityContainer there will be an Exception
        // Now if the IUnityContainer "magically" gets a Instance of a Type it will accept it and move on
        Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
    }

    #endregion
}


UnityFallbackProviderStrategy

public class UnityFallbackProviderStrategy : BuilderStrategy
{
    private IUnityContainer _container;

    public UnityFallbackProviderStrategy(IUnityContainer container)
    {
        _container = container;
    }

    #region Overrides of BuilderStrategy

    /// <summary>
    /// Called during the chain of responsibility for a build operation. The
    /// PreBuildUp method is called when the chain is being executed in the
    /// forward direction.
    /// </summary>
    /// <param name="context">Context of the build operation.</param>
    public override void PreBuildUp(IBuilderContext context)
    {
        NamedTypeBuildKey key = context.OriginalBuildKey;

        // Checking if the Type we are resolving is registered with the Container
        if (!_container.IsRegistered(key.Type))
        {
            // If not we first get our default IServiceProvider and then try to resolve the type with it
            // Then we save the Type in the Existing Property of IBuilderContext to tell Unity
            // that it doesnt need to resolve the Type
            context.Existing = _container.Resolve<IServiceProvider>(UnityFallbackProviderExtension.FALLBACK_PROVIDER_NAME).GetService(key.Type);
        }

        // Otherwise we do the default stuff
        base.PreBuildUp(context);
    }

    #endregion
}

UnityContainerに何かの登録がない場合、デフォルトのプロバイダーにそれを要求するだけです。
このすべてをいくつかの異なる記事から学びました

このアプローチのいいところは、依存関係を "混合"できることです。 ASPのサービスとIOptionsインターフェイスのいずれかが必要な場合、UnityContainerはこれらの依存関係をすべて解決し、コントローラーに挿入します!!!
覚えておくべき唯一のことは、独自の依存関係を使用する場合、デフォルトのIServiceProviderがコントローラーの依存関係を解決できないため、コントローラークラスをUnityに登録する必要があることです。

最後に:結線
今、私のプロジェクトでは、さまざまなサービス(ASPオプション、MVCとオプション)を使用しています。すべてを機能させるために、ConfigureServicesメソッドは次のようになります。

public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // Add all the ASP services here
        // #region ASP
        services.AddOptions();
        services.Configure<WcfOptions>(Configuration.GetSection("wcfOptions"));

        var globalAuthFilter = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();

        services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(globalAuthFilter)); })
                .AddJsonOptions
            (
                options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()
            );
        // #endregion ASP

        // Creating the UnityServiceProvider
        var unityServiceProvider = new UnityServiceProvider();

        IUnityContainer container = unityServiceProvider.UnityContainer;

        // Adding the Controller Activator
        // Caution!!! Do this before you Build the ServiceProvider !!!
        services.AddSingleton<IControllerActivator>(new UnityControllerActivator(container));

        //Now build the Service Provider
        var defaultProvider = services.BuildServiceProvider();

        // Configure UnityContainer
        // #region Unity

        //Add the Fallback extension with the default provider
        container.AddExtension(new UnityFallbackProviderExtension(defaultProvider));

        // Register custom Types here

        container.RegisterType<ITest, Test>();

        container.RegisterType<HomeController>();
        container.RegisterType<AuthController>();

        // #endregion Unity

        return unityServiceProvider;
    }

過去1週間にDIについて知っていることのほとんどを学んだので、もしそうなら教えてください。

37
D4rth B4n3

ASP.Net Core 2.0、2.1、2.2、およびUnityの場合、Unityの作成者からNuGetパッケージとして入手できる公式のソリューションがあります: NuGetPackage

サンプルを含むGitリポジトリを次に示します。 Git repo

使い方は非常に簡単です(Gitリポジトリのホームページから):

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
       .UseUnityServiceProvider()   <---- Add this line
       .UseStartup<Startup>()
       .Build();

そして、 here は、ASP.Net Core用のUnity DIの例です。

ASP.Net Coreアプリケーションでこのソリューションを使用しており、正常に機能しています。

19
Karel Kral