Dependency Injection
Hinter Dependency Injection (kurz DI) steht das Ziel Abhängigkeiten von Objekten und Komponenten auf ein Minimum zu reduzieren. Es gibt unterschiedliche Arten um Abhängigkeiten zu injizieren: „Constructor Injection“, „Setter Injection“ und „Interface Injection“. Nachfolgend gehe ich primär auf „Constructor Injection“ ein, da dies auch vom DI-Container, der in .NET Core enthalten ist, unterstützt wird.
Im Container wird das Interface sowie der implementierte Typ mit dem Lebenszyklus (Singleton, Transient oder Scoped) bekanntgegeben. Der Container kümmert sich dann darum, die passende Instanz zu erzeugen bzw. verwendet eine bereits erzeugte wieder. Wird ein Service erstellt und dieser hat einen Konstruktor mit Parametern, dann werden diese ebenfalls aus dem Container geholt bzw. erstellt. Ein Vorteil dieser Vorgehensweise ist, dass es keine versteckten Abhängigkeiten gibt, da alle Abhängigkeiten im Konstruktor bekanntgegeben werden müssen. Weiters erleichtert dies das Testing ungemein, da anstatt von echten Implementierungen einfach Mock-Objekte eingeschleust werden können (dazu in einem der nächsten Beiträge etwas mehr).
Für die nachfolgenden Beispiele muss das NuGet-Package „Microsoft.Extensions.DependencyInjection“ installiert werden.
Als erstes muss eine ServiceCollection erzeugt werden. Dieser werden die einzelnen Services sowie Implementierungen bekanntgegeben. Anschließend wird die Methode „BuildServiceProvider“ aufgerufen, die den eigentlichen Container erstellt.
var services = new ServiceCollection();
services.AddTransient<IMyService, MyService>();
var provider = services.BuildServiceProvider();
var myService = serviceProvider.GetService<IMyService>();
So einfach ist es :-). Schauen wir uns die einzelnen Lebenszyklen kurz an.
Singleton
Es wird genau eine Instanz erstellt und diese immer wiederverwendet.
Transient
Es wird je Anforderung der Services eine neue Instanz erzeugt.
Scoped
Dieser ist von den drei verfügbaren der am schwierigsten zu erklärende. Der Service-Provider hat eine Methode „CreateScope“. Jeder Scope erzeugt einen eigenen Service-Provider als Child vom Parent-Scope. Pro Scope verhält sich der Scoped-Service wie ein Singleton. Wenn der Scope nicht mehr benötigt wird, dann wird dieser disposed, wodurch die Scoped-Services ebenfalls disposed werden.
using (var scope = provider.CreateScope())
{
var myScopedService = provider.GetService<IMyScopedService>();
}
Wie man sieht ist zumindest die Basis von DI überschaubar. Neben dem oben beschriebenen DI-Container gibt es noch andere, die wesentlich mächtiger sind (z.B. Autofac). Vom grundsätzlichen Aufbau sind allerdings alle sehr ähnlich.
Die Vorteile für den Einsatz eines DI-Containers sprechen eindeutig für sich.