Einen POST-Insert-Endpunkt mit Validierung in .NET Aspire auf Linux hinzufügen
[[academy-video-youtube({"vid": "oAMMHR8kKnw", "start_time": "0", "title": "Adding a POST Insert Endpoint with Validation in .NET Aspire on Linux", "creator": "Tim Corey", "length": "20m 31s"})]]
Daten aus einer API zu lesen ist nur die halbe Geschichte. Letztendlich muss jede Anwendung in der Lage sein, neue Datensätze zu akzeptieren, was bedeutet, einen POST-Endpunkt zu erstellen, der einen Anfragetext empfängt, die Eingabe validiert, sie in der Datenbank speichert und einen sinnvollen Statuscode zurückgibt. Den Validierungsschritt während des Prototypings zu überspringen, ist verlockend, aber Produktions-APIs, die nicht validierte Eingaben akzeptieren, werden zu einer Quelle korrupter Daten, die schwieriger zu bereinigen als zu verhindern sind.
In seinem Video "Hinzufügen eines POST-Insert-Endpunkts mit Validierung in .NET Aspire unter Linux" fügt Tim Corey einen Insert-Endpunkt zur Tiny Ticket API hinzu, erstellt einen dedizierten Eingabedatensatztyp, verbindet .NETs integrierte Validierungspipeline für minimale APIs und konfiguriert Swagger so, dass es automatisch startet, sobald die API startet. Die Episode umfasst den kompletten Zyklus vom gespeicherten Verfahren bis zum getesteten Endpunkt, einschließlich des Validierungsfehlerrückgabeformats, das .NET von Haus aus liefert. Wenn Sie die C# auf Linux-Serie verfolgen oder zum ersten Mal Schreiboperationen zu einer minimalen API hinzufügen, führt dieser Artikel durch jeden Schritt.
Erstellen des Insert-Datensatztyps
[1:46 - 4:43] Bevor Tim den Endpoint erstellt, erstellt er ein Datenübertragungsobjekt, das die Struktur einer Insert-Anfrage repräsentiert. Das vorhandene TicketModel enthält Felder wie Id und CreatedDate, die die Datenbank automatisch generiert. Diese in einem POST-Body zu akzeptieren, würde entweder ignoriert werden oder Konflikte verursachen, daher beschränkt ein separater Typ die Eingabe nur auf die Felder, die der Anrufer bereitstellen sollte.
public record TicketInsertRecord(string Title, string Description, int Priority);
public record TicketInsertRecord(string Title, string Description, int Priority);
Die Verwendung eines record anstelle eines class ist eine bewusste Entscheidung. Records bieten standardmäßig wertbasierte Gleichheit und Unveränderlichkeit, was der Semantik eines Anfrage-Payloads entspricht: Die Daten kommen an, werden validiert, an die Datenbank übergeben und dazwischen nie verändert. Die drei Eigenschaften (Titel, Beschreibung, Priorität) korrelieren direkt mit den Parametern der gespeicherten Prozedur spTickets_Insert.
Mapping des POST-Endpunkts
[4:43 - 9:51] Mit dem definierten Record-Typ folgt die Registrierung des Endpoints demselben Muster wie die GET-Routen, verwendet aber MapPost und bindet den Anfragekörper an den Insert-Record:
app.MapPost("/api/tickets", async (TicketInsertRecord ticket, IDbConnection db) =>
{
await db.SaveDataAsync("spTickets_Insert", ticket);
return Results.NoContent();
});
app.MapPost("/api/tickets", async (TicketInsertRecord ticket, IDbConnection db) =>
{
await db.SaveDataAsync("spTickets_Insert", ticket);
return Results.NoContent();
});
Beachten Sie, dass die Route ein /api/tickets ohne ID-Segment ist, was den REST-Konventionen entspricht, bei denen POST zu einer Sammlungs-URL eine neue Ressource erstellt. Der Handler ruft die gespeicherte Prozedur mit dem gesamten ticket-Objekt als Parameter-Bag auf. Dapper mappt die Eigenschaften des Records zu den SQL-Parametern nach Namen.
Die Rückkehr von Results.NoContent() sendet einen 204-Statuscode. Tim erklärt die Begründung: Der Insert ist erfolgreich, aber es gibt nichts Bedeutungsvolles, das im Antwortkörper zurückzugeben wäre. Einige APIs geben das neu erstellte Objekt mit einem 201 Created-Status und einem Location-Header zurück, der auf die neue Ressource verweist, was eine gültige Alternative darstellt. Für das Tiny Ticket-Projekt hält 204 die Dinge schlank.
Testen des Inserts über Swagger
[9:51 - 14:43] Tim launches the project and navigates to Swagger. Der POST-Endpoint erscheint mit einem Anfragetextschema, das den Eigenschaften von TicketInsertRecord entspricht. Er füllt ein Testticket mit Titel, Beschreibung und Priorität aus und führt die Anfrage aus.
Ein 204 kehrt zurück, was bestätigt, dass das Insert erfolgreich war. Um zu überprüfen, ob die Daten tatsächlich gespeichert wurden, wechselt er zum GET-All-Endpunkt und führt ihn aus. Das neue Ticket erscheint in der Liste neben den ursprünglichen Testdatensätzen.
Was der Test auch offenbart, ist die Lücke ohne Validierung: Wenn man einen leeren Titel, eine fehlende Beschreibung oder eine Priorität von 99 sendet, wird alles mit einem 204 akzeptiert. Die Datenbank akzeptiert, was auch immer die API sendet. Diese Lücke motiviert den nächsten Abschnitt.
Hinzufügen von eingebauter Validation
[14:43 - 18:28] Beginnend mit .NET 10 unterstützen minimale APIs eine eingebaute Validierungspipeline, die Datenanmerkungsattribute vom Eingabetyp liest und ungültige Anfragen ablehnt, bevor der Handler ausgeführt wird. Tim integriert es in zwei Schritten.
Registrieren Sie zuerst die Validierungsdienste in Program.cs. Diese eine Zeile aktiviert die gesamte Pipeline:
builder.Services.AddValidation();
builder.Services.AddValidation();
Mit dem registrierten Dienst inspiziert das Framework jeden Anfragetext auf Validierungsattribute, bevor der Handler ausgeführt wird. Der zweite Schritt ist das Anmerken des Insert-Datensatzes mit den Regeln, die jedes Feld erfüllen muss:
public record TicketInsertRecord(
[Required, MinLength(1)] string Title,
[Required] string Description,
[Range(1, 5)] int Priority
);
public record TicketInsertRecord(
[Required, MinLength(1)] string Title,
[Required] string Description,
[Range(1, 5)] int Priority
);
[Required] stellt sicher, dass das Feld vorhanden und nicht null ist. [MinLength(1)] verhindert, dass leere Strings die erforderliche Prüfung bestehen (da ein leerer String technisch gesehen nicht null ist). [Range(1, 5)] beschränkt die Priorität auf eine gültige Ebene. Diese Attribute sind dieselben System.ComponentModel.DataAnnotations Typen, die ASP.NET MVC-Controller seit Jahren verwendet haben, aber jetzt in minimalen APIs ohne zusätzliches Middleware funktionieren.
Nach dem Speichern und Neustarten sendet Tim eine Anfrage mit leerem Titel und Priorität 10. Die Antwort kommt als 400 Bad Request mit einem strukturierten Fehlrkörper zurück:
{
"errors": {
"Title": ["The Title field is required."],
"Priority": ["The field Priority must be between 1 and 5."]
}
}
Die Validierungspipeline kurzschließt die Anfrage, bevor der Handler ausgeführt wird, sodass keine ungültigen Daten die Datenbank erreichen. Die Fehlerrückgabe folgt dem RFC 7807 Problem Details-Format, das API-Verbraucher programmatisch analysieren können.
Automatisches Starten von Swagger beim Start
[19:44 - 20:31] Eine kleine Verbesserung der Lebensqualität schließt die Episode ab. Jedes Mal, wenn Tim die API startete, musste er manuell /swagger in die Browser-URL eingeben. Um das zu automatisieren, öffnet er die Properties/launchSettings.json des API-Projekts und fügt eine launchUrl Eigenschaft zum HTTPS-Profil hinzu:
{
"profiles": {
"https": {
"launchUrl": "swagger"
}
}
}
Beim nächsten Start öffnet sich der Browser direkt zur Swagger UI anstatt der Standardseite. Das spart ein paar Sekunden pro Debug-Zyklus, was sich über eine ganze Entwicklungssitzung summiert.
Abschluss
[20:09 - 20:31] Das Hinzufügen eines POST-Endpunkts zu einer minimalen API beinhaltet das Erstellen eines dedizierten Eingabe-Records, das Zuordnen zu MapPost mit der Sammlungs-URL und das Aufrufen der gespeicherten Prozedur mit dem Record als Parameterobjekt. Die Validierung in .NET 10 erfordert eine Dienstregistrierung und standardmäßige Datenanmerkungsattribute für die Record-Eigenschaften. Das Framework übernimmt automatisiert die 400-Antwortformulierung.
Seriennavigation: Dieser Artikel ist Teil der C# auf Linux-Serie, die die Tiny Ticket-App aufbaut. Vorher: Hinzufügen eines Get By ID Endpunkts. Nächste: Hinzufügen eines PUT-Update-Endpunkts.
Beispiel Tipp: Wenn Ihre Insert gespeicherte Prozedur die neue Record-ID zurückgibt, ändern Sie die Rückgabe von Results.NoContent() zu Results.Created($"/api/tickets/{newId}", result), um Anrufern einen 201-Status mit einem Speicherort-Header zu geben, den sie verwenden können, um die erstellte Ressource abzurufen.
Sehen Sie sich das vollständige Video auf seinem YouTube Kanal an und gewinnen Sie weitere Einblicke in den Aufbau von Schreib-Endpunkten in der C# auf Linux-Serie.
