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
Database Access with Sessions
.NET
KP-round

Data Access in .NET Native AOT with Sessions

.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023
Old computer with native code
.NET
KP-round

Native AOT with ASP.NET Core – Overview

Originally introduced in .NET 7, Native AOT can be used with ASP.NET Core in the upcoming .NET 8 release. In this post, we look at the benefits and drawbacks from a general perspective and perform measurements to quantify the improvements on different platforms.
02.11.2023
.NET
KP-round

Optimize ASP.NET Core memory with DATAS

.NET 8 introduces a new Garbage Collector feature called DATAS for Server GC mode - let's make some benchmarks and check how it fits into the big picture.
09.10.2023
.NET CORE
pg

Incremental Roslyn Source Generators: High-Level API – ForAttributeWithMetadataName – Part 8

With the version 4.3.1 of Microsoft.CodeAnalysis.* Roslyn provides a new high-level API - the method "ForAttributeWithMetadataName". Although it is just 1 method, still, it addresses one of the biggest performance issue with Source Generators.
16.05.2023
AI
favicon

Integrating AI Power into Your .NET Applications with the Semantic Kernel Toolkit – an Early View

With the rise of powerful AI models and services, questions come up on how to integrate those into our applications and make reasonable use of them. While other languages like Python already have popular and feature-rich libraries like LangChain, we are missing these in .NET and C#. But there is a new kid on the block that might change this situation. Welcome Semantic Kernel by Microsoft!
03.05.2023
.NET
sg

.NET 7 Performance: Regular Expressions – Part 2

There is this popular quote by Jamie Zawinski: Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems."

In this second article of our short performance series, we want to look at the latter one of those problems.
25.04.2023