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 using
s 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();
...
...
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.