.NET Core DI、コンストラクターにパラメーターを渡す方法


105

次のサービスコンストラクターを持つ

public class Service : IService
{
     public Service(IOtherService service1, IAnotherOne service2, string arg)
     {

     }
}

.NET CoreIOCメカニズムを使用してパラメーターを渡すための選択肢は何ですか

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));

他に方法はありますか?


3
デザインを変更します。引数をパラメータオブジェクトに抽出し、それを挿入します。
スティーブン

回答:


124

ファクトリデリゲートの式パラメーター(この場合はx)はIServiceProvider。です。

それを使用して依存関係を解決し、

_serviceCollection.AddSingleton<IService>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
                x.GetRequiredService<IAnotherOne>(), 
                ""));

ファクトリデリゲートは遅延呼び出しです。タイプが解決されるときはいつでも、完成したプロバイダーをデリゲートパラメーターとして渡します。


1
はい、これは私が今やっている方法ですが、他の方法はありますか?もっとエレガントかも?つまり、登録されたサービスである他のパラメーターがあると、少し奇妙に見えるでしょう。サービスを通常どおり登録し、サービス以外の引数(この場合はarg)のみを渡すようなものを探しています。Autofacような何かない .WithParameter("argument", "");
ボリス・

1
いいえ、プロバイダーを手動で構築しているのは悪いことです。デリゲートは遅延呼び出しです。タイプが解決されるときはいつでも、完成したプロバイダーをデリゲートパラメーターとして渡します。
nkosi 2018

@MCRは、CoreDIをそのまま使用するデフォルトのアプローチです。
nkosi 2018

12
@Nkosi:パッケージの一部であるActivatorUtilities.CreateInstanceを見てくださいMicrosoft.Extensions.DependencyInjection.Abstractions(したがって、コンテナー固有の依存関係はありません)
Tseng 2018

ありがとう、@ Tsengは私たちがここで探している実際の答えのように見えます。
BrainSlugs 8320

63

推奨される方法は、オプションパターンを使用することであることに注意してください。ただし、実用的でない場合(パラメーターが実行時にのみ認識され、起動/コンパイル時には認識されない場合)、または依存関係を動的に置き換える必要があるユースケースがあります。

単一の依存関係(文字列、整数、または別のタイプの依存関係)を置き換える必要がある場合、または文字列/整数パラメーターのみを受け入れるサードパーティライブラリを使用していてランタイムパラメーターが必要な場合に非常に便利です。

CreateInstance(IServiceProvider、Object [])をショートカットとして試すことができます文字列パラメーター/値型/プリミティブ(int、float、string)、未テストで動作するかどうかは わかりません)(試してみて、動作を確認しただけです。複数の文字列パラメータ)すべての依存関係を手動で解決するのではなく:

_serviceCollection.AddSingleton<IService>(x => 
    ActivatorUtilities.CreateInstance<Service>(x, "");
);

パラメーター(CreateInstance<T>/の最後のパラメーターCreateInstance)は、置き換える必要のあるパラメーターを定義します(プロバイダーから解決されません)。それらは、表示されるとおりに左から右に適用されます(つまり、最初の文字列は、インスタンス化されるタイプの最初の文字列型パラメーターに置き換えられます)。

ActivatorUtilities.CreateInstance<Service> サービスを解決し、この単一のアクティベーションのデフォルト登録の1つを置き換えるために、多くの場所で使用されます。

たとえば、という名前のクラスMyServiceIOtherServiceありILogger<MyService>、依存関係としてがあり、サービスを解決したいが、デフォルトのサービスIOtherService(たとえばOtherServiceA)をOtherServiceBに置き換えたい場合は、次のようにすることができます。

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())

次に、の最初のパラメータが注入されますが、残りのパラメータはコンテナからIOtherService取得OtherServiceBOtherServiceAれます。

これは、依存関係が多く、単一の依存関係を特別に扱いたい場合に役立ちます(つまり、データベース固有のプロバイダーを、要求中または特定のユーザー用に構成された値に置き換えます。これは、実行時および要求中にのみ認識され、アプリケーションのビルド/起動時ではありません)。

代わりに、ActivatorUtilities.CreateFactory(Type、Type [])メソッドを使用してファクトリメソッドを作成することもできます。これは、GitHubリファレンスベンチマークのパフォーマンスが向上するためです。

後者は、タイプが非常に頻繁に解決される場合(SignalRやその他の要求の高いシナリオなど)に役立ちます。基本的に、ObjectFactoryビアを作成します

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });

次に、それを(変数などとして)キャッシュし、必要に応じて呼び出します

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

##更新:文字列と整数でも機能することを確認するために自分で試してみましたが、実際に機能します。ここに私がテストした具体的な例があります:

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddTransient<HelloWorldService>();
        services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow"));

        var provider = services.BuildServiceProvider();

        var demoService = provider.GetRequiredService<DemoService>();

        Console.WriteLine($"Output: {demoService.HelloWorld()}");
        Console.ReadKey();
    }
}

public class DemoService
{
    private readonly HelloWorldService helloWorldService;
    private readonly string firstname;
    private readonly string lastname;

    public DemoService(HelloWorldService helloWorldService, string firstname, string lastname)
    {
        this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService));
        this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname));
        this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname));
    }

    public string HelloWorld()
    {
        return this.helloWorldService.Hello(firstName, lastName);
    }
}

public class HelloWorldService
{
    public string Hello(string name) => $"Hello {name}";
    public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}";
}

// Just a helper method to shorten code registration code
static class ServiceProviderExtensions
{
    public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => 
        ActivatorUtilities.CreateInstance<T>(provider, parameters);
}

プリント

Output: Hello Tseng Stackoverflow

6
これは、ASP.NET CoreがデフォルトでControllerActivatorProviderをインスタンス化する方法でもあり、IoCから直接解決されません(.AddControllersAsServices使用されていない限りControllerActivatorProviderServiceBasedControllerActivator
Tseng

16

サービスの新規作成に不安を感じた場合は、このParameter Objectパターンを使用できます。

したがって、文字列パラメータを独自のタイプに抽出します

public class ServiceArgs
{
   public string Arg1 {get; set;}
}

そしてコンストラクターは次のようになります

public Service(IOtherService service1, 
               IAnotherOne service2, 
               ServiceArgs args)
{

}

そしてセットアップ

_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();

最初の利点は、Serviceコンストラクターを変更して新しいサービスを追加する必要がある場合、new Service(...呼び出しを変更する必要がないことです。もう1つの利点は、セットアップが少しすっきりしていることです。

ただし、パラメーターが1つまたは2つあるコンストラクターの場合、これは多すぎる可能性があります。


2
複雑なパラメーターではオプションパターンを使用する方が直感的であり、オプションパターンに推奨される方法ですが、実行時にのみ(つまり、要求またはクレームから)認識されるパラメーターにはあまり適していません
Tseng

0

このプロセスで依存性を注入することもできます

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.