Mit dem folgenden Servicekonstruktor
public class Service : IService
{
public Service(IOtherService service1, IAnotherOne service2, string arg)
{
}
}
Welche Möglichkeiten haben Sie, die Parameter mithilfe des .NET Core IOC-Mechanismus zu übergeben?
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));
Gibt es einen anderen Weg?
Antworten:
Der Ausdrucksparameter ( in diesem Fall x ) des Factory-Delegaten ist a
IServiceProvider
.Verwenden Sie dies, um die Abhängigkeiten aufzulösen.
_serviceCollection.AddSingleton<IService>(x => new Service(x.GetRequiredService<IOtherService>(), x.GetRequiredService<IAnotherOne>(), ""));
Der Werksdelegierte ist ein verzögerter Aufruf. Wann immer der Typ aufgelöst werden soll, wird der abgeschlossene Provider als Delegate-Parameter übergeben.
quelle
.WithParameter("argument", "");
Microsoft.Extensions.DependencyInjection.Abstractions
Pakets (also keine container-spezifischen Abhängigkeiten)Es ist zu beachten, dass die empfohlene Methode die Verwendung des Optionsmusters ist . Es gibt jedoch Anwendungsfälle, in denen dies unpraktisch ist (wenn Parameter nur zur Laufzeit bekannt sind, nicht zur Start- / Kompilierungszeit) oder Sie eine Abhängigkeit dynamisch ersetzen müssen.
Dies ist sehr nützlich, wenn Sie eine einzelne Abhängigkeit ersetzen müssen (sei es eine Zeichenfolge, eine Ganzzahl oder eine andere Art von Abhängigkeit) oder wenn Sie eine Bibliothek eines Drittanbieters verwenden, die nur Zeichenfolgen- / Ganzzahlparameter akzeptiert und Laufzeitparameter benötigen.
Sie können CreateInstance (IServiceProvider, Object []) als Verknüpfungshand versuchen
(nicht sicher, ob es mit Zeichenfolgenparametern /Werttypen / Grundelementen(int, float, string)funktioniert, nichtgetestet)(Sie haben es einfach ausprobiert und bestätigt, dass es funktioniert, auch mit mehrere Zeichenfolgenparameter), anstatt jede einzelne Abhängigkeit von Hand aufzulösen:_serviceCollection.AddSingleton<IService>(x => ActivatorUtilities.CreateInstance<Service>(x, ""); );
Die Parameter (letzter Parameter von
CreateInstance<T>
/CreateInstance
) definieren die Parameter, die ersetzt werden sollen (nicht vom Anbieter aufgelöst). Sie werden von links nach rechts angewendet, sobald sie angezeigt werden (dh die erste Zeichenfolge wird durch den ersten Parameter vom Typ "Zeichenfolge" des zu instanziierenden Typs ersetzt).ActivatorUtilities.CreateInstance<Service>
wird an vielen Stellen verwendet, um einen Dienst aufzulösen und eine der Standardregistrierungen für diese einzelne Aktivierung zu ersetzen.Wenn Sie zum Beispiel haben eine Klasse mit dem Namen
MyService
, und es hatIOtherService
,ILogger<MyService>
als Abhängigkeiten und Sie wollen den Dienst lösen , aber den Standard - Service ersetzenIOtherService
(sagen seinOtherServiceA
) mitOtherServiceB
, man könnte etwas tun , wie:myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())
Dann wird der erste Parameter
IOtherService
wird erhaltenOtherServiceB
injiziert, anstattOtherServiceA
aber die übrigen Parameter aus dem Behälter kommen.Dies ist hilfreich, wenn Sie viele Abhängigkeiten haben und nur eine einzelne speziell behandeln möchten (dh einen datenbankspezifischen Anbieter durch einen Wert ersetzen möchten, der während der Anforderung oder für einen bestimmten Benutzer konfiguriert wurde, was Sie nur zur Laufzeit und während einer Anforderung und wissen nicht wenn die Anwendung erstellt / gestartet wird).
Sie können stattdessen auch die ActivatorUtilities.CreateFactory-Methode (Type, Type []) verwenden , um die Factory-Methode zu erstellen, da sie eine bessere Leistung bietet. GitHub Reference and Benchmark .
Später ist eine nützlich, wenn der Typ sehr häufig aufgelöst wird (z. B. in SignalR und anderen Szenarien mit hohen Anforderungen). Grundsätzlich würden Sie ein
ObjectFactory
Via erstellenvar myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });
Speichern Sie es dann (als Variable usw.) und rufen Sie es bei Bedarf auf
## Update: Ich habe es selbst versucht, um zu bestätigen, dass es auch mit Zeichenfolgen und Ganzzahlen funktioniert, und es funktioniert tatsächlich. Hier das konkrete Beispiel, mit dem ich getestet habe:
class Program { static void Main(string[] args) { var services = new ServiceCollection(); services.AddTransient<HelloWorldService>(); services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow")); var provider = services.BuildServiceProvider(); var demoService = provider.GetRequiredService<DemoService>(); Console.WriteLine($"Output: {demoService.HelloWorld()}"); Console.ReadKey(); } } public class DemoService { private readonly HelloWorldService helloWorldService; private readonly string firstname; private readonly string lastname; public DemoService(HelloWorldService helloWorldService, string firstname, string lastname) { this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService)); this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname)); this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname)); } public string HelloWorld() { return this.helloWorldService.Hello(firstName, lastName); } } public class HelloWorldService { public string Hello(string name) => $"Hello {name}"; public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}"; } // Just a helper method to shorten code registration code static class ServiceProviderExtensions { public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => ActivatorUtilities.CreateInstance<T>(provider, parameters); }
Druckt
quelle
.AddControllersAsServices
sei denn, es wird verwendet, was dasControllerActivatorProvider
mitServiceBasedControllerActivator
ActivatorUtilities.CreateInstance()
ist genau das, was ich brauchte. Vielen Dank!public string HelloWorld()
fehlte die MethodenimplementierungWenn Sie sich mit der Neuerung des Dienstes nicht wohl fühlen, können Sie das
Parameter Object
Muster verwenden.Extrahieren Sie also den String-Parameter in einen eigenen Typ
public class ServiceArgs { public string Arg1 {get; set;} }
Und der Konstruktor jetzt wird es so aussehen
public Service(IOtherService service1, IAnotherOne service2, ServiceArgs args) { }
Und das Setup
_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; }); _serviceCollection.AddSingleton<IOtherService , OtherService>(); _serviceCollection.AddSingleton<IAnotherOne , AnotherOne>(); _serviceCollection.AddSingleton<IService, Service>();
Der erste Vorteil besteht darin, dass Sie die
new Service(...
Aufrufe nicht ändern müssen, wenn Sie den Servicekonstruktor ändern und neue Services hinzufügen müssen . Ein weiterer Vorteil ist, dass das Setup etwas sauberer ist.Für einen Konstruktor mit einem oder zwei Parametern kann dies jedoch zu viel sein.
quelle