Wie füge ich mit Unity eine benannte Abhängigkeit in einen Konstruktor ein?

68

Ich habe die IRespositoryzweimal (mit Namen) im folgenden Code registriert:

// Setup the Client Repository
IOC.Container.RegisterType<ClientEntities>(new InjectionConstructor());
IOC.Container.RegisterType<IRepository, GenericRepository>
    ("Client", new InjectionConstructor(typeof(ClientEntities)));

// Setup the Customer Repository
IOC.Container.RegisterType<CustomerEntities>(new InjectionConstructor());
IOC.Container.RegisterType<IRepository, GenericRepository>
    ("Customer", new InjectionConstructor(typeof(CustomerEntities)));

IOC.Container.RegisterType<IClientModel, ClientModel>();
IOC.Container.RegisterType<ICustomerModel, CustomerModel>();

Aber wenn ich dies beheben möchte (um das zu verwenden IRepository), muss ich eine manuelle Lösung wie folgt durchführen:

public ClientModel(IUnityContainer container)
{
   this.dataAccess = container.Resolve<IRepository>(Client);

   .....
}

Was ich tun möchte, ist, es im Konstruktor auflösen zu lassen (genau wie IUnityContainer). Ich brauche eine Möglichkeit zu sagen, in welchen benannten Typ ich auflösen soll.

So etwas wie das: (HINWEIS: Kein echter Code)

public ClientModel([NamedDependancy("Client")] IRepository dataAccess)
{
   this.dataAccess = dataAccess;

   .....
}

Gibt es eine Möglichkeit, meinen gefälschten Code zum Laufen zu bringen?

Vaccano
quelle

Antworten:

92

Sie können Abhängigkeiten mit oder ohne Namen in der API, in Attributen oder über die Konfigurationsdatei konfigurieren. Sie haben XML oben nicht erwähnt, daher gehe ich davon aus, dass Sie die API verwenden.

Um den Container anzuweisen, eine benannte Abhängigkeit aufzulösen, müssen Sie ein InjectionParameterObjekt verwenden. Führen Sie für Ihr ClientModelBeispiel Folgendes aus:

container.RegisterType<IClientModel, ClientModel>(
    new InjectionConstructor(                        // Explicitly specify a constructor
        new ResolvedParameter<IRepository>("Client") // Resolve parameter of type IRepository using name "Client"
    )
);

Dies teilt dem Container mit: " ClientModelRufen Sie beim Auflösen den Konstruktor auf, der einen einzelnen IRepositoryParameter verwendet. Wenn Sie diesen Parameter auflösen, lösen Sie zusätzlich zum Typ den Namen 'Client' auf."

Wenn Sie Attribute verwenden möchten, funktioniert Ihr Beispiel fast. Sie müssen lediglich den Attributnamen ändern:

public ClientModel([Dependency("Client")] IRepository dataAccess)
{
   this.dataAccess = dataAccess;

   .....
}
Chris Tavares
quelle
Ist es möglich, dass es ohne das Abhängigkeitsattribut funktioniert? Zum Beispiel IRepository>("Customer")sollte das in einer anderen Instanz vonClientModel
Legends
1
@Legends sicher, mit der API. Erstellen Sie eine separate benannte Registrierung für ClientModel für jede Variation des benötigten Injektionsstils. Lösen Sie dann die Abhängigkeit von IClientModel. Funktioniert genauso wie bei IRepository oben.
Chris Tavares
25

Dies ist eine sehr späte Antwort, aber die Frage wird immer noch in Google angezeigt.

Also sowieso 5 Jahre später ...

Ich habe einen ziemlich einfachen Ansatz. Wenn Sie "benannte Abhängigkeit" verwenden müssen, liegt dies normalerweise daran, dass Sie versuchen, eine Art Strategiemuster zu implementieren. In diesem Fall erstelle ich einfach eine Indirektionsebene zwischen Unity und dem Rest meines Codes, die StrategyResolverals nicht direkt von Unity abhängig bezeichnet wird.

public class StrategyResolver : IStrategyResolver
{
    private IUnityContainer container;

    public StrategyResolver(IUnityContainer unityContainer)
    {
        this.container = unityContainer;
    }

    public T Resolve<T>(string namedStrategy)
    {
        return this.container.Resolve<T>(namedStrategy);
    }
}

Verwendung:

public class SomeClass: ISomeInterface
{
    private IStrategyResolver strategyResolver;

    public SomeClass(IStrategyResolver stratResolver)
    {
        this.strategyResolver = stratResolver;
    }

    public void Process(SomeDto dto)
    {
        IActionHandler actionHanlder = this.strategyResolver.Resolve<IActionHandler>(dto.SomeProperty);
        actionHanlder.Handle(dto);
    }
}

Anmeldung:

container.RegisterType<IActionHandler, ActionOne>("One");
container.RegisterType<IActionHandler, ActionTwo>("Two");
container.RegisterType<IStrategyResolver, StrategyResolver>();
container.RegisterType<ISomeInterface, SomeClass>();

Das Schöne daran ist, dass ich den StrategyResolver nie wieder berühren muss, wenn ich in Zukunft neue Strategien hinzufüge.

Es ist sehr einfach. Sehr sauber und ich habe die Abhängigkeit von Unity auf ein striktes Minimum beschränkt. Das einzige Mal, dass ich den StrategyResolver berühren müsste, wäre, wenn ich mich entscheide, die Containertechnologie zu ändern, was sehr unwahrscheinlich ist.

Hoffe das hilft!

Bearbeiten: Die akzeptierte Antwort gefällt mir nicht wirklich, da Sie bei Verwendung des DependencyAttributs im Konstruktor Ihres Dienstes tatsächlich eine starke Abhängigkeit von Unity haben. Das DependencyAttribut ist Teil der Unity-Bibliothek. An diesem Punkt können Sie genauso gut IUnityContainerüberall eine Abhängigkeit übergeben.

Ich bevorzuge es, wenn meine Serviceklassen von Objekten abhängen, die ich vollständig besitze, anstatt überall stark von einer externen Bibliothek abhängig zu sein. Durch die Verwendung von DependencyAttributen werden die Konstruktorsignaturen weniger sauber und einfach.

Darüber hinaus ermöglicht diese Technik das Auflösen benannter Abhängigkeiten zur Laufzeit, ohne die benannten Abhängigkeiten im Konstruktor, in der Anwendungskonfigurationsdatei oder in der Verwendung fest codieren zu müssen. Dies InjectionParametersind alles Methoden, die wissen müssen, welche benannte Abhängigkeit zur Entwurfszeit verwendet werden soll.

Bearbeiten (19.09.2016): Für diejenigen, die sich fragen könnten, weiß der Container, dass er sich selbst übergeben muss, wenn Sie eine IUnityContainerAbhängigkeit anfordern , wie in der StrategyResolverKonstruktorsignatur gezeigt .

Bearbeiten (20.10.2018): Hier ist eine andere Möglichkeit, einfach eine Factory zu verwenden:

public class SomeStrategyFactory : ISomeStrategyFactory
{
    private IStrategy _stratA;
    private IStrategy _stratB;

    public SomeFactory(IStrategyA stratA, IStrategyB stratB)
    {
        _stratA = stratA;
        _stratB = stratB;
    }

    public IStrategy GetStrategy(string namedStrategy){
        if (namedStrategy == "A") return _stratA;
        if (namedStrategy == "B") return _stratB;
    }
}

public interface IStrategy {
    void Execute();
}

public interface IStrategyA : IStrategy {}

public interface IStrategyB : IStrategy {}

public class StrategyA : IStrategyA {
    public void Execute(){}
}

public class StrategyB : IStrategyB {
    public void Execute() {}
}

Verwendung:

public class SomeClass : ISomeClass
{
    public SomeClass(ISomeStrategyFactory strategyFactory){

        IStrategy strat = strategyFactory.GetStrategy("HelloStrategy");
        strat.Execute();

    }
}

Anmeldung:

container.RegisterType<ISomeStrategyFactory, SomeStrategyFactory>();
container.RegisterType<IStrategyA, StrategyA>();
container.RegisterType<IStrategyB, StrategyB>();
container.RegisterType<ISomeClass, SomeClass>();

Dieser zweite Vorschlag ist dasselbe, verwendet jedoch das werkseitige Entwurfsmuster.

Hoffe das hilft!

TchiYuan
quelle
1
Ich mag dieses Muster besser als InjectionConstructor, aber ist es nicht Service Locator, der hinter StrategyResolver versteckt ist?
MistyK
Es sieht aus wie eine Art Service Locator, aber ich denke auch nicht. Sie verwenden den StrategyResolver nur als Hilfsmittel, um das Ende der Story des Strategiemusters zu implementieren. Der StrategyResolver ist nicht Ihr Abhängigkeitsinjektionsbehälter. Wenn Sie anfangen, etwas so Perverses zu tun, wie alle Ihre Abhängigkeiten als benannte Abhängigkeiten zu laden und sie mit dem StrategyResolver zu finden, dann ja, in diesem Fall verwenden Sie dies wirklich nur als Anti-Pattern für den Service Locator. Wenn Sie nicht missbrauchen und sich an seinen Zweck halten, wird Ihr Code schön, ohne von Ihrer DI-Technologie abhängig zu sein.
TchiYuan
Der StrategyResolver ist als Abhängigkeit in Ihrem Container registriert. Die Abhängigkeit wird durch den Konstruktor injiziert. Wenn Ihre aktuelle Klasse in Ihrem Container registriert ist, kann Ihr Container die benötigten Abhängigkeiten einfügen, indem Sie sich die Konstruktorparameter Ihrer Klassen ansehen.
TchiYuan
2
Eine Verbesserung (um den Missbrauch zu begrenzen) könnte darin bestehen, die IStrategyResolver-Schnittstelle einzuschränken. Anstatt in der Lage zu sein, irgendetwas zu lösen, könnten TSie einfach eine Methode habenIActionHandler ResolveMyStrategy(string strategyName).
Botis
1
Meine zwei Cent ... Ich habe etwas in diese Richtung implementiert, aber anstatt das IUnityContaineran den Resolver-Typ zu übergeben, habe ich ein Func<string, ISomeInterface> resolverArgument als Konstruktor verwendet. So können Sie die Auflösung an das DI-Projektmodul delegieren und somit unabhängig vom verwendeten Container sein. Registrierung : container.RegisterInstance<IStrategyResolver>(new StrategyResolver(key => container.Resolve<ISomeInteface>(key))). Auch dieser Ansatz ist restriktiver gegenüber einem möglichen Missbrauch von Service Locator.
Miguel A. Arilla
3

Sie sollten ParameterOverrides verwenden können

var repository = IOC.Container.Resolve<IRepository>("Client");
var clientModel = IOC.Container.Resolve<ClientModel>(new ParameterOverrides<ClientModel> { {"dataAccess", repository } } );

edit: Ich bin mir nicht sicher, warum Sie den UnityContainer weitergeben - persönlich fügen wir unsere Abhängigkeiten in den Konstruktor selbst ein (was nach dem, was ich gesehen habe, "normal" ist). Unabhängig davon können Sie in Ihren RegisterType- und Resolve-Methoden einen Namen angeben.

IOC.Container.RegisterType<IRepository, GenericRepository>("Client");
IOC.Container.Resolve<IRepository>("Client");

und es gibt Ihnen den Typ, den Sie für diesen Namen registriert haben.

Kyle W.
quelle
Das würde funktionieren, aber es bedeutet, dass die Ebene, die über das clientModel Bescheid weiß, auch wissen muss, wie ein Repository ist und wie es aussieht. Ich muss das Repository von der Ebene abstrahieren, die über das 'clientModel' Bescheid weiß. (Tatsächlich besteht der Sinn des clientModel darin, das Repository für meine Service-Schicht abstrakt zu machen.
Vaccano
1
Ich möchte direkt in den Konstruktor injizieren. (Darum geht es bei dieser Frage.) Ich habe nur 2 IRepository-Zuordnungen. Ich suche nach einem Weg, um der Einheit zu helfen, zwischen ihnen zu unterscheiden.
Vaccano
@Kyle - Die Angabe von Namen beim Auflösungsaufruf ist nicht transitiv. Beim Auflösen von <ClientModel, "Client"> wird <IRepository, "Client"> nicht automatisch aufgelöst. Der Container muss konfiguriert sein, um die richtigen Namen verwenden zu können.
Chris Tavares
2

Tun Sie dies nicht - erstellen Sie einfach ein class ClientRepository : GenericRepository { }und verwenden Sie das Type-System.

mcintyre321
quelle