web-dev-qa-db-fra.com

Comment puis-je transmettre un paramètre d'exécution dans le cadre de la résolution de dépendance?

Je dois pouvoir passer une chaîne de connexion dans certaines de mes implémentations de service. Je fais cela dans le constructeur. La chaîne de connexion est configurable par l'utilisateur sera ajouté le ClaimsPrincipal en tant que revendication.

Très bien jusqu'à présent.

Malheureusement, je souhaite également pouvoir utiliser au maximum les fonctionnalités d'injection de dépendances dans ASP.NET Core et résoudre l'implémentation du service via DI.

J'ai une implémentation 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
    }
}

Ces services ont été enregistrés au démarrage et INestedService a été ajouté le constructeur d'un contrôleur.

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

Comme prévu, j'obtiens l'erreur Unable to resolve service for type 'System.String' while attempting to activate 'Test.Dependency.Services.NestedService'.

Quelles sont mes options ici?

25
Ant Swift

Configuration simple

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

Avec appSettings.json

Si vous décidez de masquer votre chaîne de connexion dans appSettings.json, par exemple:

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

À condition que vous ayez chargé votre appSettings.json dans ConfigurationBuilder (généralement situé dans le constructeur de la classe Startup), vos ConfigureServices ressembleraient à ceci:

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

Avec des méthodes d'extension

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

Ensuite, votre méthode ConfigureServices ressemblerait à ceci

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

Avec le générateur d'options

Si vous avez besoin de plus de paramètres, vous pouvez aller plus loin et créer une classe d'options que vous passez au constructeur de RootService. S'il devient complexe, vous pouvez utiliser le modèle Builder.

35
Ivan Prodanov

Pour passer un paramètre d'exécution inconnu au démarrage de l'application, vous devez utiliser le modèle d'usine. Vous avez deux options ici

  1. méthode d'usine

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

    et injectez la méthode d'usine dans votre service au lieu de INestedService.

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

    ou le résoudre par appel

    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. classe d'usine

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

    et injectez IRootServiceFactory au lieu de votre IRootService.

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