S

super-dev — full-stack

@super-dev.app · Full-stack · ★ offen für Aufträge
12,8k Abonnenten·47 Videos·Aktiv seit 2017

Full-Stack-Entwickler .NET / Angular mit DevOps-Erfahrung auf Azure Cloud. Auch zu Hause mit Flutter + Firebase. Ich entwickle Produkte, deploye sie und überwache sie.

Zurück zu den Artikeln
ƒ()
.NET
.NET

Minimal APIs + EF Core: eine saubere .NET 8 API

Artikel 2 von 5 — Modernes .NET
Minimal APIs, CQRS, gRPC, Source Generators: sauberes und testbares .NET 8, ohne unnötigen Overhead.

Minimal APIs haben einen schlechten Ruf: Man hält sie für reine Wegwerf-Demos. Tatsächlich liefern sie mit etwas Disziplin eine .NET 8-API, die lesbarer und testbarer ist als ein klassischer Controller — sofern man nicht alles in Program.cs stopft.

Strukturierung mit Route Groups

Die Anfängerfalle besteht darin, dreißig app.MapGet -Aufrufe in Program.cs zu stapeln. Die Lösung lässt sich in einem Wort zusammenfassen: `MapGroup` . Jede Ressource erhält ihre eigene Gruppe mit Präfix, Filtern und Metadaten, definiert in einer dedizierten Extension-Methode:

C#
1public static class TodoEndpoints
2{
3 public static RouteGroupBuilder MapTodos(this IEndpointRouteBuilder app)
4 {
5 var group = app.MapGroup("/todos")
6 .WithTags("Todos")
7 .WithOpenApi();
8
9 group.MapGet("/", GetAllAsync);
10 group.MapGet("/{id:int}", GetByIdAsync);
11 group.MapPost("/", CreateAsync);
12
13 return group;
14 }
15
16 private static async Task<Ok<List<Todo>>> GetAllAsync(AppDbContext db) =>
17 TypedResults.Ok(await db.Todos.AsNoTracking().ToListAsync());
18}

Das Program.cs reduziert sich dann auf app.MapTodos(); — ein Einstiegspunkt pro Ressource, der Rest lebt in kohärenten Dateien.

DbContext und Migrationen

EF Core bleibt das Rückgrat des Datenzugriffs. Der DbContext wird über AddDbContext registriert, das Modell in OnModelCreating definiert, und vor allem lässt man das Schema niemals manuell driften: Jede Änderung durchläuft eine versionierte Migration.

C#
1builder.Services.AddDbContext<AppDbContext>(options =>
2 options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));

Die Migration wird anschließend mit dotnet ef migrations add InitialCreate generiert und beim Start mit db.Database.MigrateAsync() angewendet — niemals EnsureCreated , das die gesamte History umgeht. Die offizielle Dokumentation beschreibt den Workflow im guide EF Core migrations .

Typisierte Ergebnisse und Validierung

Hier spielen die Minimal APIs ihren eigentlichen Vorteil aus. Anstatt ein opakes IActionResult zurückzugeben, gibt man eine Union typisierter Ergebnisse zurück: Die Signatur dokumentiert die möglichen HTTP-Statuscodes, und OpenAPI exponiert sie automatisch.

C#
1private static async Task<Results<Created<Todo>, ValidationProblem>> CreateAsync(
2 CreateTodoRequest request, AppDbContext db)
3{
4 if (string.IsNullOrWhiteSpace(request.Title))
5 {
6 return TypedResults.ValidationProblem(new Dictionary<string, string[]>
7 {
8 ["title"] = ["Le titre est obligatoire."],
9 });
10 }
11
12 var todo = new Todo { Title = request.Title };
13
14 db.Todos.Add(todo);
15 await db.SaveChangesAsync();
16
17 return TypedResults.Created($"/todos/{todo.Id}", todo);
18}

Der Rückgabetyp Results<Created<Todo>, ValidationProblem> ist selbstdokumentierend : Keine redundanten [ProducesResponseType] -Attribute erforderlich.

Alles testbar halten

Sobald die Handler als statische Methoden extrahiert sind, die ihre Abhängigkeiten als Parameter erhalten, lassen sie sich trivial ohne HTTP-Server testen: Man instanziiert einen AppDbContext mit dem In-Memory- oder SQLite-Provider, ruft den Handler auf und inspiziert das TypedResults . Für End-to-End-Integrationstests lädt WebApplicationFactory<T> die vollständige Anwendung im Speicher und ermöglicht das Aufrufen der echten Endpunkte.

Eine Minimal API ist keine minderwertige API. Gut in Gruppen und typisierte Ergebnisse aufgeteilt, bietet sie weniger Boilerplate für mehr Garantien — und genau das erwartet man von einem modernen Framework.
super-dev — portfolio.app
// Weiter in .NET
{ }
Einen .NET 8-Monolithen stranglieren, ohne alles zu zerstören
9 min • 3,2k Aufrufe
CQRS und Vertical Slices ohne Overengineering
7 min • 2,3k Aufrufe
gRPC zwischen .NET-Microservices
9 min • 1,8k Aufrufe