Puis-je transmettre des paramètres de constructeur à la méthode Resolve () de Unity?

91

J'utilise Unity de Microsoft pour l'injection de dépendances et je souhaite faire quelque chose comme ceci:

IDataContext context = _unityContainer.Resolve<IDataContext>();
var repositoryA = _unityContainer.Resolve<IRepositoryA>(context); //Same instance of context
var repositoryB = _unityContainer.Resolve<IRepositoryB>(context); //Same instance of context

IDataContext context2 = _unityContainer.Resolve<IDataContext>(); //New instance
var repositoryA2 = _unityContainer.Resolve<IRepositoryA>(context2);

RepositoryAet les RepositoryBdeux ont un constructeur qui prend un IDataContextparamètre, et je veux qu'Unity initialise le référentiel avec le contexte que je lui passe. Notez également que ce IDataContextn'est pas enregistré avec Unity (je ne veux pas 3 instances de IDataContext).

NotDan
la source

Réponses:

71

À partir d'aujourd'hui, ils ont ajouté cette fonctionnalité:

C'est dans la dernière goutte ici:

http://unity.codeplex.com/SourceControl/changeset/view/33899

Discussion à ce sujet ici:

http://unity.codeplex.com/Thread/View.aspx?ThreadId=66434

Exemple:

container.Resolve<IFoo>(new ParameterOverrides<Foo> { { "name", "bar" }, { "address", 42 } });"
Exister
la source
Voir aussi stackoverflow.com/questions/2813322/…
Michael Freidgeim
5
le lien unity.codeplex.com/SourceControl/changeset/view/33899 n'est pas actif
M.Kumaran
2
"La classe 'Microsoft.Practices.Unity.ParameterOverrides' n'a pas de paramètres de type". J'utilise Unity 3.5; ce code est-il valide uniquement pour une ancienne version d'Unity?
Thomas Levesque
Ça marche pour moi. Remarque: votre classe doit avoir un constructeur paramétré avec le paramètre "nom" et le paramètre "adresse". Foo(string name, int address) { ... }
adun
Utilisation d'Unity 2.1: container.Resolve<IFoo>(new ParameterOverrides { { "name", "bar" }, { "address", 42 } });
mrfelis
38

<2 cents>

Et si vous décidez plus tard d'utiliser un service différent qui nécessite plus ou moins que le contexte?

Le problème avec les paramètres du constructeur et l'IoC est que les paramètres sont finalement liés au type concret utilisé, au lieu de faire partie du contrat défini par l'interface de service.

Ma suggestion serait que vous résolviez également le contexte, et je pense que Unity devrait avoir un moyen pour vous d'éviter de construire 3 instances de celui-ci, ou vous devriez envisager un service d'usine qui vous permet de construire l'objet.

Par exemple, que se passe-t-il si vous décidez plus tard de construire un référentiel qui ne repose pas du tout sur une base de données traditionnelle, mais qui utilise plutôt un fichier XML pour produire des données factices pour le test? Comment feriez-vous pour transmettre le contenu XML à ce constructeur?

IoC est basé sur le découplage du code, en liant le type et la sémantique des arguments aux types concrets, vous n'avez vraiment pas fait le découplage correctement, il y a toujours une dépendance.

"Ce code peut éventuellement parler à n'importe quel type de référentiel, du moment qu'il implémente cette interface ... Oh, et utilise un contexte de données".

Maintenant, je sais que d'autres conteneurs IoC prennent en charge cela, et je l'avais également dans ma première version, mais à mon avis, cela n'appartient pas à l'étape de résolution.

</ 2 cents>

Lasse V. Karlsen
la source
3
Je comprends votre point et je suis d'accord avec vous, mais j'ai toujours besoin que les instances de RepositoryA et RepositoryB aient le même IDataContext, qui doit être différent de RepositoryC. Notez également que IRepositoryA et IRepositoryB ont une propriété pour IDataContext. Je vais mettre à jour un peu l'exemple de code.
NotDan
2
Excellent point. J'étais sur le point d'ajouter un paramètre de chaîne au constructeur, mais après avoir vu ce point, j'ai décidé d'en faire un objet à part entière. Il ne se compose que de la chaîne à ce stade, mais je peux déjà voir comment je pourrais y ajouter des propriétés plus utiles
Santosh Benjamin
9

Merci les gars ... le mien est similaire au post de "Exist". Voir ci-dessous:

        IUnityContainer container = new UnityContainer();
        container.LoadConfiguration();

        _activeDirectoryService = container.Resolve<IActiveDirectoryService>(new ResolverOverride[]
        {
            new ParameterOverride("activeDirectoryServer", "xyz.adserver.com")
        });
Kwex
la source
5

Vous pouvez utiliser InjectionConstructor / InjectionProperty / InjectionMethod en fonction de votre architecture d'injection dans ResolvedParameter <T> ("name") pour obtenir une instance d'un objet pré-enregistré dans le conteneur.

Dans votre cas, cet objet doit être enregistré avec un nom, et pour la même insance, vous avez besoin de ContainerControlledLifeTimeManager () comme LifeTimeManager.

_unityContainer.RegisterType<IDataContext,DataContextA>("DataContextA", new ContainerControlledLifeTimeManager());
_unityContainer.RegisterType<IDataContext,DataContextB>("DataContextB");

  var repositoryA = _unityContainer.Resolve<IRepositoryA>(new InjectionConstructor(
new ResolvedParameter<IDataContext>("DataContextA")));

  var repositoryB = _unityContainer.Resolve<IRepositoryB>(new InjectionConstructor(
new ResolvedParameter<IDataContext>("DataContextA")));

  var repositoryA2 = _unityContainer.Resolve<IRepositoryA>(new InjectionConstructor(
new ResolvedParameter<IDataContext>("DataContextB")));
Trecenti
la source
4
Êtes-vous sûr de ce code? Il ne compile pas ... Resolveprend une collection de ResolverOverride, et InjectionConstructorn'est pas un fichier ResolverOverride.
Thomas Levesque
Ouais, ça ne va pas. Bien que l'unité aurait dû le concevoir de cette façon. Si le nom du paramètre change, tout se brise
Frank Q.
3

La réponse très courte est: non. Unity n'a actuellement aucun moyen de passer des paramètres dans le constructeur qui ne sont pas constants ou injectés, que j'ai pu trouver. À mon humble avis, c'est la chose la plus importante qui manque, mais je pense que c'est par conception plutôt que par omission.

Comme le note Jeff Fritz, vous pouvez en théorie créer un gestionnaire de durée de vie personnalisé qui sait quelle instance de contexte injecter dans différents types, mais c'est un niveau de codage en dur qui semble obvier à l'objectif d'utiliser Unity ou DI en premier lieu.

Vous pouvez prendre du recul par rapport à la DI complète et confier à vos implémentations de référentiel la responsabilité d'établir leurs propres contextes de données. L' instance de contexte peut toujours être résolue à partir du conteneur, mais la logique pour décider lequel utiliser devrait aller dans l'implémentation du référentiel. Ce n'est certainement pas aussi pur, mais cela éliminerait le problème.

Neil Hewitt
la source
1

Une autre alternative que vous pourriez utiliser (je ne sais pas vraiment si c'est une bonne pratique ou non) consiste à créer deux conteneurs et à enregistrer une instance pour chacun:

IDataContext context = _unityContainer.Resolve<IDataContext>();
_unityContainer.RegisterInstance(context);
var repositoryA = _unityContainer.Resolve<IRepositoryA>(); //Same instance of context
var repositoryB = _unityContainer.Resolve<IRepositoryB>(); //Same instance of context


//declare _unityContainer2
IDataContext context2 = _unityContainer2.Resolve<IDataContext>(); //New instance
_unityContainer2.RegisterInstance(context2);
var repositoryA2 = _unityContainer2.Resolve<IRepositoryA>(context2); //will retrieve the other instance

j'espère que cela aide aussi

Samuel Carrijo
la source
0

NotDan, je pense que vous avez peut-être répondu à votre propre question dans des commentaires à lassevk.

Tout d'abord, j'utiliserais un LifetimeManager pour gérer le cycle de vie et le nombre d'instances d'IDataContext que Unity crée.
http://msdn.microsoft.com/en-us/library/cc440953.aspx

Il semble que l' ContainerControlledLifetimeManagerobjet vous donnera la gestion d'instance dont vous avez besoin. Avec ce LifetimeManager en place, Unity doit ajouter la même instance de IDataContext à tous les objets qui nécessitent une dépendance IDataContext.

Jeff Fritz
la source