Dependency-Injection in Framework Studio
Microsoft DI-Container
Framework Studio verwendet den Dependency Injection Container von Microsoft. Dieser ist ein zentrales Element für die Verwaltung von Abhängigkeiten in modernen .NET-Anwendungen.
Ein DI-Container wird durch die Klasse ServiceProvider
bzw. das Interface IServiceProvider
abgebildet.
Deswegen wird statt dem allgemeinen Begriff DI-Container oft der spezifische Begriff ServiceProvider verwendet.
Der DI-Container unterstützt verschiedene Lebenszyklen für die Registrierung von Diensten:
Singleton: Die Instanz wird einmalig erstellt und für die gesamte Lebensdauer der Anwendung wiederverwendet. Geeignet für zustandslose oder gemeinsam genutzte Ressourcen.
Scoped: Die Instanz wird einmal pro Scope erstellt, z.B. pro Web-Request. Ideal für kontextbezogene Daten, die während eines Requests konsistent bleiben sollen.
Transient: Bei jeder Anforderung wird eine neue Instanz erstellt. Geeignet für kurzlebige, zustandslose Objekte.
Weitere Informationen und Beispiele finden sich in der offiziellen Microsoft-Dokumentation zu Dependency Injection.
Richtlinien für die Dependency Injection finden sich ebenfalls in der Microsoft-Dokumentation.
Hier ein kurzer Auszug der wichtigsten Regeln:
- Singleton - darf kein Scoped - darf kein Transient verwenden. (Captive Dependency)
- Abhängigkeiten über den Konstruktor auflösen - wenn möglich nicht den ServiceProvider injecten.
- Transitive Objekte dürfen nicht
IDisposable
sein. - Singleton Objekte müssen threadsafe implementiert werden.
Scope = Request
Bei Dependency Injection wird zwischen verschiedenen Scopes - die Lebensdauer eines registrierten Services/Klasse - unterschieden. Bei Verwendung der bereitgestellten Dependency Injection Infrastruktur von FS ist es wichtig zu wissen, dass dieser Scope:
- das zu verarbeitende Event/einen Request im Broker umfasst bzw.
- bei den Services innerhalb einer Servicemethode erstellt wird.
Der Scope bezieht sich deshalb nicht auf die Session.
FSServiceProvider
Der FSServiceProvider
ist eine Brücken-Technologie, die einen statischen Zugriff auf den DI-Container ermöglicht.
Dies wird mithilfe von AsyncLocal
umgesetzt und folgt dem sogenannten Ambient Pattern.
Dadurch kann der aktuelle ServiceProvider kontextabhängig bereitgestellt werden, ohne ihn explizit durch alle Methoden und Klassen reichen zu müssen.
Der FSServiceProvider
stellt immer einen Scoped Service-Provider bereit.
Der Zugriff erfolgt über die statische Eigenschaft FSServiceProvider.Current
.
Vorteile:
- Ermöglicht den Zugriff auf den DI-Container auch dort, wo keine direkte Übergabe möglich ist (z.B. bestehendem Code).
- Kontext wird pro Ausführungspfad (z.B. pro Request/Event) sauber getrennt.
Nachteile:
- Verführt dazu, Abhängigkeiten nicht explizit zu machen und den ServiceProvider als Service-Locator zu missbrauchen.
- Erschwert das Testen und die Nachvollziehbarkeit der Abhängigkeiten.
In einer idealen Architektur werden alle Abhängigkeiten explizit über den Konstruktor injiziert. Der DI-Container verwaltet alle Instanzen und deren Lebenszyklen selbstständig, ohne dass ein statischer Zugriff notwendig ist. Dadurch bleiben die Abhängigkeiten transparent und der Code besser testbar.
Der Einsatz von FSServiceProvider
sollte daher als Brücken-Technologie verstanden werden.
In einem separaten Kapitel sind konkrete Beispiele für den Einsatz des FSServiceProvider beschrieben.
FSServiceRegistrar
Für die Registrierung eigener Services im DI-Container kann ein FSServiceRegistrar implementiert werden.
Framework Studio bietet dafür das Interface IFSServiceRegistrar
an.
Erzeugen Sie eine Modular Component mit dem Interface FS.Hosting.Shared.IFSServiceRegistrar
und implmentieren Sie die Methode RegisterServices()
.
public virtual IServiceCollection RegisterServices(IServiceCollection services)
{
services.AddSingleton<FullName.Interface1, ClassImplementation1>(); // Aufruf liefert immer dieselbe Instanz
services.AddScoped<FullName.Interface2, ClassImplementation2>(); // Aufruf liefert in einem Scope immer dieselbe Instanz
services.AddTransient<FullName.Interface3, ClassImplementation3>(); // Aufruf liefert immer neue Instanz
// ...
return services;
}
Beim Start der Anwendung wird nach allen Modular Components gescannt, die dieses Interface implementieren und die Registrierungen ausgeführt.
Important
Bei einem neuen FSServiceRegistrar muss der GlobalObjects CompileStep kompiliert werden.