web-dev-qa-db-fra.com

Injection de dépendance résolue par nom

Comment puis-je injecter différentes implémentations d'objet pour une classe spécifique?

Par exemple, dans l'unité, je peux: Définir deux implémentations de IRepository

container.RegisterType<IRepository, TestSuiteRepositor("TestSuiteRepository");
container.RegisterType<IRepository, BaseRepository>(); 

et appeler la mise en œuvre nécessaire

public BaselineManager([Dependency("TestSuiteRepository")]IRepository repository)
23
Ilya

Comme l'a souligné @Tseng, il n'y a pas de solution intégrée pour la liaison nommée. Cependant, l'utilisation de la méthode d'usine peut être utile pour votre cas. L'exemple devrait être quelque chose comme ci-dessous:

Créez un résolveur de référentiel:

public interface IRepositoryResolver
{
    IRepository GetRepositoryByName(string name);
}

public class RepositoryResolver : IRepositoryResolver 
{
    private readonly IServiceProvider _serviceProvider;
    public RepositoryResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public IRepository GetRepositoryByName(string name)
    {
         if(name == "TestSuiteRepository") 
           return _serviceProvider.GetService<TestSuiteRepositor>();
         //... other condition
         else
           return _serviceProvider.GetService<BaseRepositor>();
    }

}

Enregistrez les services nécessaires dans ConfigureServices.cs

services.AddSingleton<IRepositoryResolver, RepositoryResolver>();
services.AddTransient<TestSuiteRepository>();
services.AddTransient<BaseRepository>(); 

Enfin, utilisez-le dans n'importe quelle classe:

public class BaselineManager
{
    private readonly IRepository _repository;

    public BaselineManager(IRepositoryResolver repositoryResolver)
    {
        _repository = repositoryResolver.GetRepositoryByName("TestSuiteRepository");
    }
}
23
adem caglin

En plus de la réponse @ adem-caglin, je voudrais publier ici un code réutilisable que j'ai créé pour les enregistrements basés sur le nom.

[~ # ~] mise à jour [~ # ~] Maintenant, il est disponible sous la forme paquet nuget .

Pour enregistrer vos services, vous devrez ajouter le code suivant à votre classe Startup:

        services.AddTransient<ServiceA>();
        services.AddTransient<ServiceB>();
        services.AddTransient<ServiceC>();
        services.AddByName<IService>()
            .Add<ServiceA>("key1")
            .Add<ServiceB>("key2")
            .Add<ServiceC>("key3")
            .Build();

Ensuite, vous pouvez l'utiliser via l'interface IServiceByNameFactory:

public AccountController(IServiceByNameFactory<IService> factory) {
    _service = factory.GetByName("key2");
}

Ou vous pouvez utiliser l'enregistrement d'usine pour garder le code client propre (ce que je préfère)

_container.AddScoped<AccountController>(s => new AccountController(s.GetByName<IService>("key2")));

Le code complet de l'extension est dans github .

22
neleus

Vous ne pouvez pas avec le conteneur IoC ASP.NET Core intégré.

C'est par conception . Le conteneur intégré est intentionnellement simple et facilement extensible, vous pouvez donc brancher des conteneurs tiers si vous avez besoin de plus de fonctionnalités.

Pour ce faire, vous devez utiliser un conteneur tiers, comme Autofac (voir docs ).

public BaselineManager([WithKey("TestSuiteRepository")]IRepository repository)
6
Tseng

Après avoir lu la documentation officielle pour l'injection de dépendances , je ne pense pas que vous puissiez le faire de cette façon.

Mais la question que j'ai est: avez-vous besoin de ces deux implémentations en même temps? Parce que si vous ne le faites pas, vous pouvez créer plusieurs environnements via des variables d'environnement et avoir fonctionnalité spécifique dans la classe Startup basée sur l'environnement actuel , ou même créer plusieurs Startup{EnvironmentName} Des classes.

Lorsqu'une application ASP.NET Core démarre, la classe Startup est utilisée pour bootstrap l'application, charger ses paramètres de configuration, etc. (en savoir plus sur le démarrage d'ASP.NET). Cependant, s'il existe une classe nommée Startup{EnvironmentName} (par exemple StartupDevelopment) et le ASPNETCORE_ENVIRONMENT La variable d'environnement correspond à ce nom, puis cette classe Startup est utilisée à la place. Ainsi, vous pouvez configurer Startup pour le développement, mais avoir un StartupProduction distinct qui serait utilisé lorsque l'application est exécutée en production. Ou vice versa.

J'ai également écrit un article sur l'injection de dépendances à partir d'un fichier JSON afin que vous n'ayez pas à recompiler l'application entière chaque fois que vous souhaitez basculer entre les implémentations. Fondamentalement, vous conservez un tableau JSON avec des services comme celui-ci:

"services": [
    {
        "serviceType": "ITest",
        "implementationType": "Test",
        "lifetime": "Transient"
    }
]

Ensuite, vous pouvez modifier l'implémentation souhaitée dans ce fichier et ne pas avoir à recompiler ou changer les variables d'environnement.

J'espère que cela t'aides!

4
radu-matei

Tout d'abord, c'est probablement encore une mauvaise idée. Ce que vous essayez de réaliser, c'est une séparation entre la façon dont les dépendances sont utilisées et la façon dont elles sont définies. Mais vous voulez travailler avec le framework d'injection de dépendances, plutôt que contre. Éviter la mauvaise capacité de détection de l'anti-modèle de localisateur de services. Pourquoi ne pas utiliser des génériques d'une manière similaire à ILogger<T>/IOptions<T>?

public BaselineManager(RepositoryMapping<BaselineManager> repository){
   _repository = repository.Repository;
}

public class RepositoryMapping<T>{
    private IServiceProvider _provider;
    private Type _implementationType;
    public RepositoryMapping(IServiceProvider provider, Type implementationType){
        _provider = provider;
        _implementationType = implementationType;
    }
    public IRepository Repository => (IRepository)_provider.GetService(_implementationType);
}

public static IServiceCollection MapRepository<T,R>(this IServiceCollection services) where R : IRepository =>
    services.AddTransient(p => new RepositoryMapping<T>(p, typeof(R)));

services.AddScoped<BaselineManager>();
services.MapRepository<BaselineManager, BaseRepository>();

Depuis .net core 3, une erreur de validation devrait être déclenchée si vous n'avez pas réussi à définir un mappage.

1
Jeremy Lakeman