web-dev-qa-db-ja.com

依存関係の解決の一部としてランタイムパラメーターを渡すにはどうすればよいですか?

接続文字列をいくつかのサービス実装に渡すことができる必要があります。これをコンストラクターで実行しています。接続文字列は、ユーザーがクレームとしてClaimsPrincipalに追加することで構成可能です。

これまでのところ順調です。

残念ながら、ASP.NET Coreの依存関係注入機能を最大限に使用し、DIを介してサービスの実装を解決できるようにしたいと考えています。

POC実装があります:

public interface IRootService
{
    INestedService NestedService { get; set; }

    void DoSomething();
}

public class RootService : IRootService
{
    public INestedService NestedService { get; set; }

    public RootService(INestedService nestedService)
    {
        NestedService = nestedService;
    }

    public void DoSomething()
    {
        // implement
    }
}


public interface INestedService
{
    string ConnectionString { get; set; }

    void DoSomethingElse();
}

public class NestedService : INestedService
{
    public string ConnectionString { get; set; }

    public NestedService(string connectionString)
    {
        ConnectionString = connectionString;
    }

    public void DoSomethingElse()
    {
        // implement
    }
}

これらのサービスは起動時に登録され、INestedServiceがコントローラーのコンストラクターに追加されました。

public HomeController(INestedService nestedService)
{
    NestedService = nestedService;
}

予想どおり、エラーUnable to resolve service for type 'System.String' while attempting to activate 'Test.Dependency.Services.NestedService'.

ここで私のオプションは何ですか?

25
Ant Swift

シンプルな構成

public void ConfigureServices(IServiceCollection services)
{
    // Choose Scope, Singleton or Transient method
    services.AddSingleton<IRootService, RootService>();
    services.AddSingleton<INestedService, NestedService>(serviceProvider=>
    {
         return new NestedService("someConnectionString");
    });
}

AppSettings.jsonを使用

AppSettings.json内で接続文字列を非表示にすることにした場合、たとえば:

"Data": {
  "ConnectionString": "someConnectionString"
}

その後、ConfigurationBuilder(通常はStartupクラスのコンストラクターにあります)にappSettings.jsonをロードした場合、ConfigureServicesは次のようになります。

public void ConfigureServices(IServiceCollection services)
{
    // Choose Scope, Singleton or Transient method
    services.AddSingleton<IRootService, RootService>();
    services.AddSingleton<INestedService, NestedService>(serviceProvider=>
    {
         var connectionString = Configuration["Data:ConnectionString"];
         return new NestedService(connectionString);
    });
}

拡張メソッドを使用

namespace Microsoft.Extensions.DependencyInjection
{
    public static class RootServiceExtensions //you can pick a better name
    {
        //again pick a better name
        public static IServiceCollection AddRootServices(this IServiceCollection services, string connectionString) 
        {
            // Choose Scope, Singleton or Transient method
            services.AddSingleton<IRootService, RootService>();
            services.AddSingleton<INestedService, NestedService>(_ => 
              new NestedService(connectionString));
        }
    }
}

次に、ConfigureServicesメソッドは次のようになります

public void ConfigureServices(IServiceCollection services)
{
    var connectionString = Configuration["Data:ConnectionString"];
    services.AddRootServices(connectionString);
}

オプションビルダーを使用

さらにパラメーターが必要な場合は、さらに一歩進んで、RootServiceのコンストラクターに渡すオプションクラスを作成できます。複雑になる場合は、Builderパターンを使用できます。

35
Ivan Prodanov

アプリケーションの起動時に不明なランタイムパラメータを渡すには、ファクトリパターンを使用する必要があります。ここには2つのオプションがあります

  1. 工場工法

    services.AddTransient<Func<string,INestedService>>((provider) => 
    {
        return new Func<string,INestedService>( 
            (connectionString) => new NestedService(connectionString)
        );
    });
    

    INestedServiceの代わりにサービスにファクトリメソッドを注入します。

    public class RootService : IRootService
    {
        public INestedService NestedService { get; set; }
    
        public RootService(Func<string,INestedService> nestedServiceFactory)
        {
            NestedService = nestedServiceFactory("ConnectionStringHere");
        }
    
        public void DoSomething()
        {
            // implement
        }
    }
    

    または呼び出しごとに解決する

    public class RootService : IRootService
    {
        public Func<string,INestedService> NestedServiceFactory { get; set; }
    
        public RootService(Func<string,INestedService> nestedServiceFactory)
        {
            NestedServiceFactory = nestedServiceFactory;
        }
    
        public void DoSomething(string connectionString)
        {
            var nestedService = nestedServiceFactory(connectionString);
    
            // implement
        }
    }
    
  2. 工場クラス

    public class RootServiceFactory : IRootServiceFactory 
    {
        // in case you need other dependencies, that can be resolved by DI
        private readonly IServiceCollection services;
    
        public RootServiceCollection(IServiceCollection services)
        {
            this.services = services;
        }
    
        public CreateInstance(string connectionString) 
        {
            // instantiate service that needs runtime parameter
            var nestedService = new NestedService(connectionString);
    
            // resolve another service that doesn't need runtime parameter
            var otherDependency = services.GetService<IOtherService>()
    
            // pass both into the RootService constructor and return it
            return new RootService(otherDependency, nestedDependency);
        }
    }
    

    IRootServiceFactoryの代わりにIRootServiceを注入します。

    IRootService rootService = rootServiceFactory.CreateIntsance(connectionString);
    
31
Tseng