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)
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");
}
}
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 .
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)
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éeStartup{EnvironmentName}
(par exempleStartupDevelopment
) et leASPNETCORE_ENVIRONMENT
La variable d'environnement correspond à ce nom, puis cette classeStartup
est utilisée à la place. Ainsi, vous pouvez configurerStartup
pour le développement, mais avoir unStartupProduction
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!
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.