Webframeworkk/ASP.NET Core/Dependency Inversion

1. Einführung: Was sind Services?

In ASP.NET Core MVC sind Services Klassen, die für die Implementierung der Kern-Geschäftslogik Ihrer Anwendung verantwortlich sind. Sie sind darauf ausgelegt, wiederverwendbar, eigenständig und unabhängig von spezifischen Controllern oder Views zu sein.

Hauptaufgaben von Services

  • Kapselung der Geschäftslogik: Trennung komplexer Operationen von der Präsentationsschicht (Controller/Views).
  • Wiederverwendbarkeit: Ein Service kann von mehreren Controllern genutzt werden (DRY-Prinzip).
  • Testbarkeit: Services können leicht isoliert getestet werden (Unit Tests).
  • Datenzugriff: Kommunikation mit Datenbanken oder APIs.
  • Integration: Interaktion mit externen Systemen.

2. Theoretische Grundlagen: DIP, IoC und DI

Um Dependency Injection zu verstehen, müssen wir drei grundlegende Prinzipien betrachten:

Dependency Inversion Principle (DIP)

DIP besagt, dass High-Level-Module nicht von Low-Level-Modulen abhängen sollten. Beide sollten von Abstraktionen (Interfaces) abhängen.

  • Ziel: Lose Kopplung, Flexibilität.

Beispiel (Lichtschalter-Analogie):

  • Ohne DIP: Ein Lichtschalter ist fest mit einer speziellen Glühbirne verdrahtet. Wenn man die Birne wechseln will, muss man den Schalter ändern.
  • Mit DIP: Es gibt eine Steckdose (Interface). Der Schalter funktioniert mit allem, was in die Steckdose passt (Glühbirne, Ventilator).

Inversion of Control (IoC)

IoC bedeutet, die Kontrolle über die Objekterstellung und -verwaltung von der Anwendung an ein Framework oder einen Container abzugeben.

  • Motto: "Don't call us, we will call you."

Dependency Injection (DI)

DI ist ein Entwurfsmuster und eine konkrete Umsetzung von IoC. Abhängigkeiten werden einer Klasse von außen (meist durch einen DI-Container) zur Verfügung gestellt ("injiziert"), anstatt dass die Klasse sie selbst erstellt.


3. Service-Lebenszyklen (Lifetimes)

In ASP.NET Core definieren Sie bei der Registrierung eines Services dessen Lebensdauer. Es gibt drei Hauptoptionen:

1. Transient (Flüchtig)

  • Erstellung: Jedes Mal neu, wenn der Service angefordert wird.
  • Nutzung: Für leichte, zustandslose Services.
  • Registrierung: builder.Services.AddTransient<IMyService, MyService>();

2. Scoped (Bereichsbezogen)

  • Erstellung: Einmal pro Request (z.B. HTTP-Anfrage).
  • Nutzung: Der Standard für Web-Anwendungen (z.B. Datenbank-Kontexte, User-Daten). Alle Komponenten innerhalb eines Requests teilen sich dieselbe Instanz.
  • Registrierung: builder.Services.AddScoped<IMyService, MyService>();

3. Singleton (Einzelstück)

  • Erstellung: Einmalig für die gesamte Laufzeit der Anwendung (beim ersten Abruf).
  • Nutzung: Für globale Zustände, Caches oder Konfigurationen.
  • Registrierung: builder.Services.AddSingleton<IMyService, MyService>();

4. Implementierung in ASP.NET Core

Hier sehen wir uns an, wie man Services definiert, registriert und injiziert.

Schritt 1: Interface und Implementierung definieren

// ServiceContracts (Interface)
namespace ServiceContracts
{
    public interface ICitiesService
    {
        List<string> GetCities();
    }
}

// Services (Implementierung)
namespace Services
{
    public class CitiesService : ICitiesService
    {
        private List<string> _cities;

        public CitiesService()
        {
            _cities = new List<string>() { "London", "Paris", "New York", "Tokyo", "Rome" };
        }

        public List<string> GetCities()
        {
            return _cities;
        }
    }
}

Schritt 2: Registrierung im DI-Container (Program.cs)

var builder = WebApplication.CreateBuilder(args);

// Registrierung des Services als Transient (Beispiel)
builder.Services.AddTransient<ICitiesService, CitiesService>();

// Weitere Dienste...
builder.Services.AddControllersWithViews();

Schritt 3: Injektion im Controller (Constructor Injection)

Das ist die empfohlene Methode. Der DI-Container löst die Abhängigkeit automatisch auf.

public class HomeController : Controller
{
    private readonly ICitiesService _citiesService;

    // Der Service wird über den Konstruktor injiziert
    public HomeController(ICitiesService citiesService)
    {
        _citiesService = citiesService;
    }

    [Route("/")]
    public IActionResult Index()
    {
        // Verwendung des Services
        List<string> cities = _citiesService.GetCities();
        return View(cities);
    }
}

5. Arten der Dependency Injection

ASP.NET Core unterstützt verschiedene Arten der Injektion:

  1. Constructor Injection (Empfohlen):
    • Abhängigkeiten werden über den Konstruktor übergeben.
    • Stellt sicher, dass die Klasse validiert instanziiert wird.
  1. Property Injection:
    • Nutzung des [FromServices] Attributs an Properties.
    • Sinnvoll für optionale Abhängigkeiten (weniger gebräuchlich).
  1. Method Injection:
    • Übergabe als Parameter an eine Methode.
  1. Action Method Injection:
    • Direkt in der Controller-Action mit [FromServices].
    • Nützlich, wenn der Service nur in einer einzigen Action benötigt wird.
public IActionResult Index([FromServices] IUserService userService)
{
    // ...
}

6. Best Practices

  • Interfaces nutzen: Abhängigkeiten sollten immer auf Interfaces basieren, nicht auf konkreten Klassen.
  • Constructor Injection bevorzugen: Es ist die sauberste Form der DI.
  • Service Locator vermeiden: Rufen Sie nicht manuell GetService() auf (außer in speziellen Factory-Szenarien).
  • Lebenszyklen beachten: Vermeiden Sie Captive Dependencies (z.B. einen Scoped Service in einen Singleton injizieren – das führt zu Fehlern).
  • Kein globaler State: Vermeiden Sie statische Klassen für User-Daten; nutzen Sie stattdessen Scoped Services oder HttpContext.Items.

7. Autofac (Alternative)

Obwohl ASP.NET Core einen eingebauten Container hat, bietet Autofac erweiterte Funktionen (z.B. Property Injection, Decorators, erweiterte Scopes).

Integration von Autofac

  1. NuGet-Paket Autofac.Extensions.DependencyInjection installieren.
  2. In Program.cs konfigurieren:
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
    // Registrierung mit Autofac-Syntax
    containerBuilder.RegisterType<CitiesService>()
        .As<ICitiesService>()
        .InstancePerLifetimeScope(); // Entspricht Scoped
});

Mapping der Lebenszyklen

  • InstancePerDependency() = Transient
  • InstancePerLifetimeScope() = Scoped
  • SingleInstance() = Singleton

Zusammenfassung

DI ist ein mächtiges Werkzeug, um lose gekoppelte, testbare und wartbare Software zu schreiben. Durch das Verständnis der Prinzipien (DIP, IoC) und der korrekten Anwendung der Lebenszyklen (Transient, Scoped, Singleton) können Sie robuste ASP.NET Core Anwendungen entwickeln.


Kategorien: Keine
Zuletzt aktualisiert am 18.02.2026 21:27