Table of Contents

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.