Tutorial: Erstellen einer minimalen API mit ASP.NET Core
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der ASP.NET Core 8.0-Version dieses Artikels.
Von Rick Anderson und Tom Dykstra
Minimal-APIs sind so entworfen, dass HTTP-APIs mit minimalen Abhängigkeiten erstellt werden. Sie eignen sich ideal für Microservices und Apps, die nur ein Minimum an Dateien, Funktionen und Abhängigkeiten in ASP.NET Core enthalten sollen.
In diesem Tutorial lernen Sie die Grundlagen der Erstellung einer minimalen API mit ASP.NET Core kennen. Ein weiterer Ansatz zum Erstellen von APIs in ASP.NET Core ist die Verwendung von Controllern. Hilfe bei der Entscheidung zwischen minimalen APIs und controllerbasierten APIs finden Sie in der Übersicht über APIs. Ein Tutorial zum Erstellen eines API-Projekts basierend auf Controllern, das weitere Features umfasst, finden Sie unter Erstellen einer Web-API.
Übersicht
In diesem Tutorial wird die folgende API erstellt:
API | Beschreibung | Anforderungstext | Antworttext |
---|---|---|---|
GET /todoitems |
Alle To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/complete |
Abgeschlossene To-Do-Elemente | Keine | Array von To-do-Elementen |
GET /todoitems/{id} |
Ein Element nach ID abrufen | Keine | To-do-Element |
POST /todoitems |
Neues Element hinzufügen | To-do-Element | To-do-Element |
PUT /todoitems/{id} |
Vorhandenes Element aktualisieren | To-do-Element | Keine |
DELETE /todoitems/{id} |
Löschen eines Elements | Keine | Keine |
Voraussetzungen
Visual Studio 2022 Preview mit der Workload ASP.NET- und Webentwicklung
Erstellen eines API-Projekts
Starten Sie Visual Studio 2022 Preview, und wählen Sie Neues Projekt erstellen aus.
Im Dialogfeld Neues Projekt erstellen:
- Geben Sie im Suchfeld
Empty
Nach Vorlagen suchenden Suchbegriff ein. - Wählen Sie die Vorlage ASP.NET Core leer und dann Weiter aus.
- Geben Sie im Suchfeld
Geben Sie dem Projekt den Namen TodoApi, und klicken Sie auf Weiter.
Im Dialogfeld Zusätzliche Informationen:
- Wählen Sie .NET 8.0 (Langfristiger Support) aus
- Deaktivieren Sie Keine Anweisungen der obersten Ebene verwenden.
- Klicken Sie auf Erstellen
Untersuchen des Codes
Die Datei Program.cs
enthält den folgenden Code:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Der vorangehende Code:
- Erstellt eine Instanz von WebApplicationBuilder und WebApplication mit vorkonfigurierten Standardwerten.
- Erstellt den HTTP GET-Endpunkt
/
, derHello World!
zurückgibt:
Ausführen der App
Drücken Sie STRG+F5, um die Ausführung ohne den Debugger zu starten.
In Visual Studio wird das folgende Dialogfeld angezeigt:
Wählen Sie Ja aus, wenn Sie dem IIS Express-SLL-Zertifikat vertrauen möchten.
Das folgende Dialogfeld wird angezeigt:
Klicken Sie auf Ja, wenn Sie zustimmen möchten, dass das Entwicklungszertifikat vertrauenswürdig ist.
Informationen dazu, wie Sie dem Firefox-Browser vertrauen, finden Sie unter Firefox-Zertifikatfehler SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio startet den Kestrel-Webserver und öffnet ein Browserfenster.
Hello World!
wird im Browser angezeigt. Die Datei Program.cs
enthält eine Minimal-, aber dennoch vollständige App.
Hinzufügen von NuGet-Paketen
NuGet-Pakete müssen hinzugefügt werden, um die in diesem Tutorial verwendete Datenbank und Diagnose zu unterstützen.
- Klicken Sie im Menü Extras auf NuGet-Paket-Manager > NuGet-Pakete für Projektmappe verwalten.
- Wählen Sie die Registerkarte Durchsuchen aus.
- Geben Sie Microsoft.EntityFrameworkCore.InMemory in das Suchfeld ein, und wählen Sie
Microsoft.EntityFrameworkCore.InMemory
aus. - Aktivieren Sie das Kontrollkästchen Projekt im rechten Bereich, und klicken Sie dann auf Installieren.
- Folgen Sie den vorstehenden Anweisungen, um das
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-Paket hinzuzufügen.
Die Modell- und Datenbankkontextklassen
Erstellen Sie im Projektordner eine Datei mit dem Namen Todo.cs
und dem folgenden Code:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Der vorherige Code erstellt das Modell für diese App. Ein Modell ist eine Klasse, welche die in der App verwalteten Daten repräsentiert.
Erstellen Sie eine Datei mit dem Namen TodoDb.cs
und dem folgenden Code:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Der vorherige Code definiert den Datenbankkontext, also die Hauptklasse, die die Entity Framework-Funktionen für ein Datenmodell koordiniert. Diese Klasse wird von der Microsoft.EntityFrameworkCore.DbContext-Klasse abgeleitet.
Hinzufügen des API-Codes
Ersetzen Sie den Inhalt der Datei Program.cs
durch den folgenden Code:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Der folgende hervorgehobene Code fügt den Datenbankkontext zum Container für die Abhängigkeitsinjektion (Dependency Injection, DI) hinzu und ermöglicht die Anzeige von datenbankbezogenen Ausnahmen:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Der DI-Container bietet Zugriff auf den Datenbankkontext und andere Dienste.
In diesem Tutorial werden Endpunkte Explorer- und HTTP-Dateien verwendet, um die API zu testen.
Testen der Übertragung von Daten
Der folgende Code in Program.cs
erstellt den HTTP POST-Endpunkt /todoitems
zum Hinzufügen von Daten zur In-Memory-Datenbank:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Führen Sie die App aus. Der Browser zeigt einen 404-Fehler an, da kein /
-Endpunkt mehr vorhanden ist.
Fügen Sie der App mithilfe des POST-Endpunkts Daten hinzu.
Wählen Sie Ansicht>Weitere Fenster>Endpunkt-Explorer aus.
Klicken Sie mit der rechten Maustaste auf den POST-Endpunkt, und wählen Sie Anforderung generieren aus.
Im Projektordner wird eine neue Datei mit dem Namen
TodoApi.http
erstellt, deren Inhalt dem folgenden Beispiel ähnelt:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- Die erste Zeile erstellt eine Variable, die für alle Endpunkte verwendet wird.
- In der nächsten Zeile wird eine POST-Anforderung definiert.
- Die Zeile mit dem dreifachen Hashtag (
###
) ist ein Anforderungstrennzeichen: Was danach kommt, gilt für eine andere Anforderung.
Die POST-Anforderung benötigt Header und einen Text. Um diese Teile der Anforderung zu definieren, fügen Sie die folgenden Zeilen unmittelbar nach der POST-Anforderungszeile hinzu:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Der vorangehende Code fügt einen Content-Type-Header und einen JS-ON-Anforderungstext hinzu. Die Datei „TodoApi.http“ sollte nun wie im folgenden Beispiel aussehen, jedoch mit Ihrer Portnummer:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Führen Sie die App aus.
Wählen Sie den Link Anforderung senden aus. Er befindet sich oberhalb der
POST
-Anforderungszeile.Die POST-Anforderung wird an die App gesendet, und die Antwort wird im Bereich Antwort angezeigt.
Untersuchen der GET-Endpunkte
Die Beispiel-App implementiert mehrere GET-Endpunkte durch Aufrufen von MapGet
:
API | Beschreibung | Anforderungstext | Antworttext |
---|---|---|---|
GET /todoitems |
Alle To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/complete |
Alle abgeschlossenen To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/{id} |
Ein Element nach ID abrufen | Keine | To-do-Element |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Testen der GET-Endpunkte
Testen Sie die App, indem Sie die GET
-Endpunkte in einem Browser aufrufen oder indem Sie den Endpunkte-Explorer verwenden. Die folgenden Schritte gelten für den Endpunkte Explorer.
Klicken Sie im Endpunkte-Explorer mit der rechten Maustaste auf den ersten GET-Endpunkt, und wählen Sie Anforderung generieren aus.
Der folgende Inhalt wird der Datei
TodoApi.http
hinzugefügt:Get {{TodoApi_HostAddress}}/todoitems ###
Wählen Sie den Link Anforderung senden aus. Er befindet sich oberhalb der neuen
GET
-Anforderungszeile.Die GET-Anforderung wird an die App gesendet, und die Antwort wird im Bereich Antwort angezeigt.
Der Antworttext ist mit folgendem JSON vergleichbar:
[ { "id": 1, "name": "walk dog", "isComplete": false } ]
Klicken Sie im Endpunkte-Explorer mit der rechten Maustaste auf den ersten GET-Endpunkt, und wählen Sie Anforderung generieren aus. Der folgende Inhalt wird der Datei
TodoApi.http
hinzugefügt:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Ersetzen Sie
{id}
durch1
.Wählen Sie den Link Anforderung senden aus. Er befindet sich oberhalb der GET-Anforderungszeile.
Die GET-Anforderung wird an die App gesendet, und die Antwort wird im Bereich Antwort angezeigt.
Der Antworttext ist mit folgendem JSON vergleichbar:
{ "id": 1, "name": "walk dog", "isComplete": false }
Diese App verwendet eine In-Memory-Datenbank. Wenn die App neu gestartet wird, gibt die GET-Anforderung keinerlei Daten zurück. Wenn keine Daten zurückgegeben werden, übermitteln Sie Daten per POST an die App, und wiederholen Sie die GET-Anforderung.
Rückgabewerte
ASP.NET Core serialisiert automatisch das Objekt in JSON und schreibt den JSON-Code in den Text der Antwortnachricht. Der Antwortcode für diesen Rückgabetyp ist 200 OK, vorausgesetzt, es gibt keine Ausnahmefehler. Nicht behandelte Ausnahmen werden in 5xx-Fehler übersetzt.
Die Rückgabetypen können eine Vielzahl von HTTP-Statuscodes darstellen. Beispielsweise kann GET /todoitems/{id}
zwei verschiedene Statuswerte zurückgeben:
- Wenn kein Element mit der angeforderten ID übereinstimmt, gibt die Methode einen NotFound-Fehlercode Status 404 zurück.
- Andernfalls gibt die Methode 200 mit einem JSON-Antworttext zurück. Die Rückgabe von
item
löst eine HTTP 200-Antwort aus.
Untersuchen des PUT-Endpunkts
Die Beispiel-App implementiert einen einzelnen PUT-Endpunkt mithilfe von MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Diese Methode ähnelt der MapPost
-Methode, verwendet jedoch HTTP PUT. Eine erfolgreiche Antwort gibt 204 (No Content) zurück. Gemäß der HTTP-Spezifikation erfordert eine PUT-Anforderung, dass der Client die gesamte aktualisierte Entität (nicht nur die Änderungen) sendet. Verwenden Sie HTTP PATCH, um Teilupdates zu unterstützen.
Testen des PUT-Endpunkts
In diesem Beispiel wird eine In-Memory-Datenbank verwendet, die jedes Mal initialisiert werden muss, wenn die App gestartet wird. Es muss ein Element in der Datenbank vorhanden sein, bevor Sie einen PUT-Aufruf durchführen. Rufen Sie vor einem PUT-Aufruf GET auf, um sicherzustellen, dass ein Element in der Datenbank vorhanden ist.
Aktualisieren Sie das To-do-Element, das über den ID-Wert 1 verfügt, und legen Sie als Namen "feed fish"
fest.
Klicken Sie im Endpunkte-Explorer mit der rechten Maustaste auf den PUT-Endpunkt, und wählen Sie Anforderung generieren aus.
Der folgende Inhalt wird der Datei
TodoApi.http
hinzugefügt:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
Ersetzen Sie in der PUT-Anforderungszeile
{id}
durch1
.Fügen Sie die folgenden Zeilen unmittelbar nach der PUT-Anforderungszeile hinzu:
Content-Type: application/json { "id": 1, "name": "feed fish", "isComplete": false }
Der vorangehende Code fügt einen Content-Type-Header und einen JS-ON-Anforderungstext hinzu.
Wählen Sie den Link Anforderung senden aus. Er befindet sich oberhalb der GET-Anforderungszeile.
Die PUT-Anforderung wird an die App gesendet, und die Antwort wird im Bereich Antwort angezeigt. Der Antworttext ist leer, und der Statuscode ist 204.
Überprüfen und Testen des DELETE-Endpunkts
Die Beispiel-App implementiert einen einzelnen DELETE-Endpunkt mithilfe von MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
Klicken Sie im Endpunkte-Explorer mit der rechten Maustaste auf den DELETE-Endpunkt, und wählen Sie Anforderung generieren aus.
Eine DELETE-Anforderung wird zu
TodoApi.http
hinzugefügt.Ersetzen Sie
{id}
in der DELETE-Anforderungszeile durch1
. Die DELETE-Anforderung sollte wie im folgenden Beispiel aussehen:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Wählen Sie den Link Anforderung senden für die DELETE-Anforderung aus.
Die DELETE-Anforderung wird an die App gesendet, und die Antwort wird im Bereich Antwort angezeigt. Der Antworttext ist leer, und der Statuscode ist 204.
Verwenden der MapGroup-API
Der Beispiel-App-Code wiederholt bei jeder Einrichtung eines Endpunkts das URL-Präfix todoitems
. APIs enthalten oft Gruppen von Endpunkten mit einem gemeinsamen URL-Präfix, und die MapGroup-Methode hilft bei der Organisation solcher Gruppen. Sie reduziert sich wiederholenden Code und ermöglicht die benutzerdefinierte Anpassung ganzer Gruppen von Endpunkten mit einem einzigen Aufruf von Methoden wie RequireAuthorization und WithMetadata.
Ersetzen Sie den Inhalt von Program.cs
durch den folgenden Code.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Der vorstehende Code weist die folgenden Änderungen auf:
- Fügt
var todoItems = app.MapGroup("/todoitems");
hinzu, um die Gruppe mithilfe des URL-Präfixes/todoitems
einzurichten. - Ändert alle
app.Map<HttpVerb>
-Methoden intodoItems.Map<HttpVerb>
. - Entfernt das URL-Präfix
/todoitems
aus den Aufrufen derMap<HttpVerb>
-Methode.
Testet die Endpunkte, um zu prüfen, ob sie identisch funktionieren.
Verwenden der TypedResults-API
Die Rückgabe TypedResults anstelle von Results hat mehrere Vorteile, einschließlich Prüfbarkeit und automatische Rückgabe der Metadaten des Antworttyps für OpenAPI, mit denen der Endpunkt beschrieben wird. Weitere Informationen finden Sie unter Vergleich von TypedResults und Results.
Die Map<HttpVerb>
-Methoden können Routinghandlermethoden aufrufen, anstatt Lambdafunktionen zu verwenden. Um ein Beispiel zu sehen, aktualisieren Sie Program.cs mit folgendem Code:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Der Map<HttpVerb>
-Code ruft jetzt Methoden anstelle von Lambdafunktionen auf:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
Diese Methoden geben Objekte zurück, die IResult implementieren und von TypedResults definiert werden:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Komponententests können diese Methoden aufrufen und testen, ob sie den richtigen Typ zurückgeben. Wenn die Methode z. B. GetAllTodos
ist:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Der Komponententestcode kann überprüfen, ob ein Objekt des Typs Ok<Todo[]> von der Handlermethode zurückgegeben wird. Beispiel:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
Vermeiden von Overposting
Derzeit macht die Beispiel-App das gesamte Todo
-Objekt verfügbar. In den Produktions-Apps sind die Daten, die eingegeben und mithilfe einer Teilmenge des Modells zurückgegeben werden, in der Regel eingeschränkt. Hierfür gibt es mehrere Gründe, wobei die Sicherheit einer der Hauptgründe ist. Die Teilmenge eines Modells wird üblicherweise als Datenübertragungsobjekt (DTO, Data Transfer Object), Eingabemodell oder Anzeigemodell bezeichnet. In diesem Artikel wird das DTO verwendet.
Ein DTO kann für Folgendes verwendet werden:
- Vermeiden Sie Overposting.
- Ausblenden von Eigenschaften, die Clients nicht einsehen sollen
- Auslassen einiger Eigenschaften, um die Nutzlast zu verringern
- Vereinfachen von Objektgraphen, die geschachtelte Objekte enthalten Vereinfachte Objektgraphen können für Clients zweckmäßiger sein.
Um den DTO-Ansatz zu veranschaulichen, aktualisieren Sie die Todo
-Klasse, sodass sie ein geheimes Feld einschließt:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Das geheime Feld muss in dieser App ausgeblendet werden, eine administrative App kann es jedoch verfügbar machen.
Vergewissern Sie sich, dass Sie das geheime Feld veröffentlichen und abrufen können.
Erstellen Sie eine Datei mit dem Namen TodoItemDTO.cs
und dem folgenden Code:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Aktualisieren Sie den Code in Program.cs
, um dieses DTO-Modell zu verwenden:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Vergewissern Sie sich, dass Sie für das Geheimnisfeld POST und GET ausführen können.
Problembehandlung mit dem fertiggestellten Beispiel
Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Anzeigen oder Herunterladen des fertiggestellten Projekts (Downloadanleitung).
Nächste Schritte
- JSKonfigurieren von ON-Serialisierungsoptionen
- Behandeln von Fehlern und Ausnahmen: Die Seite mit Ausnahmen für Entwickler*innen ist in der Entwicklungsumgebung für Minimal-API-Apps standardmäßig aktiviert. Informationen zum Behandeln von Fehlern und Ausnahmen finden Sie unter Behandeln von Fehlern in ASP.NET Core-APIs.
- Ein Beispiel für das Testen einer Minimal-API-App finden Sie in diesem GitHub-Beispiel.
- OpenAPI-Unterstützung in Minimal-APIs
- Schnellstart: Veröffentlichen in Azure
- Organisieren von Minimal-APIs für ASP.NET Core
Weitere Informationen
Weitere Informationen: Kurzreferenz zu Minimal-APIs
Minimal-APIs sind so entworfen, dass HTTP-APIs mit minimalen Abhängigkeiten erstellt werden. Sie eignen sich ideal für Microservices und Apps, die nur ein Minimum an Dateien, Funktionen und Abhängigkeiten in ASP.NET Core enthalten sollen.
In diesem Tutorial lernen Sie die Grundlagen der Erstellung einer minimalen API mit ASP.NET Core kennen. Ein weiterer Ansatz zum Erstellen von APIs in ASP.NET Core ist die Verwendung von Controllern. Hilfe bei der Entscheidung zwischen minimalen APIs und controllerbasierten APIs finden Sie in der Übersicht über APIs. Ein Tutorial zum Erstellen eines API-Projekts basierend auf Controllern, das weitere Features umfasst, finden Sie unter Erstellen einer Web-API.
Übersicht
In diesem Tutorial wird die folgende API erstellt:
API | Beschreibung | Anforderungstext | Antworttext |
---|---|---|---|
GET /todoitems |
Alle To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/complete |
Abgeschlossene To-Do-Elemente | Keine | Array von To-do-Elementen |
GET /todoitems/{id} |
Ein Element nach ID abrufen | Keine | To-do-Element |
POST /todoitems |
Neues Element hinzufügen | To-do-Element | To-do-Element |
PUT /todoitems/{id} |
Vorhandenes Element aktualisieren | To-do-Element | Keine |
DELETE /todoitems/{id} |
Löschen eines Elements | Keine | Keine |
Voraussetzungen
Visual Studio 2022 mit der Workload ASP.NET und Webentwicklung
Erstellen eines API-Projekts
Starten Sie Visual Studio 2022, und wählen Sie Neues Projekt erstellen aus.
Im Dialogfeld Neues Projekt erstellen:
- Geben Sie im Suchfeld
Empty
Nach Vorlagen suchenden Suchbegriff ein. - Wählen Sie die Vorlage ASP.NET Core leer und dann Weiter aus.
- Geben Sie im Suchfeld
Geben Sie dem Projekt den Namen TodoApi, und klicken Sie auf Weiter.
Im Dialogfeld Zusätzliche Informationen:
- Wählen Sie .NET 7.0 aus.
- Deaktivieren Sie Keine Anweisungen der obersten Ebene verwenden.
- Klicken Sie auf Erstellen
Untersuchen des Codes
Die Datei Program.cs
enthält den folgenden Code:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Der vorangehende Code:
- Erstellt eine Instanz von WebApplicationBuilder und WebApplication mit vorkonfigurierten Standardwerten.
- Erstellt den HTTP GET-Endpunkt
/
, derHello World!
zurückgibt:
Ausführen der App
Drücken Sie STRG+F5, um die Ausführung ohne den Debugger zu starten.
In Visual Studio wird das folgende Dialogfeld angezeigt:
Wählen Sie Ja aus, wenn Sie dem IIS Express-SLL-Zertifikat vertrauen möchten.
Das folgende Dialogfeld wird angezeigt:
Klicken Sie auf Ja, wenn Sie zustimmen möchten, dass das Entwicklungszertifikat vertrauenswürdig ist.
Informationen dazu, wie Sie dem Firefox-Browser vertrauen, finden Sie unter Firefox-Zertifikatfehler SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio startet den Kestrel-Webserver und öffnet ein Browserfenster.
Hello World!
wird im Browser angezeigt. Die Datei Program.cs
enthält eine Minimal-, aber dennoch vollständige App.
Hinzufügen von NuGet-Paketen
NuGet-Pakete müssen hinzugefügt werden, um die in diesem Tutorial verwendete Datenbank und Diagnose zu unterstützen.
- Klicken Sie im Menü Extras auf NuGet-Paket-Manager > NuGet-Pakete für Projektmappe verwalten.
- Wählen Sie die Registerkarte Durchsuchen aus.
- Geben Sie Microsoft.EntityFrameworkCore.InMemory in das Suchfeld ein, und wählen Sie
Microsoft.EntityFrameworkCore.InMemory
aus. - Aktivieren Sie das Kontrollkästchen Projekt im rechten Bereich, und klicken Sie dann auf Installieren.
- Folgen Sie den vorstehenden Anweisungen, um das
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-Paket hinzuzufügen.
Die Modell- und Datenbankkontextklassen
Erstellen Sie im Projektordner eine Datei mit dem Namen Todo.cs
und dem folgenden Code:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Der vorherige Code erstellt das Modell für diese App. Ein Modell ist eine Klasse, welche die in der App verwalteten Daten repräsentiert.
Erstellen Sie eine Datei mit dem Namen TodoDb.cs
und dem folgenden Code:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Der vorherige Code definiert den Datenbankkontext, also die Hauptklasse, die die Entity Framework-Funktionen für ein Datenmodell koordiniert. Diese Klasse wird von der Microsoft.EntityFrameworkCore.DbContext-Klasse abgeleitet.
Hinzufügen des API-Codes
Ersetzen Sie den Inhalt der Datei Program.cs
durch den folgenden Code:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Der folgende hervorgehobene Code fügt den Datenbankkontext zum Container für die Abhängigkeitsinjektion (Dependency Injection, DI) hinzu und ermöglicht die Anzeige von datenbankbezogenen Ausnahmen:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Der DI-Container bietet Zugriff auf den Datenbankkontext und andere Dienste.
Installieren von Postman zum Testen der App
Dieses Tutorial verwendet Postman zum Testen der API.
- Installieren Sie Postman.
- Starten Sie die Web-App.
- Starten Sie Postman.
- Wählen Sie Arbeitsbereiche>Arbeitsbereich erstellen und anschließend Weiter aus.
- Nennen Sie den Arbeitsbereich TodoApi, und wählen Sie Erstellen aus.
- Wählen Sie das Zahnradsymbol >Einstellungen (Registerkarte Allgemein ) aus, und deaktivieren Sie SSL-Zertifikatüberprüfung.
Warnung
Reaktivieren Sie die Überprüfung des SSL-Zertifikats, nachdem Sie die Beispiel-App getestet haben.
Testen der Übertragung von Daten
Der folgende Code in Program.cs
erstellt den HTTP POST-Endpunkt /todoitems
zum Hinzufügen von Daten zur In-Memory-Datenbank:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Führen Sie die App aus. Der Browser zeigt einen 404-Fehler an, da kein /
-Endpunkt mehr vorhanden ist.
Fügen Sie der App mithilfe des POST-Endpunkts Daten hinzu:
Erstellen Sie in Postman eine neue HTTP-Anforderung, indem Sie Neu>HTTP auswählen.
Legen Sie als HTTP-Methode
POST
fest.Legen Sie den URI auf
https://localhost:<port>/todoitems
fest. Beispiel:https://localhost:5001/todoitems
Wählen Sie die Registerkarte Body (Text) aus.
Wählen Sie raw (Unformatiert) aus.
Legen Sie den Typ auf JSON fest.
Geben Sie für die Aufgabe den Anforderungstext im JSON-Format ein:
{ "name":"walk dog", "isComplete":true }
Wählen Sie Send (Senden) aus.
Untersuchen der GET-Endpunkte
Die Beispiel-App implementiert mehrere GET-Endpunkte durch Aufrufen von MapGet
:
API | Beschreibung | Anforderungstext | Antworttext |
---|---|---|---|
GET /todoitems |
Alle To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/complete |
Alle abgeschlossenen To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/{id} |
Ein Element nach ID abrufen | Keine | To-do-Element |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Testen der GET-Endpunkte
Testen Sie die App, indem Sie die Endpunkte in einem Browser oder über Postman aufrufen. Die folgenden Schritte gelten für Postman.
- Erstellen Sie eine neue HTTP-Anforderung.
- Legen Sie die HTTP-Methode auf GET fest.
- Legen Sie den Anforderungs-URI auf
https://localhost:<port>/todoitems
fest. Beispiel:https://localhost:5001/todoitems
. - Wählen Sie Send (Senden) aus.
Durch einen Aufruf von GET /todoitems
wird eine Antwort ähnlich der folgenden erzeugt:
[
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
]
- Legen Sie den Anforderungs-URI auf
https://localhost:<port>/todoitems/1
fest. Beispiel:https://localhost:5001/todoitems/1
. - Wählen Sie Send (Senden) aus.
- Die Antwort ähnelt dem folgenden Code:
{ "id": 1, "name": "walk dog", "isComplete": false }
Diese App verwendet eine In-Memory-Datenbank. Wenn die App neu gestartet wird, gibt die GET-Anforderung keinerlei Daten zurück. Wenn keine Daten zurückgegeben werden, übermitteln Sie Daten per POST an die App, und wiederholen Sie die GET-Anforderung.
Rückgabewerte
ASP.NET Core serialisiert automatisch das Objekt in JSON und schreibt den JSON-Code in den Text der Antwortnachricht. Der Antwortcode für diesen Rückgabetyp ist 200 OK, vorausgesetzt, es gibt keine Ausnahmefehler. Nicht behandelte Ausnahmen werden in 5xx-Fehler übersetzt.
Die Rückgabetypen können eine Vielzahl von HTTP-Statuscodes darstellen. Beispielsweise kann GET /todoitems/{id}
zwei verschiedene Statuswerte zurückgeben:
- Wenn kein Element mit der angeforderten ID übereinstimmt, gibt die Methode einen NotFound-Fehlercode Status 404 zurück.
- Andernfalls gibt die Methode 200 mit einem JSON-Antworttext zurück. Die Rückgabe von
item
löst eine HTTP 200-Antwort aus.
Untersuchen des PUT-Endpunkts
Die Beispiel-App implementiert einen einzelnen PUT-Endpunkt mithilfe von MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Diese Methode ähnelt der MapPost
-Methode, verwendet jedoch HTTP PUT. Eine erfolgreiche Antwort gibt 204 (No Content) zurück. Gemäß der HTTP-Spezifikation erfordert eine PUT-Anforderung, dass der Client die gesamte aktualisierte Entität (nicht nur die Änderungen) sendet. Verwenden Sie HTTP PATCH, um Teilupdates zu unterstützen.
Testen des PUT-Endpunkts
In diesem Beispiel wird eine In-Memory-Datenbank verwendet, die jedes Mal initialisiert werden muss, wenn die App gestartet wird. Es muss ein Element in der Datenbank vorhanden sein, bevor Sie einen PUT-Aufruf durchführen. Rufen Sie vor einem PUT-Aufruf GET auf, um sicherzustellen, dass ein Element in der Datenbank vorhanden ist.
Aktualisieren Sie das To-do-Element, das über den ID-Wert 1 verfügt, und legen Sie als Namen "feed fish"
fest:
{
"id": 1,
"name": "feed fish",
"isComplete": false
}
Überprüfen und Testen des DELETE-Endpunkts
Die Beispiel-App implementiert einen einzelnen DELETE-Endpunkt mithilfe von MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
So löschen Sie mit Postman eine Aufgabe
- Legen Sie die Methode auf
DELETE
fest. - Legen Sie den URI des zu löschenden Objekts fest, z. B.
https://localhost:5001/todoitems/1
. - Wählen Sie Send (Senden) aus.
Verwenden der MapGroup-API
Der Beispiel-App-Code wiederholt bei jeder Einrichtung eines Endpunkts das URL-Präfix todoitems
. APIs enthalten oft Gruppen von Endpunkten mit einem gemeinsamen URL-Präfix, und die MapGroup-Methode hilft bei der Organisation solcher Gruppen. Sie reduziert sich wiederholenden Code und ermöglicht die benutzerdefinierte Anpassung ganzer Gruppen von Endpunkten mit einem einzigen Aufruf von Methoden wie RequireAuthorization und WithMetadata.
Ersetzen Sie den Inhalt von Program.cs
durch den folgenden Code.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Der vorstehende Code weist die folgenden Änderungen auf:
- Fügt
var todoItems = app.MapGroup("/todoitems");
hinzu, um die Gruppe mithilfe des URL-Präfixes/todoitems
einzurichten. - Ändert alle
app.Map<HttpVerb>
-Methoden intodoItems.Map<HttpVerb>
. - Entfernt das URL-Präfix
/todoitems
aus den Aufrufen derMap<HttpVerb>
-Methode.
Testet die Endpunkte, um zu prüfen, ob sie identisch funktionieren.
Verwenden der TypedResults-API
Die Rückgabe TypedResults anstelle von Results hat mehrere Vorteile, einschließlich Prüfbarkeit und automatische Rückgabe der Metadaten des Antworttyps für OpenAPI, mit denen der Endpunkt beschrieben wird. Weitere Informationen finden Sie unter Vergleich von TypedResults und Results.
Die Map<HttpVerb>
-Methoden können Routinghandlermethoden aufrufen, anstatt Lambdafunktionen zu verwenden. Um ein Beispiel zu sehen, aktualisieren Sie Program.cs mit folgendem Code:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Der Map<HttpVerb>
-Code ruft jetzt Methoden anstelle von Lambdafunktionen auf:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
Diese Methoden geben Objekte zurück, die IResult implementieren und von TypedResults definiert werden:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Komponententests können diese Methoden aufrufen und testen, ob sie den richtigen Typ zurückgeben. Wenn die Methode z. B. GetAllTodos
ist:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Der Komponententestcode kann überprüfen, ob ein Objekt des Typs Ok<Todo[]> von der Handlermethode zurückgegeben wird. Beispiel:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
Vermeiden von Overposting
Derzeit macht die Beispiel-App das gesamte Todo
-Objekt verfügbar. In den Produktions-Apps sind die Daten, die eingegeben und mithilfe einer Teilmenge des Modells zurückgegeben werden, in der Regel eingeschränkt. Hierfür gibt es mehrere Gründe, wobei die Sicherheit einer der Hauptgründe ist. Die Teilmenge eines Modells wird üblicherweise als Datenübertragungsobjekt (DTO, Data Transfer Object), Eingabemodell oder Anzeigemodell bezeichnet. In diesem Artikel wird das DTO verwendet.
Ein DTO kann für Folgendes verwendet werden:
- Vermeiden Sie Overposting.
- Ausblenden von Eigenschaften, die Clients nicht anzeigen sollen
- Auslassen einiger Eigenschaften, um die Nutzlast zu verringern
- Vereinfachen von Objektgraphen, die geschachtelte Objekte enthalten Vereinfachte Objektgraphen können für Clients zweckmäßiger sein.
Um den DTO-Ansatz zu veranschaulichen, aktualisieren Sie die Todo
-Klasse, sodass sie ein geheimes Feld einschließt:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Das geheime Feld muss in dieser App ausgeblendet werden, eine administrative App kann es jedoch verfügbar machen.
Vergewissern Sie sich, dass Sie das geheime Feld veröffentlichen und abrufen können.
Erstellen Sie eine Datei mit dem Namen TodoItemDTO.cs
und dem folgenden Code:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Aktualisieren Sie den Code in Program.cs
, um dieses DTO-Modell zu verwenden:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Vergewissern Sie sich, dass Sie für das Geheimnisfeld POST und GET ausführen können.
Problembehandlung mit dem fertiggestellten Beispiel
Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Anzeigen oder Herunterladen des fertiggestellten Projekts (Downloadanleitung).
Nächste Schritte
Konfigurieren von JSON-Serialisierungsoptionen
Informationen zum Konfigurieren der JSON-Serialisierung in Ihren Minimal-API-Apps finden Sie unter Konfigurieren von JSON-Serialisierungsoptionen.
Behandeln von Fehlern und Ausnahmen
Die Seite mit Ausnahmen für Entwickler ist in der Entwicklungsumgebung für Minimal-API-Apps standardmäßig aktiviert. Informationen zum Behandeln von Fehlern und Ausnahmen finden Sie unter Behandeln von Fehlern in ASP.NET Core-APIs.
Testen von Minimal-API-Apps
Ein Beispiel für das Testen einer Minimal-API-App finden Sie in diesem GitHub-Beispiel.
Verwenden von OpenAPI (Swagger)
Informationen zur Verwendung von OpenAPI mit Minimal-API-Apps finden Sie unter OpenAPI-Unterstützung in Minimal-APIs.
Veröffentlichen in Azure
Weitere Informationen zur Bereitstellung in Azure finden Sie unter Schnellstart: Bereitstellen einer ASP.NET-Web-App.
Weitere Informationen
Weitere Informationen zu Minimal-API-Apps finden Sie in der Schnellreferenz zu Minimal-APIs.
Minimal-APIs sind so entworfen, dass HTTP-APIs mit minimalen Abhängigkeiten erstellt werden. Sie eignen sich ideal für Microservices und Apps, die nur ein Minimum an Dateien, Funktionen und Abhängigkeiten in ASP.NET Core enthalten sollen.
In diesem Tutorial lernen Sie die Grundlagen der Erstellung einer minimalen API mit ASP.NET Core kennen. Ein Tutorial zum Erstellen eines API-Projekts basierend auf Controllern, das weitere Features umfasst, finden Sie unter Erstellen einer Web-API. Einen Vergleich finden Sie unter Unterschiede zwischen Minimal-APIs und APIs mit Controllern in diesem Dokument.
Übersicht
In diesem Tutorial wird die folgende API erstellt:
API | Beschreibung | Anforderungstext | Antworttext |
---|---|---|---|
GET / |
Browsertest, „Hello World“ | Keine | Hello World! |
GET /todoitems |
Alle To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/complete |
Abgeschlossene To-Do-Elemente | Keine | Array von To-do-Elementen |
GET /todoitems/{id} |
Ein Element nach ID abrufen | Keine | To-do-Element |
POST /todoitems |
Neues Element hinzufügen | To-do-Element | To-do-Element |
PUT /todoitems/{id} |
Vorhandenes Element aktualisieren | To-do-Element | Keine |
DELETE /todoitems/{id} |
Löschen eines Elements | Keine | Keine |
Voraussetzungen
- Visual Studio 2022 mit der Workload ASP.NET und Webentwicklung
- .NET 6.0 SDK
Erstellen eines API-Projekts
Starten Sie Visual Studio 2022, und wählen Sie Neues Projekt erstellen aus.
Im Dialogfeld Neues Projekt erstellen:
- Geben Sie im Suchfeld
API
Nach Vorlagen suchenden Suchbegriff ein. - Wählen Sie die ASP.NET Core-Web-API-Vorlage aus, und klicken Sie auf Weiter.
- Geben Sie im Suchfeld
Geben Sie dem Projekt den Namen TodoApi, und klicken Sie auf Weiter.
Im Dialogfeld Zusätzliche Informationen:
- Wählen Sie .NET 6.0 (Langfristiger Support) aus.
- Deaktivieren Sie Controller verwenden (zur Verwendung von Minimal-APIs deaktivieren)
- Klicken Sie auf Erstellen
Untersuchen des Codes
Die Datei Program.cs
enthält den folgenden Code:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateTime.Now.AddDays(index),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Die Projektvorlage erstellt eine WeatherForecast
-API mit Unterstützung für Swagger. Swagger wird verwendet, um nützliche Dokumentations- und Hilfeseiten für APIs zu generieren.
Der folgende hervorgehobene Code fügt Unterstützung für Swagger hinzu:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Ausführen der App
Drücken Sie STRG+F5, um die Ausführung ohne den Debugger zu starten.
In Visual Studio wird das folgende Dialogfeld angezeigt:
Wählen Sie Ja aus, wenn Sie dem IIS Express-SLL-Zertifikat vertrauen möchten.
Das folgende Dialogfeld wird angezeigt:
Klicken Sie auf Ja, wenn Sie zustimmen möchten, dass das Entwicklungszertifikat vertrauenswürdig ist.
Informationen dazu, wie Sie dem Firefox-Browser vertrauen, finden Sie unter Firefox-Zertifikatfehler SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio startet den Kestrel-Webserver.
Die Swagger-Seite /swagger/index.html
wird angezeigt. Wählen Sie GET > Try it out> Execute
aus. Die Seite zeigt Folgendes an:
- Der Curl-Befehl, zum Testen der WeatherForecast-API.
- Die URL zum Testen der WeatherForecast-API.
- Der Antwortcode, der Text und die Header.
- Ein Dropdown-Listenfeld mit Medientypen und dem Beispielwert und -schema.
Kopieren Sie die Anforderungs-URL, und fügen Sie sie in den Browser ein: https://localhost:<port>/WeatherForecast
. Der zurückgegebene JSON-Code sieht in etwa wie folgt aus:
[
{
"date": "2021-10-19T14:12:50.3079024-10:00",
"temperatureC": 13,
"summary": "Bracing",
"temperatureF": 55
},
{
"date": "2021-10-20T14:12:50.3080559-10:00",
"temperatureC": -8,
"summary": "Bracing",
"temperatureF": 18
},
{
"date": "2021-10-21T14:12:50.3080601-10:00",
"temperatureC": 12,
"summary": "Hot",
"temperatureF": 53
},
{
"date": "2021-10-22T14:12:50.3080603-10:00",
"temperatureC": 10,
"summary": "Sweltering",
"temperatureF": 49
},
{
"date": "2021-10-23T14:12:50.3080604-10:00",
"temperatureC": 36,
"summary": "Warm",
"temperatureF": 96
}
]
Aktualisieren des generierten Codes
In diesem Tutorial liegt der Fokus auf der Erstellung einer API, deshalb löschen wir den Swagger-Code und den WeatherForecast
-Code. Ersetzen Sie den Inhalt der Datei Program.cs
durch Folgendes:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Mit dem folgenden hervorgehobenen Code werden ein WebApplicationBuilder und eine WebApplication mit vorkonfigurierten Standardwerten erstellt:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Der nachstehende Code erstellt einen HTTP GET-Endpunkt /
, der Hello World!
zurückgibt:
app.MapGet("/", () => "Hello World!");
app.Run();
führt die App aus.
Entfernen Sie die beiden "launchUrl": "swagger",
-Zeilen aus der Properties/launchSettings.json
-Datei. Wenn die launchUrl
nicht angegeben ist, fordert der Webbrowser den Endpunkt /
an.
Führen Sie die App aus. Hello World!
wird angezeigt. Die aktualisierte Datei Program.cs
enthält eine minimale, aber vollständige App.
Hinzufügen von NuGet-Paketen
NuGet-Pakete müssen hinzugefügt werden, um die in diesem Tutorial verwendete Datenbank und Diagnose zu unterstützen.
- Klicken Sie im Menü Extras auf NuGet-Paket-Manager > NuGet-Pakete für Projektmappe verwalten.
- Geben Sie Microsoft.EntityFrameworkCore.InMemory in das Suchfeld ein, und wählen Sie
Microsoft.EntityFrameworkCore.InMemory
aus. - Aktivieren Sie das Kontrollkästchen Projekt im rechten Bereich, und klicken Sie dann auf Installieren.
- Folgen Sie den vorstehenden Anweisungen, um das
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-Paket hinzuzufügen.
Hinzufügen des API-Codes
Ersetzen Sie den Inhalt der Datei Program.cs
durch den folgenden Code:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Die Modell- und Datenbankkontextklassen
Die Beispiel-App enthält das folgende Modell:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Ein Modell ist eine Klasse, welche die in der App verwalteten Daten repräsentiert. Das Modell für diese App ist die Klasse Todo
.
Die Beispiel-App enthält die folgende Datenbankkontextklasse:
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Der Datenbankkontext ist die Hauptklasse, die die Entity Framework-Funktionalität für ein Datenmodell koordiniert. Diese Klasse wird durch Ableiten von der Microsoft.EntityFrameworkCore.DbContext-Klasse erstellt.
Der folgende hervorgehobene Code fügt den Datenbankkontext zum Container für die Abhängigkeitsinjektion (Dependency Injection, DI) hinzu und ermöglicht die Anzeige von datenbankbezogenen Ausnahmen:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Der DI-Container bietet Zugriff auf den Datenbankkontext und andere Dienste.
Der folgende Code erstellt einen HTTP POST-Endpunkt /todoitems
zum Hinzufügen von Daten zur In-Memory-Datenbank:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Installieren von Postman zum Testen der App
Dieses Tutorial verwendet Postman zum Testen der API.
- Installieren Sie Postman.
- Starten Sie die Web-App.
- Starten Sie Postman.
- Deaktivieren Sie SSL certificate verification (Verifizierung des SSL-Zertifikats).
- Deaktivieren Sie auf der Registerkarte General (Allgemein) unter File>Settings (Datei > Einstellungen) SSL certificate verification (Verifizierung des SSL-Zertifikats).
Warnung
Aktivieren Sie die Verifizierung des SSL-Zertifikats wieder, nachdem Sie den Controller getestet haben.
- Deaktivieren Sie auf der Registerkarte General (Allgemein) unter File>Settings (Datei > Einstellungen) SSL certificate verification (Verifizierung des SSL-Zertifikats).
Testen der Übertragung von Daten
Befolgen Sie diese Anweisungen, um Daten an die App zu übertragen:
Erstellen Sie eine neue HTTP-Anforderung.
Legen Sie als HTTP-Methode
POST
fest.Legen Sie den URI auf
https://localhost:<port>/todoitems
fest. Beispiel:https://localhost:5001/todoitems
Wählen Sie die Registerkarte Body (Text) aus.
Wählen Sie raw (Unformatiert) aus.
Legen Sie den Typ auf JSON fest.
Geben Sie für die Aufgabe den Anforderungstext im JSON-Format ein:
{ "name":"walk dog", "isComplete":true }
Wählen Sie Send (Senden) aus.
Untersuchen der GET-Endpunkte
Die Beispiel-App implementiert mehrere GET-Endpunkte mithilfe von MapGet
-Aufrufen:
API | Beschreibung | Anforderungstext | Antworttext |
---|---|---|---|
GET / |
Browsertest, „Hello World“ | Keine | Hello World! |
GET /todoitems |
Alle To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/complete |
Alle abgeschlossenen To-do-Elemente abrufen | Keine | Array von To-do-Elementen |
GET /todoitems/{id} |
Ein Element nach ID abrufen | Keine | To-do-Element |
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Testen der GET-Endpunkte
Testen Sie die App, indem Sie die beiden Endpunkte in einem Browser oder über Postman aufrufen. Zum Beispiel:
GET https://localhost:5001/todoitems
GET https://localhost:5001/todoitems/1
Durch einen Aufruf von GET /todoitems
wird eine Antwort ähnlich der folgenden erzeugt:
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Testen der GET-Endpunkte mit Postman
- Erstellen Sie eine neue HTTP-Anforderung.
- Legen Sie die HTTP-Methode auf GET fest.
- Legen Sie den Anforderungs-URI auf
https://localhost:<port>/todoitems
fest. Beispiel:https://localhost:5001/todoitems
. - Wählen Sie Send (Senden) aus.
Diese App verwendet eine In-Memory-Datenbank. Wenn die App neu gestartet wird, gibt die GET-Anforderung keinerlei Daten zurück. Wenn keine Daten zurückgegeben werden, senden Sie mit POST Daten an die App.
Rückgabewerte
ASP.NET Core serialisiert automatisch das Objekt in JSON und schreibt den JSON-Code in den Text der Antwortnachricht. Der Antwortcode für diesen Rückgabetyp ist 200 OK, vorausgesetzt, es gibt keine Ausnahmefehler. Nicht behandelte Ausnahmen werden in 5xx-Fehler übersetzt.
Die Rückgabetypen können eine Vielzahl von HTTP-Statuscodes darstellen. Beispielsweise kann GET /todoitems/{id}
zwei verschiedene Statuswerte zurückgeben:
- Wenn kein Element mit der angeforderten ID übereinstimmt, gibt die Methode einen NotFound-Fehlercode Status 404 zurück.
- Andernfalls gibt die Methode 200 mit einem JSON-Antworttext zurück. Die Rückgabe von
item
löst eine HTTP 200-Antwort aus.
Untersuchen des PUT-Endpunkts
Die Beispiel-App implementiert einen einzelnen PUT-Endpunkt mithilfe von MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Diese Methode ähnelt der MapPost
-Methode, verwendet jedoch HTTP PUT. Eine erfolgreiche Antwort gibt 204 (No Content) zurück. Gemäß der HTTP-Spezifikation erfordert eine PUT-Anforderung, dass der Client die gesamte aktualisierte Entität (nicht nur die Änderungen) sendet. Verwenden Sie HTTP PATCH, um Teilupdates zu unterstützen.
Testen des PUT-Endpunkts
In diesem Beispiel wird eine In-Memory-Datenbank verwendet, die jedes Mal initialisiert werden muss, wenn die App gestartet wird. Es muss ein Element in der Datenbank vorhanden sein, bevor Sie einen PUT-Aufruf durchführen. Rufen Sie vor einem PUT-Aufruf GET auf, um sicherzustellen, dass ein Element in der Datenbank vorhanden ist.
Aktualisieren Sie das To-do-Element, das über den ID-Wert 1 verfügt, und legen Sie als Namen "feed fish"
fest:
{
"id": 1,
"name": "feed fish",
"isComplete": false
}
Untersuchen des DELETE-Endpunkts
Die Beispiel-App implementiert einen einzelnen DELETE-Endpunkt mithilfe von MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
So löschen Sie mit Postman eine Aufgabe
- Legen Sie die Methode auf
DELETE
fest. - Legen Sie den URI des zu löschenden Objekts fest, z. B.
https://localhost:5001/todoitems/1
. - Wählen Sie Send (Senden) aus.
Vermeiden von Overposting
Derzeit macht die Beispiel-App das gesamte Todo
-Objekt verfügbar. In den Produktions-Apps sind die Daten, die eingegeben und mithilfe einer Teilmenge des Modells zurückgegeben werden, in der Regel eingeschränkt. Hierfür gibt es mehrere Gründe, wobei die Sicherheit einer der Hauptgründe ist. Die Teilmenge eines Modells wird üblicherweise als Datenübertragungsobjekt (DTO, Data Transfer Object), Eingabemodell oder Anzeigemodell bezeichnet. In diesem Artikel wird das DTO verwendet.
Ein DTO kann für Folgendes verwendet werden:
- Vermeiden Sie Overposting.
- Ausblenden von Eigenschaften, die Clients nicht anzeigen sollen
- Auslassen einiger Eigenschaften, um die Nutzlast zu verringern
- Vereinfachen von Objektgraphen, die geschachtelte Objekte enthalten Vereinfachte Objektgraphen können für Clients zweckmäßiger sein.
Um den DTO-Ansatz zu veranschaulichen, aktualisieren Sie die Todo
-Klasse, sodass sie ein geheimes Feld einschließt:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Das geheime Feld muss in dieser App ausgeblendet werden, eine administrative App kann es jedoch verfügbar machen.
Vergewissern Sie sich, dass Sie das geheime Feld veröffentlichen und abrufen können.
Erstellen Sie ein DTO-Modell:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Aktualisieren Sie den Code, um TodoItemDTO
zu verwenden:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Vergewissern Sie sich, dass Sie das geheime Feld weder veröffentlichen noch abrufen können.
Unterschiede zwischen Minimal-APIs und APIs mit Controllern
- Keine Unterstützung für Filter: Beispielsweise werden IAsyncAuthorizationFilter, IAsyncActionFilter, IAsyncExceptionFilter, IAsyncResultFilter und IAsyncResourceFilter nicht unterstützt.
- Keine Unterstützung für die Modellbindung, d. h. IModelBinderProvider, IModelBinder. Unterstützung kann mit einem benutzerdefinierten Bindungs-Shim hinzugefügt werden.
- Keine Unterstützung für die Bindung aus Formularen. Dies schließt die Bindung IFormFile ein. Eine zukünftige Unterstützung für
IFormFile
ist geplant.
- Keine Unterstützung für die Bindung aus Formularen. Dies schließt die Bindung IFormFile ein. Eine zukünftige Unterstützung für
- Keine integrierte Unterstützung für die Validierung, d. h. IModelValidator.
- Keine Unterstützung für Anwendungsparts oder das Anwendungsmodell. Es gibt keine Möglichkeit, eigene Konventionen anzuwenden oder zu erstellen.
- Keine integrierte Unterstützung für das Ansichtsrendering. Es wird empfohlen, Razor Pages zum Rendern von Ansichten zu verwenden.
- Keine Unterstützung für JsonPatch.
- Keine Unterstützung für OData.
- Keine Unterstützung für ApiVersioning. Ausführlichere Informationen finden Sie im Zusammenhang mit diesem Issue.
Verwenden von JsonOptions
Der folgende Code verwendet JsonOptions:
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
// Configure JSON options
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapGet("/", () => new Todo { Name = "Walk dog", IsComplete = false });
app.Run();
class Todo
{
// These are public fields instead of properties.
public string? Name;
public bool IsComplete;
}
Der folgende Code verwendet JsonSerializerOptions:
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
app.MapGet("/", () => Results.Json(new Todo {
Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Im vorangehenden Code werden Webstandardwerte verwendet, mit denen Eigenschaftsnamen in das Camel Case-Format konvertiert werden.
Problembehandlung mit dem fertiggestellten Beispiel
Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Anzeigen oder Herunterladen des fertiggestellten Projekts (Downloadanleitung).
Testen der Minimal-API
Ein Beispiel für das Testen einer Minimal-API-App finden Sie in diesem GitHub-Beispiel.
Veröffentlichen in Azure
Weitere Informationen zur Bereistellung in Azure finden Sie unter Schnellstart: Bereitstellen einer ASP.NET-Web-App.
Zusätzliche Ressourcen
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Tickets als Feedbackmechanismus für Inhalte auslaufen lassen und es durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unter:Feedback senden und anzeigen für