Ich habe Dienste, die von derselben Schnittstelle abgeleitet sind.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
In der Regel können Sie mit anderen IoC-Containern Unity
konkrete Implementierungen von einigen registrieren Key
, die sie unterscheiden.
Wie registriere ich diese Dienste in ASP.NET Core und löse sie zur Laufzeit basierend auf einem Schlüssel auf?
Ich sehe keine Add
Service-Methoden, die einen key
oder name
Parameter annehmen , die normalerweise zur Unterscheidung der konkreten Implementierung verwendet werden.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services of the same interface?
}
public MyController:Controller
{
public void DoSomething(string key)
{
// How do I resolve the service by key?
}
}
Ist das Factory-Muster hier die einzige Option?
Update1
Ich habe den Artikel hier durchgesehen , der zeigt, wie das Factory-Muster verwendet wird, um Service-Instanzen abzurufen, wenn wir mehrere konkrete Implementierungen haben. Es ist jedoch immer noch keine vollständige Lösung. Wenn ich die _serviceProvider.GetService()
Methode aufrufe, kann ich keine Daten in den Konstruktor einfügen.
Betrachten Sie zum Beispiel Folgendes:
public class ServiceA : IService
{
private string _efConnectionString;
ServiceA(string efconnectionString)
{
_efConnecttionString = efConnectionString;
}
}
public class ServiceB : IService
{
private string _mongoConnectionString;
public ServiceB(string mongoConnectionString)
{
_mongoConnectionString = mongoConnectionString;
}
}
public class ServiceC : IService
{
private string _someOtherConnectionString
public ServiceC(string someOtherConnectionString)
{
_someOtherConnectionString = someOtherConnectionString;
}
}
Wie kann _serviceProvider.GetService()
die entsprechende Verbindungszeichenfolge eingefügt werden? In Unity oder einer anderen IoC-Bibliothek können wir dies bei der Typregistrierung tun. Ich kann jedoch IOption verwenden , für die ich alle Einstellungen vornehmen muss. Ich kann keine bestimmte Verbindungszeichenfolge in den Dienst einfügen.
Beachten Sie auch, dass ich versuche, die Verwendung anderer Container (einschließlich Unity) zu vermeiden, da ich dann auch alles andere (z. B. Controller) beim neuen Container registrieren muss.
Die Verwendung des Factory-Musters zum Erstellen von Dienstinstanzen ist außerdem gegen DIP gerichtet, da dadurch die Anzahl der Abhängigkeiten erhöht wird, über die ein Client hier Details verfügt .
Ich denke, der Standard-DI in ASP.NET Core fehlen zwei Dinge:
- Die Möglichkeit, Instanzen mithilfe eines Schlüssels zu registrieren
- Die Möglichkeit, statische Daten während der Registrierung in Konstruktoren einzufügen
Update1
zu einer anderen Frage verschoben werden, da das Injizieren von Dingen in Konstruktoren sich stark von der Ermittlung des zu konstruierenden Objekts unterscheidetAntworten:
Ich habe eine einfache Problemumgehung durchgeführt,
Func
als ich mich in dieser Situation befand.Erklären Sie zunächst einen gemeinsamen Delegierten:
Richten Sie dann in Ihrem
Startup.cs
die mehreren konkreten Registrierungen und eine manuelle Zuordnung dieser Typen ein:Und verwenden Sie es aus jeder bei DI registrierten Klasse:
Beachten Sie, dass in diesem Beispiel der Schlüssel für die Auflösung der Einfachheit halber und weil OP insbesondere nach diesem Fall gefragt hat, eine Zeichenfolge ist.
Sie können jedoch einen beliebigen benutzerdefinierten Auflösungstyp als Schlüssel verwenden, da Sie normalerweise keinen großen n-case-Schalter benötigen, der Ihren Code verrottet. Hängt davon ab, wie Ihre App skaliert.
quelle
Eine andere Möglichkeit ist die Verwendung der Erweiterungsmethode
GetServices
vonMicrosoft.Extensions.DependencyInjection
.Registrieren Sie Ihre Dienste als:
Dann lösen Sie mit ein wenig Linq:
oder
(vorausgesetzt, das
IService
hat eine String-Eigenschaft namens "Name")Stellen Sie sicher, zu haben
using Microsoft.Extensions.DependencyInjection;
Aktualisieren
AspNet 2.1 Quelle:
GetServices
quelle
IEnumerable<IService>
Es wird nicht unterstützt von
Microsoft.Extensions.DependencyInjection
.Sie können jedoch auch einen anderen Mechanismus zur Abhängigkeitsinjektion anschließen, z. B.
StructureMap
"Startseite" und " GitHub-Projekt" .Es ist überhaupt nicht schwer:
Fügen Sie StructureMap eine Abhängigkeit hinzu in
project.json
:Injizieren Sie es in die ASP.NET-Pipeline
ConfigureServices
und registrieren Sie Ihre Klassen (siehe Dokumente).Um eine benannte Instanz zu erhalten, müssen Sie die anfordern
IContainer
Das ist es.
Für das zu erstellende Beispiel benötigen Sie
quelle
Sie haben Recht, der integrierte ASP.NET Core-Container hat nicht das Konzept, mehrere Dienste zu registrieren und dann einen bestimmten abzurufen. Wie Sie vorschlagen, ist in diesem Fall eine Factory die einzige echte Lösung.
Alternativ können Sie zu einem Container eines Drittanbieters wie Unity oder StructureMap wechseln, der die von Ihnen benötigte Lösung bietet (hier dokumentiert: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- the-default-services-container ).
quelle
Ich habe das gleiche Problem und möchte mitteilen, wie ich es gelöst habe und warum.
Wie Sie erwähnt haben, gibt es zwei Probleme. Der Erste:
Welche Möglichkeiten haben wir? Leute schlagen zwei vor:
Verwenden Sie eine benutzerdefinierte Fabrik (wie
_myFactory.GetServiceByKey(key)
)Verwenden Sie einen anderen DI-Motor (wie
_unityContainer.Resolve<IService>(key)
)Tatsächlich sind beide Optionen Fabriken, da jeder IoC-Container auch eine Fabrik ist (hoch konfigurierbar und jedoch kompliziert). Und es scheint mir, dass andere Optionen auch Variationen des Factory-Musters sind.
Welche Option ist dann besser? Hier stimme ich @Sock zu, der die Verwendung einer benutzerdefinierten Factory vorgeschlagen hat, und deshalb.
Erstens versuche ich immer zu vermeiden, neue Abhängigkeiten hinzuzufügen, wenn sie nicht wirklich benötigt werden. Daher stimme ich Ihnen in diesem Punkt zu. Darüber hinaus ist die Verwendung von zwei DI-Frameworks schlechter als die Erstellung einer benutzerdefinierten Factory-Abstraktion. Im zweiten Fall müssen Sie eine neue Paketabhängigkeit hinzufügen (wie Unity), aber abhängig von einer neuen Factory-Schnittstelle ist dies hier weniger böse. Ich glaube, die Hauptidee von ASP.NET Core DI ist die Einfachheit. Es werden nur minimale Funktionen nach dem KISS-Prinzip beibehalten . Wenn Sie eine zusätzliche Funktion benötigen, dann DIY oder verwenden Sie ein entsprechendes Plungin , das die gewünschte Funktion implementiert (Open Closed Principle).
Zweitens müssen wir häufig viele benannte Abhängigkeiten für einen einzelnen Dienst einfügen. Im Falle von Unity müssen Sie möglicherweise Namen für Konstruktorparameter angeben (using
InjectionConstructor
). Diese Registrierung verwendet Reflexion und eine intelligente Logik, um Argumente für den Konstruktor zu erraten. Dies kann auch zu Laufzeitfehlern führen, wenn die Registrierung nicht mit den Konstruktorargumenten übereinstimmt. Auf der anderen Seite haben Sie bei Verwendung Ihrer eigenen Factory die volle Kontrolle darüber, wie die Konstruktorparameter bereitgestellt werden. Es ist besser lesbar und wird beim Kompilieren aufgelöst. Wieder KISS-Prinzip .Das zweite Problem:
Erstens stimme ich Ihnen zu, dass es keine gute Idee ist , abhängig von neuen Dingen wie
IOptions
(und daher vom PaketMicrosoft.Extensions.Options.ConfigurationExtensions
). Ich habe einige Diskussionen darüber gesehen,IOptions
wo es unterschiedliche Meinungen über seinen Nutzen gab. Auch hier versuche ich zu vermeiden, neue Abhängigkeiten hinzuzufügen, wenn sie nicht wirklich benötigt werden. Wird es wirklich gebraucht? Ich denke nicht. Andernfalls müsste jede Implementierung davon abhängen, ohne dass eine eindeutige Notwendigkeit von dieser Implementierung ausgeht (für mich sieht es nach einer Verletzung des ISP aus, wo ich auch Ihnen zustimme). Dies gilt auch etwa in der Fabrik abhängig , aber in diesem Fall ist es kann vermieden werden.Der ASP.NET Core DI bietet zu diesem Zweck eine sehr schöne Überlastung:
quelle
IServiceA
in Ihrem Fall nicht auf Schnittstellen verlassen, die Sie nicht verwenden (ISP) . Da SieIService
nur Methoden von verwenden , sollten Sie nur von abhängig seinIService
.services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
für die erste Klasse undservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
für die zweite.Ich injiziere einfach eine IEnumerable
ConfigureServices in Startup.cs
Dienstordner
MyController.cs
Extensions.cs
quelle
Die meisten Antworten hier verstoßen gegen das Prinzip der Einzelverantwortung (eine Serviceklasse sollte Abhängigkeiten nicht selbst auflösen) und / oder verwenden das Anti-Pattern des Service Locator.
Eine andere Möglichkeit, diese Probleme zu vermeiden, ist:
Ich habe einen Artikel mit weiteren Details geschrieben: Abhängigkeitsinjektion in .NET: Eine Möglichkeit, fehlende benannte Registrierungen zu umgehen
quelle
Etwas spät zu dieser Party, aber hier ist meine Lösung: ...
Startup.cs oder Program.cs wenn generischer Handler ...
IMyInterface von T Interface Setup
Konkrete Implementierungen von IMyInterface von T.
Wenn es ein Problem damit gibt, wird hoffentlich jemand darauf hinweisen, warum dies der falsche Weg ist.
quelle
IMyInterface<CustomerSavedConsumer>
undIMyInterface<ManagerSavedConsumer>
sind verschiedene Diensttypen - dies beantwortet die Frage des OP überhaupt nicht.Anscheinend können Sie einfach IEnumerable Ihrer Serviceschnittstelle einfügen! Suchen Sie dann die gewünschte Instanz mit LINQ.
Mein Beispiel ist für den AWS SNS-Dienst, aber Sie können dasselbe für jeden injizierten Dienst wirklich tun.
Anfang
SNSConfig
appsettings.json
SNS-Fabrik
Jetzt können Sie den SNS-Dienst für die gewünschte Region in Ihrem benutzerdefinierten Dienst oder Controller abrufen
quelle
Ein Fabrikansatz ist sicherlich realisierbar. Ein anderer Ansatz besteht darin, mithilfe der Vererbung einzelne Schnittstellen zu erstellen, die von IService erben, die geerbten Schnittstellen in Ihren IService-Implementierungen zu implementieren und die geerbten Schnittstellen anstelle der Basis zu registrieren. Ob das Hinzufügen einer Vererbungshierarchie oder von Fabriken das "richtige" Muster ist, hängt davon ab, mit wem Sie sprechen. Ich muss dieses Muster häufig verwenden, wenn ich mit mehreren Datenbankanbietern in derselben Anwendung arbeite, die ein generisches Beispiel verwenden, z. B.
IRepository<T>
als Grundlage für den Datenzugriff.Beispielschnittstellen und Implementierungen:
Container:
quelle
Nekromantie.
Ich denke, die Leute hier erfinden das Rad neu - und wenn ich so sagen darf ...
Wenn Sie eine Komponente per Schlüssel registrieren möchten, verwenden Sie einfach ein Wörterbuch:
Und dann registrieren Sie das Wörterbuch bei der Service-Sammlung:
Wenn Sie dann nicht bereit sind, das Wörterbuch abzurufen und per Schlüssel darauf zuzugreifen, können Sie das Wörterbuch ausblenden, indem Sie der Service-Sammlung eine zusätzliche Schlüssel-Suchmethode hinzufügen:
(Die Verwendung von Delegate / Closure sollte einem potenziellen Betreuer eine Chance geben verstehen, was los ist - die Pfeilnotation ist etwas kryptisch)
Jetzt können Sie mit beiden auf Ihre Typen zugreifen
oder
Wie wir sehen können, ist der erste einfach völlig überflüssig, da Sie genau das auch mit einem Wörterbuch tun können, ohne Abschlüsse und AddTransient zu benötigen (und wenn Sie VB verwenden, werden nicht einmal die geschweiften Klammern unterschiedlich sein):
(Einfacher ist besser - vielleicht möchten Sie es aber als Erweiterungsmethode verwenden)
Wenn Ihnen das Wörterbuch nicht gefällt, können Sie Ihre Benutzeroberfläche natürlich auch mit einer Eigenschaft
Name
(oder was auch immer) ausstatten und diese nach Schlüssel nachschlagen:Dies erfordert jedoch eine Änderung der Benutzeroberfläche, um die Eigenschaft zu berücksichtigen, und das Durchlaufen vieler Elemente sollte viel langsamer sein als eine Suche nach assoziativen Arrays (Wörterbuch).
Es ist jedoch schön zu wissen, dass dies ohne Wörterbuch möglich ist.
Dies sind nur meine $ 0,05
quelle
IDispose
implementiert wurde, wer ist für die Entsorgung des Dienstes verantwortlich? Sie haben Wörterbuch alsSingleton
Seit meinem obigen Beitrag bin ich in eine generische Fabrikklasse gewechselt
Verwendung
Implementierung
Erweiterung
quelle
Während es so aussieht, als hätte @Miguel A. Arilla klar darauf hingewiesen und ich für ihn gestimmt, habe ich zusätzlich zu seiner nützlichen Lösung eine andere Lösung erstellt, die ordentlich aussieht, aber viel mehr Arbeit erfordert.
Es hängt definitiv von der obigen Lösung ab. Im Grunde habe ich etwas Ähnliches erstellt
Func<string, IService>>
und esIServiceAccessor
als Schnittstelle bezeichnet. Dann musste ich demIServiceCollection
als solches einige weitere Erweiterungen hinzufügen :Der Service Accessor sieht folgendermaßen aus:
Als Endergebnis können Sie Dienste mit Namen oder benannten Instanzen registrieren, wie wir es früher mit anderen Containern getan haben. Zum Beispiel:
Das reicht vorerst aus, aber um Ihre Arbeit zu vervollständigen, ist es besser, mehr Erweiterungsmethoden hinzuzufügen, um alle Arten von Registrierungen nach demselben Ansatz abzudecken.
Es gab einen anderen Beitrag zu Stackoverflow, aber ich kann ihn nicht finden, in dem das Poster ausführlich erklärt hat, warum diese Funktion nicht unterstützt wird und wie man sie umgeht, im Grunde ähnlich wie bei @Miguel. Es war ein schöner Beitrag, obwohl ich nicht mit jedem Punkt einverstanden bin, weil ich denke, dass es Situationen gibt, in denen Sie wirklich benannte Instanzen benötigen. Ich werde diesen Link hier posten, sobald ich ihn wieder finde.
In der Tat müssen Sie diesen Selector oder Accessor nicht übergeben:
Ich verwende den folgenden Code in meinem Projekt und es hat bisher gut funktioniert.
quelle
Ich habe dafür eine Bibliothek erstellt, die einige nette Funktionen implementiert. Code finden Sie auf GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/
Die Verwendung ist unkompliziert:
Beachten Sie im obigen Beispiel, dass Sie für jede benannte Registrierung auch die Lebensdauer oder Singleton, Scoped oder Transient angeben.
Sie können Dienste auf zwei Arten auflösen, je nachdem, ob Sie damit vertraut sind, dass Ihre Dienste von diesem Paket abhängig sind:
oder
Ich habe diese Bibliothek speziell für die Zusammenarbeit mit Microsoft.Extensions.DependencyInjection entwickelt - zum Beispiel:
Wenn Sie benannte Dienste registrieren, können alle von Ihnen registrierten Typen Konstruktoren mit Parametern haben - diese werden über DI auf die gleiche Weise erfüllt
AddTransient<>
,AddScoped<>
undAddSingleton<>
Methoden funktionieren normalerweise.Für vorübergehende und bereichsspezifische benannte Dienste erstellt die Registrierung eine,
ObjectFactory
damit sie bei Bedarf sehr schnell neue Instanzen dieses Typs aktivieren kann. Dies ist viel schneller als andere Ansätze und entspricht der Vorgehensweise von Microsoft.Extensions.DependencyInjection.quelle
Meine Lösung für das, was es wert ist ... erwog, zu Castle Windsor zu wechseln, da ich nicht sagen kann, dass mir eine der oben genannten Lösungen gefallen hat. Es tut uns leid!!
Erstellen Sie Ihre verschiedenen Implementierungen
Anmeldung
Verwendung von Konstruktoren und Instanzen ...
quelle
Erweiterung der Lösung von @rnrneverdies. Anstelle von ToString () können auch die folgenden Optionen verwendet werden: 1) Bei Implementierung gemeinsamer Eigenschaften 2) Ein von @Craig Brunetti vorgeschlagener Dienst von Diensten.
quelle
Nachdem ich die Antworten hier und Artikel an anderer Stelle gelesen hatte, konnte ich es ohne Schnüre zum Laufen bringen. Wenn Sie mehrere Implementierungen derselben Schnittstelle haben, fügt der DI diese einer Sammlung hinzu, sodass Sie die gewünschte Version mithilfe von aus der Sammlung abrufen können
typeof
.quelle
var serviceA = new ServiceA();
Die Out-of-the-Box-Implementierung bietet dies zwar nicht, aber hier ist ein Beispielprojekt, mit dem Sie benannte Instanzen registrieren und dann INamedServiceFactory in Ihren Code einfügen und Instanzen nach Namen abrufen können. Im Gegensatz zu anderen Facory-Lösungen können Sie hier mehrere Instanzen derselben Implementierung registrieren, die jedoch unterschiedlich konfiguriert sind
https://github.com/macsux/DotNetDINamedInstances
quelle
Wie wäre es mit einem Service für Services?
Wenn wir eine INamedService-Schnittstelle (mit der Eigenschaft .Name) hätten, könnten wir eine IServiceCollection-Erweiterung für .GetService (Zeichenfolgenname) schreiben, wobei die Erweiterung diesen Zeichenfolgenparameter übernehmen und eine .GetServices () für sich selbst und für jede zurückgegebene ausführen würde Suchen Sie die Instanz, deren INamedService.Name mit dem angegebenen Namen übereinstimmt.
So was:
Daher muss Ihr IMyService INamedService implementieren, aber Sie erhalten die gewünschte schlüsselbasierte Auflösung, oder?
Um fair zu sein, scheint es hässlich, diese INamedService-Schnittstelle haben zu müssen. Wenn Sie jedoch noch weiter gehen und die Dinge eleganter gestalten möchten, kann der Code in diesem Code ein [NamedServiceAttribute ("A")] für die Implementierung / Klasse finden Erweiterung, und es würde genauso gut funktionieren. Um noch fairer zu sein, Reflection ist langsam, daher ist eine Optimierung möglicherweise angebracht, aber ehrlich gesagt hätte die DI-Engine dabei helfen sollen. Geschwindigkeit und Einfachheit tragen maßgeblich zu den Gesamtbetriebskosten bei.
Alles in allem ist keine explizite Factory erforderlich, da das Finden eines benannten Service ein so wiederverwendbares Konzept ist und Factory-Klassen nicht als Lösung skaliert werden können. Und ein Func <> scheint in Ordnung zu sein, aber ein Schalterblock ist so bleh , und wieder werden Sie Funcs so oft schreiben, wie Sie Fabriken schreiben würden. Beginnen Sie einfach, wiederverwendbar, mit weniger Code, und wenn sich herausstellt, dass dies für Sie nicht der Fall ist, werden Sie komplex.
quelle
Ich bin auf dasselbe Problem gestoßen und habe mit einer einfachen Erweiterung gearbeitet, um benannte Dienste zuzulassen. Sie finden es hier:
Sie können so viele (benannte) Dienste hinzufügen, wie Sie möchten:
Mit der Bibliothek können Sie auch ein "Factory-Muster" wie folgt einfach implementieren:
Ich hoffe es hilft
quelle
Ich habe meine eigene Erweiterung über die
IServiceCollection
verwendeteWithName
Erweiterung erstellt:ServiceCollectionTypeMapper
eine Singleton - Instanz ist , dass die KartenIService
>NameOfService
>Implementation
wo eine Schnittstelle viele Implementierungen mit unterschiedlichen Namen haben könnte, erlaubt diese Typen zu registrieren , als wir , wenn wee Bedarf lösen können und ist ein anderer Ansatz als Dienste mehrere Entschlossenheit zu wählen , was wir wollen.So registrieren Sie einen neuen Dienst:
Um den Service zu lösen, benötigen wir eine solche Erweiterung
IServiceProvider
.Bei der Lösung:
Denken Sie daran, dass serviceProvider in einen Konstruktor in unserer Anwendung als eingefügt werden kann
IServiceProvider
.Ich hoffe das hilft.
quelle