Minimal APIs in ASP.NET Core 6: Web-APIs jetzt anders?

Mit der Veröffentlichung von .NET 6 hat Microsoft nicht nur neue Features und Performance-Verbesserungen umgesetzt, sondern uns auch ein neues Entwicklungsmodell für Web-APIs bereitgestellt.
Hinweis: Alle Code-Beispiele sind auf Github verfügbar!

In diesem Artikel:

Das neue, zusätzliche Entwicklungsmodell nennt sich Minimal-APIs und soll es erlauben, mit möglichst wenigen Zeilen Code Web-APIs zu erstellen. Dazu hat Microsoft Veränderungen an der Sprache C# vorgenommen, sowie eine neue Builder-Klasse für Minimal-APIs erstellt. Dadurch sind wir in der Lage, ähnlich wie in anderen Sprachen (zum Beispiel Go oder NodeJS), Web-APIs in einer einzigen Datei und mit kompaktem Code zu kreieren.

Kompaktere Form für Web-APIs

Schauen wir uns dazu kurz ein Beispiel an. Hier sehen wir eine Minimal-API, basierend auf der Standard-Projektvorlage in .NET 6. Bei diesem Beispiel wurde nichts weg gelassen, es kommen genau diese vier Zeilen, ohne usings-statements, Namespace- oder Klassen-Definition als Ergebnis heraus.

				
					var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
				
			

Dies verdeutlicht, dass wir nun in der Lage sind, mit vier Zeilen Code eine Web-API zu erstellen, die einen HTTP(S)-Endpunkt bereitstellt.

Doch was genau wurde gemacht, um dies zu ermöglichen? Hier gehen wir etwas auf die Details ein und schauen uns die verschiedenen Features an.

Globale & implizite using-Statements

Mit globalen using-Statements können wir Namespaces an einer zentralen Stelle definieren und diese werden dann in allen Dateien automatisch importiert. Zum Beispiel können wir mit dieser Zeile den System-Namespace in alle Dateien importieren.

				
					global using System;
				
			

Durch Implicit usings werden Namespaces importiert, die in einem SDK definiert werden. Ab .NET 6 werden dadurch für verschiedene Projekttypen automatisch die relevanten Namespaces importiert. Web-Projekte importieren Namespaces wie zum Beispiel System.Net.Http.Json oder Microsoft.AspNetCore.Http automatisch.

Durch diese beiden Features konnten so schon einige Zeilen eingespart werden und der Zugriff auf beispielsweise den Builder, ist ohne ein explizites using möglich.

Globale und implizite usings können übrigens ebenfalls in anderen Projekttypen genutzt werden und uns auch dort Code ersparen.

Top Level Statements

Mit Top Level Statements können wir Code, den wir normalerweise in einer Main-Methode haben, direkt in einer Datei schreiben, ohne eine Klasse oder einen Namespace zu definieren. Top Level Statements gibt es schon seit C# 9. Es ist daher kein neues Feature in .NET 6, aber erhält im Zusammenhang mit Minimal-APIs nochmal eine neue Wichtigkeit.

Wichtig ist: Top Level Statements funktionieren nur mit dem Inhalt der Main-Methode! Der Compiler generiert hier automatisch eine Klasse Program mit einer Methode Main, welche den Code beinhaltet.

Lambda als Web-API-Endpunkt-Handler

Im Gegensatz zu bisherigen, klassischen Web-APIs, schreiben wir bei Minimal-APIs nun keine Controller und Actions mehr, sondern hier werden Lambda-Ausdrücke als Endpunkt-Handler genutzt. Im folgenden Beispiel wird ein Lambda genutzt, welches den String Hello World zurück gibt. Der Lambda-Ausdruck wird bei einem HTTP-Get auf / ausgeführt.

				
					app.MapGet("/", () => "Hello World!");

				
			

Über die Argumente des Lambda-Ausdrucks können wir auf Route-Parameter, den Body des Requests oder uns über Dependency Injection auf Abhängigkeiten zugreifen.

				
					app.MapGet("/{name}", (string name) => $"Hello {name}!");
app.MapPost("/", (InputModel model) => $"Hello {model.Name}!");
app.MapGet("/greet/{name}", (string name, IGreeter greeter) => greeter.Greet(name));
				
			

Abhängigkeiten können auf einer ServiceCollection, die auf dem Builder liegt, registriert werden und dann über ein ganz normales Argument an den Lambda-Ausdruck genutzt werden.

				
					builder.Services.AddSingleton<IGreeter, Greeter>();
...
...
app.MapGet("/greet/{name}", (string name, IGreeter greeter) => greeter.Greet(name));
				
			

Mit C# 10 sind wir nun auch in der Lage, Lambdas mit Attributen zu annotieren. Wir können daher in Minimal-APIs, ähnlich wie bei klassischen Web-APIs, auch das Authorize-Attribut benutzen, um einen Endpunkt zu schützen. Wir können natürlich auch weitere Attribute hinzufügen, zum Beispiel, um unsere Swagger-Dokumentation mit weiteren Daten anzureichern.

				
					app.MapGet("/{name}", [Authorize] (string name) => $"Hello {name}!");
				
			

Statt einer Lambda-Funktion können wir den Code auch in eine separate Funktion auslagern und dann auf diese Funktion verweisen. Dies kann bei mehreren Endpunkten nützlich sein, um den Code besser zu strukturieren.

				
					app.MapGet("/{name}", Handler.Index);

public static class Handler
{
    public static string Index(string name, IGreeter greeter)
    {
        return greeter.Greet(name);
    }
}
				
			

Wenn wir aus unserem Endpunkt-Handler einen HTTP-Statuscode zurück geben wollen, so können wir die statische Results-Klasse verwenden, welche uns Zugriff auf diverse Standard-Statuscodes gibt oder aber auch die Möglichkeit bietet, einen benutzerdefinierten Statuscode zurückzugeben.

				
					app.MapGet("/TT", () => Results.Redirect("https://www.thinktecture.com/"));
app.MapGet("/Teapot", () => Results.StatusCode(StatusCodes.Status418ImATeapot));
				
			

Vor- und Nachteile von Minimal-APIs

Doch was sind denn nun die Vor- und Nachteile und wann soll ich Minimal-APIs nutzen und wann soll ich lieber eine klassische Web-API erstellen?

Minimal-APIs erlauben es uns sehr schnell und effektiv, einen neuen Service zu implementieren. Sie sind also optimal, wenn wir in einer Microservices-Architektur sind, bei der ein Service wirklich nur sehr wenige Endpunkte besitzt. Ist bereits absehbar, dass unsere API mehrere/viele Endpunkte haben wird, so sollte man lieber gleich mit einer normalen Web-API anfangen. Denn eine Minimal-API mit vielen Endpunkten kann sehr schnell sehr unübersichtlich werden.

Des Weiteren ist zu beachten, dass Minimal-APIs nicht auf der bisherigen ASP.NET-MVC-Architektur aufbauen und daher Features wie zum Beispiel Action-Filter oder Model-Binding entweder gar nicht vorhanden sind oder anders funktionieren. Hier sind wir also leider nicht in der Lage, bestehenden Code für Model-Binding wiederzuverwenden, sondern müssen diesen gegebenenfalls neu schreiben. Dies gilt besonders dann, wenn die API andere Formate als JSON unterstützen muss (z.B. Form-Data oder XML), da Minimal-APIs “out of the box” nur JSON können.

Durch den Verzicht von MVC als Unterbau sind Minimal-APIs theoretisch auch etwas schneller und effizienter (kürzere Latenz, weniger Speicherallokation), da pro Request weniger Code ausgeführt werden muss. Praktisch ist der Geschwindigkeitsvorteil aber nicht relevant. Ein simpler Controller mit einer “Hello World”-Action kann lediglich 3-5% weniger Requests pro Sekunde verarbeiten als die “Hello World”-Minimal-API. Der wesentliche Faktor bei der Performance ist die Implementierung der Action/des Handlers und da helfen uns weder MVC noch Minimal-APIs.

Resumee

Minimal-APIs bieten uns die Möglichkeit, sehr schnell und effektiv kleine APIs und Proof-of-Concepts zu erstellen. Dadurch, dass sich der Code in einer Datei befindet, ist dieser auch schnell und effektiv anpassbar. Wird die API jedoch größer und komplexer, muss man sich über eine geeignet Code-Struktur Gedanken machen und gegebenenfalls wieder auf klassische MVC basierende Web-APIs wechseln.

Mehr Artikel zu .NET, .NET CORE
Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
.NET
pg

Discriminated Unions in .NET: Integration with Frameworks and Libraries

A key aspect of adopting any new pattern is understanding how it interacts with the surrounding application infrastructure. When using Discriminated Unions, questions arise: How can a Result union be serialized to JSON? How can an OrderState union be persisted using Entity Framework Core? This article explores practical integration strategies with common .NET frameworks.
02.11.2025
.NET
pg

Advanced Value Object Patterns in .NET

While basic value objects solve primitive obsession, complex domain requirements need sophisticated modeling techniques. This article explores advanced patterns using Thinktecture.Runtime.Extensions to tackle real-world scenarios: open-ended dates for employment contracts, composite file identifiers across storage systems, recurring anniversaries without year components, and geographical jurisdictions using discriminated unions.
19.10.2025
.NET
pg

Discriminated Unions in .NET: Modeling States and Variants

Domain models often involve concepts that exist in multiple distinct states or variations. Traditional approaches using enums and nullable properties can lead to invalid states and scattered logic. This article explores how discriminated unions provide a structured, type-safe way to model domain variants in .NET, aligning perfectly with Domain-Driven Design principles while enforcing invariants at the type level.
06.10.2025
.NET
pg

Smart Enums in .NET: Integration with Frameworks and Libraries

Learn how to seamlessly integrate Smart Enums with essential .NET frameworks and libraries. This article covers practical solutions for JSON serialization, ASP.NET Core model binding for both Minimal APIs and MVC controllers, and Entity Framework Core persistence using value converters. Discover how Thinktecture.Runtime.Extensions provides dedicated packages to eliminate integration friction and maintain type safety across your application stack.
21.09.2025
.NET
pg

Value Objects in .NET: Enhancing Business Semantics

Value objects are fundamental building blocks in Domain-Driven Design, serving far more than simple data wrappers. This article explores their strategic importance in bridging technical code and business concepts, enforcing domain rules, and fostering clearer communication with domain experts. Learn how to build robust aggregates, cultivate ubiquitous language, and encapsulate domain-specific behavior using Thinktecture.Runtime.Extensions in .NET applications.
16.09.2025
.NET
pg

Pattern Matching with Discriminated Unions in .NET

Traditional C# pattern matching with switch statements and if/else chains is error-prone and doesn't guarantee exhaustive handling of all cases. When you add new types or states, it's easy to miss updating conditional logic, leading to runtime bugs. The library Thinktecture.Runtime.Extensions solves this with built-in Switch and Map methods for discriminated unions that enforce compile-time exhaustiveness checking.
26.08.2025