Arno Fahrenkamp deep · dive · tech notes

22. Mai 2026 · 4 min lesen

Dependency Injection in ASP.NET Core

Ein praktischer Einstieg in das DI-Modell von ASP.NET Core mit Lifetimes, Sequenzdiagramm, Beispielcode und Quellen.

Dependency Injection ist in ASP.NET Core kein Zusatz, sondern der Normalfall. Das Framework bringt den Container direkt mit, und viele Bausteine im Ökosystem sind genau darauf ausgelegt: Abhängigkeiten werden von außen geliefert, nicht intern erzeugt.

Das klingt banal, ist aber einer der wichtigsten Designentscheidungen im gesamten Stack.

Warum DI überhaupt?

Ohne DI erzeugt eine Klasse ihre Abhängigkeiten selbst:

public class OrderService
{
    private readonly EmailSender _emailSender = new EmailSender();

    public void PlaceOrder()
    {
        _emailSender.SendConfirmation();
    }
}

Das funktioniert, aber es koppelt die Klasse hart an eine konkrete Implementierung. Tests, Austausch und Erweiterung werden unnötig unbequem.

Mit DI bekommt die Klasse die Abhängigkeit injiziert:

public interface INotificationSender
{
    void SendConfirmation(string orderId);
}

public class OrderService
{
    private readonly INotificationSender _notificationSender;

    public OrderService(INotificationSender notificationSender)
    {
        _notificationSender = notificationSender;
    }

    public void PlaceOrder(string orderId)
    {
        _notificationSender.SendConfirmation(orderId);
    }
}

Registrierung im Container

In ASP.NET Core passiert die Registrierung typischerweise in Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<INotificationSender, EmailSender>();
builder.Services.AddScoped<OrderService>();

var app = builder.Build();
app.MapControllers();
app.Run();

Damit sagt man dem Container:

  • wenn INotificationSender gebraucht wird, nimm EmailSender
  • OrderService darf ebenfalls vom Container gebaut werden

Konstruktor-Injection im Controller

[ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{
    private readonly OrderService _orderService;

    public OrdersController(OrderService orderService)
    {
        _orderService = orderService;
    }

    [HttpPost("{orderId}")]
    public IActionResult Create(string orderId)
    {
        _orderService.PlaceOrder(orderId);
        return Ok();
    }
}

Der Controller bleibt dünn. Er nimmt den Request an, delegiert an den Service und liefert eine Response zurück.

Lebensdauern

ASP.NET Core kennt drei Standard-Lifetimes:

Transient

Eine neue Instanz bei jeder Auflösung.

builder.Services.AddTransient<IMyService, MyService>();

Gut für:

  • kleine, zustandslose Helfer
  • Objekte ohne teure Initialisierung

Scoped

Eine Instanz pro HTTP-Request.

builder.Services.AddScoped<IMyService, MyService>();

Gut für:

  • DbContext
  • Request-bezogene Fachlogik
  • Services, die innerhalb eines Requests konsistent bleiben sollen

Singleton

Eine Instanz für die gesamte Laufzeit der Anwendung.

builder.Services.AddSingleton<IMyCache, MyCache>();

Gut für:

  • Cache
  • Konfiguration
  • reine, threadsichere Infrastruktur

Ablauf als Sequenz

sequenceDiagram
    autonumber
    participant Client
    participant ASP as ASP.NET Core
    participant DI as DI-Container
    participant C as OrdersController
    participant S as OrderService
    participant N as EmailSender

    Client->>ASP: POST /orders/123
    ASP->>DI: resolve OrdersController
    DI->>DI: resolve OrderService
    DI->>DI: resolve INotificationSender
    DI->>N: create EmailSender
    DI->>S: create OrderService(EmailSender)
    DI->>C: create OrdersController(OrderService)
    C->>S: PlaceOrder("123")
    S->>N: SendConfirmation("123")
    ASP->>Client: 200 OK

Objektgraph

flowchart TD
    Controller[OrdersController] --> Service[OrderService]
    Service --> Sender[INotificationSender]
    Sender --> Email[EmailSender]

Das ist die eigentliche Stärke von DI:

  • der Controller kennt nur den Service
  • der Service kennt nur das Interface
  • die konkrete Implementierung bleibt austauschbar

Ein realistischer Mini-Stack

public interface IOrderRepository
{
    void Save(string orderId);
}

public class SqlOrderRepository : IOrderRepository
{
    public void Save(string orderId)
    {
        Console.WriteLine($"Saving order {orderId} to SQL");
    }
}

public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly INotificationSender _notificationSender;

    public OrderService(
        IOrderRepository repository,
        INotificationSender notificationSender)
    {
        _repository = repository;
        _notificationSender = notificationSender;
    }

    public void PlaceOrder(string orderId)
    {
        _repository.Save(orderId);
        _notificationSender.SendConfirmation(orderId);
    }
}

Registrierung dazu:

builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddScoped<INotificationSender, EmailSender>();
builder.Services.AddScoped<OrderService>();

Was DI löst — und was nicht

DI löst nur die Frage:

Wie bekommen Objekte ihre Abhängigkeiten?

DI löst nicht automatisch:

  • schlechte Domänenmodellierung
  • vermischte Zuständigkeiten
  • unklare Schichten
  • schlechte Teststrategie

Man kann mit DI auch schlechten Code sauber verpacken. Das Pattern ist kein Architektur-Ersatz.

Typische Fehler

1. Zu dicke Controller

Wenn Controller fachliche Logik enthalten, kippt die Struktur schnell.

2. Falsche Lifetime

Der Klassiker ist ein Singleton, der intern etwas Scopedes festhält. Das führt in ASP.NET Core schnell zu Problemen.

3. Konstruktor mit zu vielen Parametern

Wenn ein Service acht oder mehr Abhängigkeiten braucht, ist das oft kein DI-Problem, sondern ein Designsignal.

Fazit

ASP.NET Core macht DI zum Standard, nicht zur Option. Wer es sauber nutzt, bekommt testbaren, austauschbaren und besser strukturierten Code.