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:

Minimal APIs in ASP.NET Core 6: Web-APIs jetzt anders?
Boris Wilhelms ist Consultant bei Thinktecture und hat sich auf .NET Core and Identity Management fokussiert.
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.

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.

Diese Artikel könnten Sie interessieren
.NET
Incremental Roslyn Source Generators: High-Level API – ForAttributeWithMetadataName – Part 8

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
.NET
Integrating AI Power into Your .NET Applications with the Semantic Kernel Toolkit – an Early View

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
.NET 7 Performance: Regular Expressions – Part 2

.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
.NET
.NET 7 Performance: Introduction and Runtime Optimizations – Part 1

.NET 7 Performance: Introduction and Runtime Optimizations – Part 1

.NET 7 is fast. Superfast. All the teams at Microsoft working on .NET are keen to improve the performance and do so every year with each new .NET release. Though this time the achievements are really impressive. In this series of short articles, we want to explore some of the most significant performance updates in .NET and look at how that may affect our own projects. This first article is taking a deep look under the hood of the compiler and the runtime to look for some remarkably interesting and significant updates.
28.03.2023
.NET
Incremental Roslyn Source Generators: Using Additional Files – Part 7

Incremental Roslyn Source Generators: Using Additional Files – Part 7

In the previous article the Source Generator itself needed a 3rd-party library Newtonsoft.Json in order to generate new source code. The JSON-strings were hard-coded inside the Source Generator for simplicity reasons. In this article we will see how to process not just .NET code, but also other files, like JSON or XML.
21.03.2023
.NET
Understanding and Controlling the Blazor WebAssembly Startup Process

Understanding and Controlling the Blazor WebAssembly Startup Process

There are a lot of things going on in the background, when a Blazor WebAssembly application is being started. In some cases you might want to take a bit more control over that process. One example might be the wish to display a loading screen for applications that take some time for initial preparation, or when users are on a slow internet connection. However, in order to control something, we need to understand what is happening first. This article takes you down the rabbit hole of how a Blazor WASM application starts up.
07.03.2023