Samouczek: tworzenie minimalnego interfejsu API przy użyciu platformy ASP.NET Core
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Autor : Rick Anderson i Tom Dykstra
Minimalne interfejsy API są tworzone w celu tworzenia interfejsów API HTTP z minimalnymi zależnościami. Są one idealne dla mikrousług i aplikacji, które chcą uwzględniać tylko minimalne pliki, funkcje i zależności w ASP.NET Core.
W tym samouczku przedstawiono podstawy tworzenia minimalnego interfejsu API przy użyciu platformy ASP.NET Core. Innym podejściem do tworzenia interfejsów API w programie ASP.NET Core jest użycie kontrolerów. Aby uzyskać pomoc dotyczącą wybierania między minimalnymi interfejsami API i interfejsami API opartymi na kontrolerach, zobacz Omówienie interfejsów API. Aby zapoznać się z samouczkiem dotyczącym tworzenia projektu interfejsu API na podstawie kontrolerów zawierających więcej funkcji, zobacz Tworzenie internetowego interfejsu API.
Omówienie
Ten samouczek tworzy następujący interfejs API:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
POST /todoitems |
Dodawanie nowego elementu | Element do wykonania | Element do wykonania |
PUT /todoitems/{id} |
Aktualizowanie istniejącego elementu | Element do wykonania | Brak |
DELETE /todoitems/{id} |
Usuwanie elementu | Brak | Brak |
Wymagania wstępne
Program Visual Studio 2022 z pakietem roboczym tworzenia aplikacji ASP.NET i aplikacji internetowych.
Tworzenie projektu interfejsu API
Uruchom program Visual Studio 2022 i wybierz pozycję Utwórz nowy projekt.
W oknie dialogowym Tworzenie nowego projektu:
- Wprowadź ciąg
Empty
w polu wyszukiwania Wyszukaj szablony . - Wybierz szablon ASP.NET Core Empty i wybierz przycisk Dalej.
- Wprowadź ciąg
Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.
W oknie dialogowym Dodatkowe informacje:
- Wybierz .NET 9.0
- Usuń zaznaczenie pola Wyboru Nie używaj instrukcji najwyższego poziomu
- Wybierz pozycję Utwórz
Analizowanie kodu
Plik Program.cs
zawiera następujący kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod ma następujące działanie:
- Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
- Tworzy punkt końcowy
/
HTTP GET, który zwraca wartośćHello World!
:
Uruchom aplikację
Naciśnij Ctrl+F5, aby uruchomić bez debugera.
Program Visual Studio wyświetla następujące okno dialogowe:
Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.
Zostanie wyświetlone następujące okno dialogowe:
Wybierz pozycję Tak, jeśli wyrażasz zgodę na zaufanie certyfikatowi programistycznemu.
Aby uzyskać informacje na temat zaufania przeglądarce Firefox, zobacz Błąd certyfikatu przeglądarki Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Program Visual Studio uruchamia Kestrel serwer internetowy i otwiera okno przeglądarki.
Hello World!
jest wyświetlany w przeglądarce. Plik Program.cs
zawiera minimalną, ale kompletną aplikację.
Zamknij okno przeglądarki.
Dodawanie pakietów NuGet
Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.
- W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
- Wybierz kartę Przeglądaj.
- Wybierz pozycję Uwzględnij wersję P.
- Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję
Microsoft.EntityFrameworkCore.InMemory
. - Zaznacz pole wyboru Projekt w okienku po prawej stronie, a następnie wybierz pozycję Zainstaluj.
- Postępuj zgodnie z poprzednimi instrukcjami, aby dodać
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
pakiet.
Klasy kontekstu modelu i bazy danych
- W folderze projektu utwórz plik o nazwie
Todo.cs
z następującym kodem:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.
- Utwórz plik o nazwie z
TodoDb.cs
następującym kodem:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .
Dodawanie kodu interfejsu API
- Zastąp zawartość pliku
Program.cs
następującym kodem:
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();
Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.
W tym samouczku używane są pliki Endpoints Explorer i .http do testowania interfejsu API.
Testowanie publikowania danych
Poniższy kod w pliku Program.cs
tworzy punkt końcowy /todoitems
HTTP POST, który dodaje dane do bazy danych w pamięci:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego /
.
Punkt końcowy POST będzie używany do dodawania danych do aplikacji.
Wybierz pozycję Wyświetl>inne eksploratora punktów końcowych systemu Windows.>
Kliknij prawym przyciskiem myszy punkt końcowy POST i wybierz polecenie Generuj żądanie.
Nowy plik jest tworzony w folderze projektu o nazwie
TodoApi.http
, z zawartością podobną do następującego przykładu:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- Pierwszy wiersz tworzy zmienną używaną dla wszystkich punktów końcowych.
- Następny wiersz definiuje żądanie POST.
- Potrójny hasztag (
###
) wiersz jest ogranicznikiem żądania: co następuje po nim dla innego żądania.
Żądanie POST wymaga nagłówków i treści. Aby zdefiniować te części żądania, dodaj następujące wiersze bezpośrednio po wierszu żądania POST:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Powyższy kod dodaje nagłówek Content-Type i treść żądania JSON. Plik TodoApi.http powinien teraz wyglądać podobnie do poniższego przykładu, ale z numerem portu:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Uruchom aplikację.
Wybierz link Wyślij żądanie powyżej
POST
wiersza żądania.Żądanie POST jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .
Sprawdzanie punktów końcowych GET
Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet
:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie wszystkich ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
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());
Testowanie punktów końcowych GET
Przetestuj GET
aplikację, wywołując punkty końcowe z przeglądarki lub przy użyciu Eksploratora punktów końcowych. Poniższe kroki dotyczą Eksploratora punktów końcowych.
W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy pierwszy punkt końcowy GET i wybierz polecenie Generuj żądanie.
Do pliku zostanie dodana następująca
TodoApi.http
zawartość:Get {{TodoApi_HostAddress}}/todoitems ###
Wybierz link Wyślij żądanie powyżej nowego
GET
wiersza żądania.Żądanie GET jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .
Treść odpowiedzi jest podobna do następującego kodu JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
W Eksploratorze punktów końcowych kliknij prawym przyciskiem
/todoitems/{id}
myszy punkt końcowy GET i wybierz pozycję Generuj żądanie. Do pliku zostanie dodana następującaTodoApi.http
zawartość:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Zamień
{id}
na1
.Wybierz link Wyślij żądanie powyżej nowego wiersza żądania GET.
Żądanie GET jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .
Treść odpowiedzi jest podobna do następującego kodu JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Ta aplikacja używa bazy danych w pamięci. Jeśli aplikacja zostanie ponownie uruchomiona, żądanie GET nie zwraca żadnych danych. Jeśli żadne dane nie są zwracane, prześlij dane POST do aplikacji i spróbuj ponownie wysłać żądanie GET.
Wartości zwracane
ASP.NET Core automatycznie serializuje obiekt w formacie JSON i zapisuje kod JSON w treści komunikatu odpowiedzi. Kod odpowiedzi dla tego typu zwracanego to 200 OK, zakładając, że nie ma żadnych nieobsługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.
Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id}
może zwrócić dwie różne wartości stanu:
- Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
- W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie
item
wyników w odpowiedzi HTTP 200.
Badanie punktu końcowego PUT
Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia 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();
});
Ta metoda jest podobna do metody , z tą różnicą MapPost
, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.
Testowanie punktu końcowego PUT
W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.
Zaktualizuj element to-do, który ma Id = 1
wartość , i ustaw jego nazwę na "feed fish"
.
W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy punkt końcowy PUT i wybierz pozycję Generuj żądanie.
Do pliku zostanie dodana następująca
TodoApi.http
zawartość:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
W wierszu żądania PUT zastąp ciąg
{id}
ciągiem1
.Dodaj następujące wiersze bezpośrednio po wierszu żądania PUT:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Powyższy kod dodaje nagłówek Content-Type i treść żądania JSON.
Wybierz link Wyślij żądanie powyżej nowego wiersza żądania PUT.
Żądanie PUT jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź . Treść odpowiedzi jest pusta, a kod stanu to 204.
Sprawdzanie i testowanie punktu końcowego DELETE
Przykładowa aplikacja implementuje pojedynczy punkt końcowy DELETE przy użyciu polecenia 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();
});
W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy punkt końcowy DELETE i wybierz pozycję Generuj żądanie.
Żądanie DELETE jest dodawane do
TodoApi.http
elementu .Zastąp element
{id}
w wierszu żądania DELETE ciągiem1
. Żądanie DELETE powinno wyglądać podobnie do następującego przykładu:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Wybierz link Wyślij żądanie dla żądania DELETE.
Żądanie DELETE jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź . Treść odpowiedzi jest pusta, a kod stanu to 204.
Korzystanie z interfejsu API grupy map
Przykładowy kod aplikacji powtarza prefiks adresu URL za każdym razem, gdy konfiguruje todoitems
punkt końcowy. Interfejsy API często mają grupy punktów końcowych z typowym prefiksem adresu URL, a MapGroup metoda jest dostępna w celu ułatwienia organizowania takich grup. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata.
Zastąp zawartość Program.cs
następującym kodem:
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();
Powyższy kod ma następujące zmiany:
- Dodaje
var todoItems = app.MapGroup("/todoitems");
element do skonfigurowania grupy przy użyciu prefiksu/todoitems
adresu URL . - Zmienia wszystkie
app.Map<HttpVerb>
metody natodoItems.Map<HttpVerb>
. - Usuwa prefiks
/todoitems
adresu URL zMap<HttpVerb>
wywołań metody.
Przetestuj punkty końcowe, aby sprawdzić, czy działają one tak samo.
Korzystanie z interfejsu API TypedResults
Zwracanie TypedResults , a nie Results ma kilku zalet, w tym możliwości testowania i automatycznego zwracania metadanych typu odpowiedzi dla interfejsu OpenAPI w celu opisania punktu końcowego. Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Metody Map<HttpVerb>
mogą wywoływać metody obsługi tras zamiast używać lambd. Aby zobaczyć przykład, zaktualizuj Program.cs przy użyciu następującego kodu:
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();
}
Kod Map<HttpVerb>
wywołuje teraz metody zamiast lambd:
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);
Te metody zwracają obiekty, które implementują IResult obiekty i są definiowane przez TypedResultselement :
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();
}
Testy jednostkowe mogą wywoływać te metody i testować, czy zwracają prawidłowy typ. Jeśli na przykład metoda to GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Kod testu jednostkowego może sprawdzić, czy obiekt typu Ok<Todo[]> jest zwracany z metody obsługi. Na przykład:
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);
}
Zapobieganie nadmiernemu delegowaniu
Obecnie przykładowa aplikacja uwidacznia cały Todo
obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku.
DTO jest używane w tym artykule.
Cel DTO może służyć do:
- Zapobiegaj nadmiernemu delegowaniu.
- Ukryj właściwości, których klienci nie powinni wyświetlać.
- Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
- Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.
Aby zademonstrować podejście DTO, zaktualizuj klasę Todo
tak, aby zawierała pole wpisu tajnego:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.
Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.
Utwórz plik o nazwie z TodoItemDTO.cs
następującym kodem:
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);
}
Zastąp zawartość Program.cs
pliku następującym kodem, aby użyć tego modelu DTO:
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();
}
Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.
Rozwiązywanie problemów z ukończonym przykładem
Jeśli napotkasz problem, nie możesz go rozwiązać, porównaj kod z ukończonym projektem. Wyświetl lub pobierz ukończony projekt (jak pobrać).
Następne kroki
- Skonfiguruj opcje serializacji JSON.
- Obsługa błędów i wyjątków: strona wyjątków dla deweloperów jest domyślnie włączona w środowisku projektowym dla minimalnych aplikacji interfejsu API. Aby uzyskać informacje na temat obsługi błędów i wyjątków, zobacz Obsługa błędów w interfejsach API platformy ASP.NET Core.
- Przykład testowania minimalnej aplikacji interfejsu API można znaleźć w tym przykładzie usługi GitHub.
- Obsługa interfejsu OpenAPI w minimalnych interfejsach API.
- Szybki start: publikowanie na platformie Azure.
- Organizowanie minimalnych interfejsów API ASP.NET Core.
Dowiedz się więcej
Minimalne interfejsy API są tworzone w celu tworzenia interfejsów API HTTP z minimalnymi zależnościami. Są one idealne dla mikrousług i aplikacji, które chcą uwzględniać tylko minimalne pliki, funkcje i zależności w ASP.NET Core.
W tym samouczku przedstawiono podstawy tworzenia minimalnego interfejsu API przy użyciu platformy ASP.NET Core. Innym podejściem do tworzenia interfejsów API w programie ASP.NET Core jest użycie kontrolerów. Aby uzyskać pomoc dotyczącą wybierania między minimalnymi interfejsami API i interfejsami API opartymi na kontrolerach, zobacz Omówienie interfejsów API. Aby zapoznać się z samouczkiem dotyczącym tworzenia projektu interfejsu API na podstawie kontrolerów zawierających więcej funkcji, zobacz Tworzenie internetowego interfejsu API.
Omówienie
Ten samouczek tworzy następujący interfejs API:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
POST /todoitems |
Dodawanie nowego elementu | Element do wykonania | Element do wykonania |
PUT /todoitems/{id} |
Aktualizowanie istniejącego elementu | Element do wykonania | Brak |
DELETE /todoitems/{id} |
Usuwanie elementu | Brak | Brak |
Wymagania wstępne
Program Visual Studio 2022 z pakietem roboczym tworzenia aplikacji ASP.NET i aplikacji internetowych.
Tworzenie projektu interfejsu API
Uruchom program Visual Studio 2022 i wybierz pozycję Utwórz nowy projekt.
W oknie dialogowym Tworzenie nowego projektu:
- Wprowadź ciąg
Empty
w polu wyszukiwania Wyszukaj szablony . - Wybierz szablon ASP.NET Core Empty i wybierz przycisk Dalej.
- Wprowadź ciąg
Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.
W oknie dialogowym Dodatkowe informacje:
- Wybierz pozycję .NET 7.0
- Usuń zaznaczenie pola Wyboru Nie używaj instrukcji najwyższego poziomu
- Wybierz pozycję Utwórz
Analizowanie kodu
Plik Program.cs
zawiera następujący kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod ma następujące działanie:
- Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
- Tworzy punkt końcowy
/
HTTP GET, który zwraca wartośćHello World!
:
Uruchom aplikację
Naciśnij Ctrl+F5, aby uruchomić bez debugera.
Program Visual Studio wyświetla następujące okno dialogowe:
Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.
Zostanie wyświetlone następujące okno dialogowe:
Wybierz pozycję Tak, jeśli wyrażasz zgodę na zaufanie certyfikatowi programistycznemu.
Aby uzyskać informacje na temat zaufania przeglądarce Firefox, zobacz Błąd certyfikatu przeglądarki Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Program Visual Studio uruchamia Kestrel serwer internetowy i otwiera okno przeglądarki.
Hello World!
jest wyświetlany w przeglądarce. Plik Program.cs
zawiera minimalną, ale kompletną aplikację.
Dodawanie pakietów NuGet
Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.
- W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
- Wybierz kartę Przeglądaj.
- Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję
Microsoft.EntityFrameworkCore.InMemory
. - Zaznacz pole wyboru Project (Projekt) w okienku po prawej stronie.
-
Z listy rozwijanej Wersja wybierz najnowszą dostępną wersję 7, na przykład
7.0.17
, a następnie wybierz pozycję Zainstaluj. - Postępuj zgodnie z poprzednimi instrukcjami, aby dodać
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
pakiet z najnowszą dostępną wersją 7.
Klasy kontekstu modelu i bazy danych
W folderze projektu utwórz plik o nazwie Todo.cs
z następującym kodem:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.
Utwórz plik o nazwie z TodoDb.cs
następującym kodem:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .
Dodawanie kodu interfejsu API
Zastąp zawartość pliku Program.cs
następującym kodem:
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();
Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.
Tworzenie interfejsu użytkownika testowania interfejsu API za pomocą struktury Swagger
Dostępnych jest wiele dostępnych internetowych narzędzi do testowania interfejsu API, które można wybrać, i możesz wykonać kroki wprowadzające do testowania interfejsu API przy użyciu własnego preferowanego narzędzia.
W tym samouczku wykorzystano pakiet .NET NSwag.AspNetCore, który integruje narzędzia Swagger do generowania interfejsu użytkownika testowania zgodnie ze specyfikacją interfejsu OpenAPI:
- NSwag: biblioteka platformy .NET, która integruje program Swagger bezpośrednio z aplikacjami ASP.NET Core, zapewniając oprogramowanie pośredniczące i konfigurację.
- Swagger: zestaw narzędzi typu open source, takich jak OpenAPIGenerator i SwaggerUI, które generują strony testowania interfejsu API zgodne ze specyfikacją interfejsu OpenAPI.
- Specyfikacja interfejsu OpenAPI: dokument opisujący możliwości interfejsu API na podstawie adnotacji XML i atrybutów w kontrolerach i modelach.
Aby uzyskać więcej informacji na temat korzystania z interfejsu OpenAPI i NSwag z ASP.NET, zobacz dokumentację internetowego interfejsu API platformy ASP.NET Core w programie Swagger/OpenAPI.
Instalowanie narzędzi struktury Swagger
Uruchom następujące polecenie:
dotnet add package NSwag.AspNetCore
Poprzednie polecenie dodaje pakiet NSwag.AspNetCore zawierający narzędzia do generowania dokumentów i interfejsu użytkownika struktury Swagger.
Konfigurowanie oprogramowania pośredniczącego programu Swagger
Dodaj następujący wyróżniony kod przed
app
zdefiniowaną w wierszuvar app = builder.Build();
using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
W poprzednim kodzie:
builder.Services.AddEndpointsApiExplorer();
: włącza Eksplorator interfejsu API, czyli usługę, która udostępnia metadane dotyczące interfejsu API HTTP. Eksplorator interfejsu API jest używany przez program Swagger do generowania dokumentu programu Swagger.builder.Services.AddOpenApiDocument(config => {...});
: dodaje generator dokumentów OpenAPI struktury Swagger do usług aplikacji i konfiguruje go w celu udostępnienia dodatkowych informacji o interfejsie API, takich jak jego tytuł i wersja. Aby uzyskać więcej informacji na temat zapewniania bardziej niezawodnych szczegółów interfejsu API, zobacz Rozpoczynanie pracy z siecią NSwag i ASP.NET CoreDodaj następujący wyróżniony kod do następnego wiersza po
app
zdefiniowaniu w wierszuvar app = builder.Build();
var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
Poprzedni kod umożliwia oprogramowanie pośredniczące struktury Swagger do obsługi wygenerowanego dokumentu JSON i interfejsu użytkownika programu Swagger. Program Swagger jest włączony tylko w środowisku projektowym. Włączenie struktury Swagger w środowisku produkcyjnym może spowodować ujawnienie potencjalnie poufnych szczegółów dotyczących struktury i implementacji interfejsu API.
Testowanie publikowania danych
Poniższy kod w pliku Program.cs
tworzy punkt końcowy /todoitems
HTTP POST, który dodaje dane do bazy danych w pamięci:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego /
.
Punkt końcowy POST będzie używany do dodawania danych do aplikacji.
Gdy aplikacja jest nadal uruchomiona, w przeglądarce przejdź do
https://localhost:<port>/swagger
strony testowania interfejsu API wygenerowanej przez program Swagger.Na stronie Testowanie interfejsu API programu Swagger wybierz pozycję Opublikuj /todoitems>Wypróbuj.
Zwróć uwagę, że pole Treść żądania zawiera wygenerowany przykładowy format odzwierciedlający parametry interfejsu API.
W treści żądania wprowadź kod JSON dla elementu do wykonania bez określania opcjonalnego elementu
id
:{ "name":"walk dog", "isComplete":true }
Wybierz polecenie Wykonaj.
Program Swagger udostępnia okienko Odpowiedzi poniżej przycisku Wykonaj .
Zwróć uwagę na kilka przydatnych szczegółów:
- cURL: Program Swagger udostępnia przykładowe polecenie cURL w składni systemu Unix/Linux, które można uruchomić w wierszu polecenia z dowolną powłoką bash korzystającą ze składni systemu Unix/Linux, w tym powłoki Git Bash z systemu Windows.
- Adres URL żądania: uproszczona reprezentacja żądania HTTP wykonanego przez kod JavaScript interfejsu użytkownika struktury Swagger dla wywołania interfejsu API. Rzeczywiste żądania mogą zawierać szczegóły, takie jak nagłówki i parametry zapytania oraz treść żądania.
- Odpowiedź serwera: zawiera treść odpowiedzi i nagłówki. Treść odpowiedzi pokazuje, że
id
ustawiono wartość1
. - Kod odpowiedzi: zwrócono kod stanu 201
HTTP
, wskazujący, że żądanie zostało pomyślnie przetworzone i spowodowało utworzenie nowego zasobu.
Sprawdzanie punktów końcowych GET
Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet
:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie wszystkich ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
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());
Testowanie punktów końcowych GET
Przetestuj aplikację, wywołując punkty końcowe z przeglądarki lub programu Swagger.
W programie Swagger wybierz pozycję GET /todoitems>Wypróbuj wykonanie.>
Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI
http://localhost:<port>/todoitems
. Na przykładhttp://localhost:5001/todoitems
Wywołanie metody w celu GET /todoitems
utworzenia odpowiedzi podobnej do następującej:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Wywołaj metodę GET /todoitems/{id} w programie Swagger, aby zwrócić dane z określonego identyfikatora:
- Wybierz pozycję GET /todoitems>Wypróbuj.
-
Ustaw pole id na
1
i wybierz pozycję Wykonaj.
Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI
https://localhost:<port>/todoitems/1
. Na przykładhttps://localhost:5001/todoitems/1
Odpowiedź jest podobna do następującej:
{ "id": 1, "name": "walk dog", "isComplete": true }
Ta aplikacja używa bazy danych w pamięci. Jeśli aplikacja zostanie ponownie uruchomiona, żądanie GET nie zwraca żadnych danych. Jeśli żadne dane nie są zwracane, prześlij dane POST do aplikacji i spróbuj ponownie wysłać żądanie GET.
Wartości zwracane
ASP.NET Core automatycznie serializuje obiekt w formacie JSON i zapisuje kod JSON w treści komunikatu odpowiedzi. Kod odpowiedzi dla tego typu zwracanego to 200 OK, zakładając, że nie ma żadnych nieobsługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.
Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id}
może zwrócić dwie różne wartości stanu:
- Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
- W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie
item
wyników w odpowiedzi HTTP 200.
Badanie punktu końcowego PUT
Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia 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();
});
Ta metoda jest podobna do metody , z tą różnicą MapPost
, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.
Testowanie punktu końcowego PUT
W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.
Zaktualizuj element to-do, który ma Id = 1
wartość , i ustaw jego nazwę na "feed fish"
.
Użyj struktury Swagger, aby wysłać żądanie PUT:
Wybierz pozycję Umieść /todoitems/{id}>Wypróbuj.
Ustaw pole id na
1
.Ustaw treść żądania na następujący kod JSON:
{ "name": "feed fish", "isComplete": false }
Wybierz polecenie Wykonaj.
Sprawdzanie i testowanie punktu końcowego DELETE
Przykładowa aplikacja implementuje pojedynczy punkt końcowy DELETE przy użyciu polecenia 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();
});
Użyj struktury Swagger, aby wysłać żądanie DELETE:
Wybierz pozycję USUŃ /todoitems/{id}>Wypróbuj.
Ustaw pole ID na
1
i wybierz pozycję Wykonaj.Żądanie DELETE jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedzi . Treść odpowiedzi jest pusta, a kod stanu odpowiedzi serwera to 204.
Korzystanie z interfejsu API grupy map
Przykładowy kod aplikacji powtarza prefiks adresu URL za każdym razem, gdy konfiguruje todoitems
punkt końcowy. Interfejsy API często mają grupy punktów końcowych z typowym prefiksem adresu URL, a MapGroup metoda jest dostępna w celu ułatwienia organizowania takich grup. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata.
Zastąp zawartość Program.cs
następującym kodem:
using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
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();
Powyższy kod ma następujące zmiany:
- Dodaje
var todoItems = app.MapGroup("/todoitems");
element do skonfigurowania grupy przy użyciu prefiksu/todoitems
adresu URL . - Zmienia wszystkie
app.Map<HttpVerb>
metody natodoItems.Map<HttpVerb>
. - Usuwa prefiks
/todoitems
adresu URL zMap<HttpVerb>
wywołań metody.
Przetestuj punkty końcowe, aby sprawdzić, czy działają one tak samo.
Korzystanie z interfejsu API TypedResults
Zwracanie TypedResults , a nie Results ma kilku zalet, w tym możliwości testowania i automatycznego zwracania metadanych typu odpowiedzi dla interfejsu OpenAPI w celu opisania punktu końcowego. Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Metody Map<HttpVerb>
mogą wywoływać metody obsługi tras zamiast używać lambd. Aby zobaczyć przykład, zaktualizuj Program.cs przy użyciu następującego kodu:
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();
}
Kod Map<HttpVerb>
wywołuje teraz metody zamiast lambd:
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);
Te metody zwracają obiekty, które implementują IResult obiekty i są definiowane przez TypedResultselement :
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();
}
Testy jednostkowe mogą wywoływać te metody i testować, czy zwracają prawidłowy typ. Jeśli na przykład metoda to GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Kod testu jednostkowego może sprawdzić, czy obiekt typu Ok<Todo[]> jest zwracany z metody obsługi. Na przykład:
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);
}
Zapobieganie nadmiernemu delegowaniu
Obecnie przykładowa aplikacja uwidacznia cały Todo
obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku.
DTO jest używane w tym artykule.
Cel DTO może służyć do:
- Zapobiegaj nadmiernemu delegowaniu.
- Ukryj właściwości, których klienci nie powinni wyświetlać.
- Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
- Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.
Aby zademonstrować podejście DTO, zaktualizuj klasę Todo
tak, aby zawierała pole wpisu tajnego:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.
Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.
Utwórz plik o nazwie z TodoItemDTO.cs
następującym kodem:
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);
}
Zastąp zawartość Program.cs
pliku następującym kodem, aby użyć tego modelu DTO:
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>();
}
Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.
Rozwiązywanie problemów z ukończonym przykładem
Jeśli napotkasz problem, nie możesz go rozwiązać, porównaj kod z ukończonym projektem. Wyświetl lub pobierz ukończony projekt (jak pobrać).
Następne kroki
- Skonfiguruj opcje serializacji JSON.
- Obsługa błędów i wyjątków: strona wyjątków dla deweloperów jest domyślnie włączona w środowisku projektowym dla minimalnych aplikacji interfejsu API. Aby uzyskać informacje na temat obsługi błędów i wyjątków, zobacz Obsługa błędów w interfejsach API platformy ASP.NET Core.
- Przykład testowania minimalnej aplikacji interfejsu API można znaleźć w tym przykładzie usługi GitHub.
- Obsługa interfejsu OpenAPI w minimalnych interfejsach API.
- Szybki start: publikowanie na platformie Azure.
- Organizowanie minimalnych interfejsów API ASP.NET Core.
Dowiedz się więcej
Minimalne interfejsy API są tworzone w celu tworzenia interfejsów API HTTP z minimalnymi zależnościami. Są one idealne dla mikrousług i aplikacji, które chcą uwzględniać tylko minimalne pliki, funkcje i zależności w ASP.NET Core.
W tym samouczku przedstawiono podstawy tworzenia minimalnego interfejsu API przy użyciu platformy ASP.NET Core. Innym podejściem do tworzenia interfejsów API w programie ASP.NET Core jest użycie kontrolerów. Aby uzyskać pomoc dotyczącą wybierania między minimalnymi interfejsami API i interfejsami API opartymi na kontrolerach, zobacz Omówienie interfejsów API. Aby zapoznać się z samouczkiem dotyczącym tworzenia projektu interfejsu API na podstawie kontrolerów zawierających więcej funkcji, zobacz Tworzenie internetowego interfejsu API.
Omówienie
Ten samouczek tworzy następujący interfejs API:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
POST /todoitems |
Dodawanie nowego elementu | Element do wykonania | Element do wykonania |
PUT /todoitems/{id} |
Aktualizowanie istniejącego elementu | Element do wykonania | Brak |
DELETE /todoitems/{id} |
Usuwanie elementu | Brak | Brak |
Wymagania wstępne
- Program Visual Studio 2022 z pakietem roboczym tworzenia aplikacji ASP.NET i aplikacji internetowych.
- Zestaw SDK dla platformy .NET 6.0
Tworzenie projektu interfejsu API
Uruchom program Visual Studio 2022 i wybierz pozycję Utwórz nowy projekt.
W oknie dialogowym Tworzenie nowego projektu:
- Wprowadź ciąg
Empty
w polu wyszukiwania Wyszukaj szablony . - Wybierz szablon ASP.NET Core Empty i wybierz przycisk Dalej.
- Wprowadź ciąg
Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.
W oknie dialogowym Dodatkowe informacje:
- Wybierz pozycję .NET 6.0
- Usuń zaznaczenie pola Wyboru Nie używaj instrukcji najwyższego poziomu
- Wybierz pozycję Utwórz
Analizowanie kodu
Plik Program.cs
zawiera następujący kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod ma następujące działanie:
- Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
- Tworzy punkt końcowy
/
HTTP GET, który zwraca wartośćHello World!
:
Uruchom aplikację
Naciśnij Ctrl+F5, aby uruchomić bez debugera.
Program Visual Studio wyświetla następujące okno dialogowe:
Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.
Zostanie wyświetlone następujące okno dialogowe:
Wybierz pozycję Tak, jeśli wyrażasz zgodę na zaufanie certyfikatowi programistycznemu.
Aby uzyskać informacje na temat zaufania przeglądarce Firefox, zobacz Błąd certyfikatu przeglądarki Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Program Visual Studio uruchamia Kestrel serwer internetowy i otwiera okno przeglądarki.
Hello World!
jest wyświetlany w przeglądarce. Plik Program.cs
zawiera minimalną, ale kompletną aplikację.
Dodawanie pakietów NuGet
Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.
- W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
- Wybierz kartę Przeglądaj.
- Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję
Microsoft.EntityFrameworkCore.InMemory
. - Zaznacz pole wyboru Project (Projekt) w okienku po prawej stronie.
-
Z listy rozwijanej Wersja wybierz najnowszą dostępną wersję 7, na przykład
6.0.28
, a następnie wybierz pozycję Zainstaluj. - Postępuj zgodnie z poprzednimi instrukcjami, aby dodać
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
pakiet z najnowszą dostępną wersją 7.
Klasy kontekstu modelu i bazy danych
W folderze projektu utwórz plik o nazwie Todo.cs
z następującym kodem:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.
Utwórz plik o nazwie z TodoDb.cs
następującym kodem:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .
Dodawanie kodu interfejsu API
Zastąp zawartość pliku Program.cs
następującym kodem:
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();
Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.
Tworzenie interfejsu użytkownika testowania interfejsu API za pomocą struktury Swagger
Dostępnych jest wiele dostępnych internetowych narzędzi do testowania interfejsu API, które można wybrać, i możesz wykonać kroki wprowadzające do testowania interfejsu API przy użyciu własnego preferowanego narzędzia.
W tym samouczku wykorzystano pakiet .NET NSwag.AspNetCore, który integruje narzędzia Swagger do generowania interfejsu użytkownika testowania zgodnie ze specyfikacją interfejsu OpenAPI:
- NSwag: biblioteka platformy .NET, która integruje program Swagger bezpośrednio z aplikacjami ASP.NET Core, zapewniając oprogramowanie pośredniczące i konfigurację.
- Swagger: zestaw narzędzi typu open source, takich jak OpenAPIGenerator i SwaggerUI, które generują strony testowania interfejsu API zgodne ze specyfikacją interfejsu OpenAPI.
- Specyfikacja interfejsu OpenAPI: dokument opisujący możliwości interfejsu API na podstawie adnotacji XML i atrybutów w kontrolerach i modelach.
Aby uzyskać więcej informacji na temat korzystania z interfejsu OpenAPI i NSwag z ASP.NET, zobacz dokumentację internetowego interfejsu API platformy ASP.NET Core w programie Swagger/OpenAPI.
Instalowanie narzędzi struktury Swagger
Uruchom następujące polecenie:
dotnet add package NSwag.AspNetCore
Poprzednie polecenie dodaje pakiet NSwag.AspNetCore zawierający narzędzia do generowania dokumentów i interfejsu użytkownika struktury Swagger.
Konfigurowanie oprogramowania pośredniczącego programu Swagger
W Program.cs dodaj następujące
using
instrukcje u góry:using NSwag.AspNetCore;
Dodaj następujący wyróżniony kod przed
app
zdefiniowaną w wierszuvar app = builder.Build();
using NSwag.AspNetCore; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
W poprzednim kodzie:
builder.Services.AddEndpointsApiExplorer();
: włącza Eksplorator interfejsu API, czyli usługę, która udostępnia metadane dotyczące interfejsu API HTTP. Eksplorator interfejsu API jest używany przez program Swagger do generowania dokumentu programu Swagger.builder.Services.AddOpenApiDocument(config => {...});
: dodaje generator dokumentów OpenAPI struktury Swagger do usług aplikacji i konfiguruje go w celu udostępnienia dodatkowych informacji o interfejsie API, takich jak jego tytuł i wersja. Aby uzyskać więcej informacji na temat zapewniania bardziej niezawodnych szczegółów interfejsu API, zobacz Rozpoczynanie pracy z siecią NSwag i ASP.NET CoreDodaj następujący wyróżniony kod do następnego wiersza po
app
zdefiniowaniu w wierszuvar app = builder.Build();
var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
Poprzedni kod umożliwia oprogramowanie pośredniczące struktury Swagger do obsługi wygenerowanego dokumentu JSON i interfejsu użytkownika programu Swagger. Program Swagger jest włączony tylko w środowisku projektowym. Włączenie struktury Swagger w środowisku produkcyjnym może spowodować ujawnienie potencjalnie poufnych szczegółów dotyczących struktury i implementacji interfejsu API.
Testowanie publikowania danych
Poniższy kod w pliku Program.cs
tworzy punkt końcowy /todoitems
HTTP POST, który dodaje dane do bazy danych w pamięci:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego /
.
Punkt końcowy POST będzie używany do dodawania danych do aplikacji.
Gdy aplikacja jest nadal uruchomiona, w przeglądarce przejdź do
https://localhost:<port>/swagger
strony testowania interfejsu API wygenerowanej przez program Swagger.Na stronie Testowanie interfejsu API programu Swagger wybierz pozycję Opublikuj /todoitems>Wypróbuj.
Zwróć uwagę, że pole Treść żądania zawiera wygenerowany przykładowy format odzwierciedlający parametry interfejsu API.
W treści żądania wprowadź kod JSON dla elementu do wykonania bez określania opcjonalnego elementu
id
:{ "name":"walk dog", "isComplete":true }
Wybierz polecenie Wykonaj.
Program Swagger udostępnia okienko Odpowiedzi poniżej przycisku Wykonaj .
Zwróć uwagę na kilka przydatnych szczegółów:
- cURL: Program Swagger udostępnia przykładowe polecenie cURL w składni systemu Unix/Linux, które można uruchomić w wierszu polecenia z dowolną powłoką bash korzystającą ze składni systemu Unix/Linux, w tym powłoki Git Bash z systemu Windows.
- Adres URL żądania: uproszczona reprezentacja żądania HTTP wykonanego przez kod JavaScript interfejsu użytkownika struktury Swagger dla wywołania interfejsu API. Rzeczywiste żądania mogą zawierać szczegóły, takie jak nagłówki i parametry zapytania oraz treść żądania.
- Odpowiedź serwera: zawiera treść odpowiedzi i nagłówki. Treść odpowiedzi pokazuje, że
id
ustawiono wartość1
. - Kod odpowiedzi: zwrócono kod stanu 201
HTTP
, wskazujący, że żądanie zostało pomyślnie przetworzone i spowodowało utworzenie nowego zasobu.
Sprawdzanie punktów końcowych GET
Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet
:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie wszystkich ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
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());
Testowanie punktów końcowych GET
Przetestuj aplikację, wywołując punkty końcowe z przeglądarki lub programu Swagger.
W programie Swagger wybierz pozycję GET /todoitems>Wypróbuj wykonanie.>
Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI
http://localhost:<port>/todoitems
. Na przykładhttp://localhost:5001/todoitems
Wywołanie metody w celu GET /todoitems
utworzenia odpowiedzi podobnej do następującej:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Wywołaj metodę GET /todoitems/{id} w programie Swagger, aby zwrócić dane z określonego identyfikatora:
- Wybierz pozycję GET /todoitems>Wypróbuj.
-
Ustaw pole id na
1
i wybierz pozycję Wykonaj.
Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI
https://localhost:<port>/todoitems/1
. Na przykład:https://localhost:5001/todoitems/1
Odpowiedź jest podobna do następującej:
{ "id": 1, "name": "walk dog", "isComplete": true }
Ta aplikacja używa bazy danych w pamięci. Jeśli aplikacja zostanie ponownie uruchomiona, żądanie GET nie zwraca żadnych danych. Jeśli żadne dane nie są zwracane, prześlij dane POST do aplikacji i spróbuj ponownie wysłać żądanie GET.
Wartości zwracane
ASP.NET Core automatycznie serializuje obiekt w formacie JSON i zapisuje kod JSON w treści komunikatu odpowiedzi. Kod odpowiedzi dla tego typu zwracanego to 200 OK, zakładając, że nie ma żadnych nieobsługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.
Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id}
może zwrócić dwie różne wartości stanu:
- Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
- W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie
item
wyników w odpowiedzi HTTP 200.
Badanie punktu końcowego PUT
Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia 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();
});
Ta metoda jest podobna do metody , z tą różnicą MapPost
, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.
Testowanie punktu końcowego PUT
W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.
Zaktualizuj element to-do, który ma Id = 1
wartość , i ustaw jego nazwę na "feed fish"
.
Użyj struktury Swagger, aby wysłać żądanie PUT:
Wybierz pozycję Umieść /todoitems/{id}>Wypróbuj.
Ustaw pole id na
1
.Ustaw treść żądania na następujący kod JSON:
{ "name": "feed fish", "isComplete": false }
Wybierz polecenie Wykonaj.
Sprawdzanie i testowanie punktu końcowego DELETE
Przykładowa aplikacja implementuje pojedynczy punkt końcowy DELETE przy użyciu polecenia 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();
});
Użyj struktury Swagger, aby wysłać żądanie DELETE:
Wybierz pozycję USUŃ /todoitems/{id}>Wypróbuj.
Ustaw pole ID na
1
i wybierz pozycję Wykonaj.Żądanie DELETE jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedzi . Treść odpowiedzi jest pusta, a kod stanu odpowiedzi serwera to 204.
Zapobieganie nadmiernemu delegowaniu
Obecnie przykładowa aplikacja uwidacznia cały Todo
obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku.
DTO jest używane w tym artykule.
Cel DTO może służyć do:
- Zapobiegaj nadmiernemu delegowaniu.
- Ukryj właściwości, których klienci nie powinni wyświetlać.
- Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
- Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.
Aby zademonstrować podejście DTO, zaktualizuj klasę Todo
tak, aby zawierała pole wpisu tajnego:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.
Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.
Utwórz plik o nazwie z TodoItemDTO.cs
następującym kodem:
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);
}
Zastąp zawartość Program.cs
pliku następującym kodem, aby użyć tego modelu DTO:
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>();
}
Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.
Testowanie minimalnego interfejsu API
Przykład testowania minimalnej aplikacji interfejsu API można znaleźć w tym przykładzie usługi GitHub.
Publikowanie na platformie Azure
Aby uzyskać informacje na temat wdrażania na platformie Azure, zobacz Szybki start: wdrażanie aplikacji internetowej ASP.NET.
Dodatkowe zasoby
Minimalne interfejsy API są tworzone w celu tworzenia interfejsów API HTTP z minimalnymi zależnościami. Są one idealne dla mikrousług i aplikacji, które chcą uwzględniać tylko minimalne pliki, funkcje i zależności w ASP.NET Core.
W tym samouczku przedstawiono podstawy tworzenia minimalnego interfejsu API przy użyciu platformy ASP.NET Core. Innym podejściem do tworzenia interfejsów API w programie ASP.NET Core jest użycie kontrolerów. Aby uzyskać pomoc dotyczącą wybierania między minimalnymi interfejsami API i interfejsami API opartymi na kontrolerach, zobacz Omówienie interfejsów API. Aby zapoznać się z samouczkiem dotyczącym tworzenia projektu interfejsu API na podstawie kontrolerów zawierających więcej funkcji, zobacz Tworzenie internetowego interfejsu API.
Omówienie
Ten samouczek tworzy następujący interfejs API:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
POST /todoitems |
Dodawanie nowego elementu | Element do wykonania | Element do wykonania |
PUT /todoitems/{id} |
Aktualizowanie istniejącego elementu | Element do wykonania | Brak |
DELETE /todoitems/{id} |
Usuwanie elementu | Brak | Brak |
Wymagania wstępne
Program Visual Studio 2022 z pakietem roboczym tworzenia aplikacji ASP.NET i aplikacji internetowych.
Tworzenie projektu interfejsu API
Uruchom program Visual Studio 2022 i wybierz pozycję Utwórz nowy projekt.
W oknie dialogowym Tworzenie nowego projektu:
- Wprowadź ciąg
Empty
w polu wyszukiwania Wyszukaj szablony . - Wybierz szablon ASP.NET Core Empty i wybierz przycisk Dalej.
- Wprowadź ciąg
Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.
W oknie dialogowym Dodatkowe informacje:
- Wybierz pozycję .NET 8.0 (obsługa długoterminowa)
- Usuń zaznaczenie pola Wyboru Nie używaj instrukcji najwyższego poziomu
- Wybierz pozycję Utwórz
Analizowanie kodu
Plik Program.cs
zawiera następujący kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod ma następujące działanie:
- Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
- Tworzy punkt końcowy
/
HTTP GET, który zwraca wartośćHello World!
:
Uruchom aplikację
Naciśnij Ctrl+F5, aby uruchomić bez debugera.
Program Visual Studio wyświetla następujące okno dialogowe:
Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.
Zostanie wyświetlone następujące okno dialogowe:
Wybierz pozycję Tak, jeśli wyrażasz zgodę na zaufanie certyfikatowi programistycznemu.
Aby uzyskać informacje na temat zaufania przeglądarce Firefox, zobacz Błąd certyfikatu przeglądarki Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Program Visual Studio uruchamia Kestrel serwer internetowy i otwiera okno przeglądarki.
Hello World!
jest wyświetlany w przeglądarce. Plik Program.cs
zawiera minimalną, ale kompletną aplikację.
Zamknij okno przeglądarki.
Dodawanie pakietów NuGet
Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.
- W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
- Wybierz kartę Przeglądaj.
- Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję
Microsoft.EntityFrameworkCore.InMemory
. - Zaznacz pole wyboru Projekt w okienku po prawej stronie, a następnie wybierz pozycję Zainstaluj.
- Postępuj zgodnie z poprzednimi instrukcjami, aby dodać
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
pakiet.
Klasy kontekstu modelu i bazy danych
- W folderze projektu utwórz plik o nazwie
Todo.cs
z następującym kodem:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.
- Utwórz plik o nazwie z
TodoDb.cs
następującym kodem:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .
Dodawanie kodu interfejsu API
- Zastąp zawartość pliku
Program.cs
następującym kodem:
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();
Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.
W tym samouczku używane są pliki Endpoints Explorer i .http do testowania interfejsu API.
Testowanie publikowania danych
Poniższy kod w pliku Program.cs
tworzy punkt końcowy /todoitems
HTTP POST, który dodaje dane do bazy danych w pamięci:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego /
.
Punkt końcowy POST będzie używany do dodawania danych do aplikacji.
Wybierz pozycję Wyświetl>inne eksploratora punktów końcowych systemu Windows.>
Kliknij prawym przyciskiem myszy punkt końcowy POST i wybierz polecenie Generuj żądanie.
Nowy plik jest tworzony w folderze projektu o nazwie
TodoApi.http
, z zawartością podobną do następującego przykładu:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- Pierwszy wiersz tworzy zmienną używaną dla wszystkich punktów końcowych.
- Następny wiersz definiuje żądanie POST.
- Potrójny hasztag (
###
) wiersz jest ogranicznikiem żądania: co następuje po nim dla innego żądania.
Żądanie POST wymaga nagłówków i treści. Aby zdefiniować te części żądania, dodaj następujące wiersze bezpośrednio po wierszu żądania POST:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Powyższy kod dodaje nagłówek Content-Type i treść żądania JSON. Plik TodoApi.http powinien teraz wyglądać podobnie do poniższego przykładu, ale z numerem portu:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Uruchom aplikację.
Wybierz link Wyślij żądanie powyżej
POST
wiersza żądania.Żądanie POST jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .
Sprawdzanie punktów końcowych GET
Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet
:
Interfejs API | opis | Treść żądania | Treść odpowiedzi |
---|---|---|---|
GET /todoitems |
Pobieranie wszystkich elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/complete |
Pobieranie wszystkich ukończonych elementów do wykonania | Brak | Tablica elementów do wykonania |
GET /todoitems/{id} |
Pobieranie elementu według identyfikatora | Brak | Element do wykonania |
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());
Testowanie punktów końcowych GET
Przetestuj GET
aplikację, wywołując punkty końcowe z przeglądarki lub przy użyciu Eksploratora punktów końcowych. Poniższe kroki dotyczą Eksploratora punktów końcowych.
W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy pierwszy punkt końcowy GET i wybierz polecenie Generuj żądanie.
Do pliku zostanie dodana następująca
TodoApi.http
zawartość:Get {{TodoApi_HostAddress}}/todoitems ###
Wybierz link Wyślij żądanie powyżej nowego
GET
wiersza żądania.Żądanie GET jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .
Treść odpowiedzi jest podobna do następującego kodu JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
W Eksploratorze punktów końcowych kliknij prawym przyciskiem
/todoitems/{id}
myszy punkt końcowy GET i wybierz pozycję Generuj żądanie. Do pliku zostanie dodana następującaTodoApi.http
zawartość:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Zamień
{id}
na1
.Wybierz link Wyślij żądanie powyżej nowego wiersza żądania GET.
Żądanie GET jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .
Treść odpowiedzi jest podobna do następującego kodu JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Ta aplikacja używa bazy danych w pamięci. Jeśli aplikacja zostanie ponownie uruchomiona, żądanie GET nie zwraca żadnych danych. Jeśli żadne dane nie są zwracane, prześlij dane POST do aplikacji i spróbuj ponownie wysłać żądanie GET.
Wartości zwracane
ASP.NET Core automatycznie serializuje obiekt w formacie JSON i zapisuje kod JSON w treści komunikatu odpowiedzi. Kod odpowiedzi dla tego typu zwracanego to 200 OK, zakładając, że nie ma żadnych nieobsługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.
Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id}
może zwrócić dwie różne wartości stanu:
- Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
- W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie
item
wyników w odpowiedzi HTTP 200.
Badanie punktu końcowego PUT
Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia 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();
});
Ta metoda jest podobna do metody , z tą różnicą MapPost
, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.
Testowanie punktu końcowego PUT
W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.
Zaktualizuj element to-do, który ma Id = 1
wartość , i ustaw jego nazwę na "feed fish"
.
W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy punkt końcowy PUT i wybierz pozycję Generuj żądanie.
Do pliku zostanie dodana następująca
TodoApi.http
zawartość:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
W wierszu żądania PUT zastąp ciąg
{id}
ciągiem1
.Dodaj następujące wiersze bezpośrednio po wierszu żądania PUT:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Powyższy kod dodaje nagłówek Content-Type i treść żądania JSON.
Wybierz link Wyślij żądanie powyżej nowego wiersza żądania PUT.
Żądanie PUT jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź . Treść odpowiedzi jest pusta, a kod stanu to 204.
Sprawdzanie i testowanie punktu końcowego DELETE
Przykładowa aplikacja implementuje pojedynczy punkt końcowy DELETE przy użyciu polecenia 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();
});
W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy punkt końcowy DELETE i wybierz pozycję Generuj żądanie.
Żądanie DELETE jest dodawane do
TodoApi.http
elementu .Zastąp element
{id}
w wierszu żądania DELETE ciągiem1
. Żądanie DELETE powinno wyglądać podobnie do następującego przykładu:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Wybierz link Wyślij żądanie dla żądania DELETE.
Żądanie DELETE jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź . Treść odpowiedzi jest pusta, a kod stanu to 204.
Korzystanie z interfejsu API grupy map
Przykładowy kod aplikacji powtarza prefiks adresu URL za każdym razem, gdy konfiguruje todoitems
punkt końcowy. Interfejsy API często mają grupy punktów końcowych z typowym prefiksem adresu URL, a MapGroup metoda jest dostępna w celu ułatwienia organizowania takich grup. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata.
Zastąp zawartość Program.cs
następującym kodem:
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();
Powyższy kod ma następujące zmiany:
- Dodaje
var todoItems = app.MapGroup("/todoitems");
element do skonfigurowania grupy przy użyciu prefiksu/todoitems
adresu URL . - Zmienia wszystkie
app.Map<HttpVerb>
metody natodoItems.Map<HttpVerb>
. - Usuwa prefiks
/todoitems
adresu URL zMap<HttpVerb>
wywołań metody.
Przetestuj punkty końcowe, aby sprawdzić, czy działają one tak samo.
Korzystanie z interfejsu API TypedResults
Zwracanie TypedResults , a nie Results ma kilku zalet, w tym możliwości testowania i automatycznego zwracania metadanych typu odpowiedzi dla interfejsu OpenAPI w celu opisania punktu końcowego. Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Metody Map<HttpVerb>
mogą wywoływać metody obsługi tras zamiast używać lambd. Aby zobaczyć przykład, zaktualizuj Program.cs przy użyciu następującego kodu:
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();
}
Kod Map<HttpVerb>
wywołuje teraz metody zamiast lambd:
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);
Te metody zwracają obiekty, które implementują IResult obiekty i są definiowane przez TypedResultselement :
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();
}
Testy jednostkowe mogą wywoływać te metody i testować, czy zwracają prawidłowy typ. Jeśli na przykład metoda to GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Kod testu jednostkowego może sprawdzić, czy obiekt typu Ok<Todo[]> jest zwracany z metody obsługi. Na przykład:
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);
}
Zapobieganie nadmiernemu delegowaniu
Obecnie przykładowa aplikacja uwidacznia cały Todo
obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku.
DTO jest używane w tym artykule.
Cel DTO może służyć do:
- Zapobiegaj nadmiernemu delegowaniu.
- Ukryj właściwości, których klienci nie powinni wyświetlać.
- Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
- Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.
Aby zademonstrować podejście DTO, zaktualizuj klasę Todo
tak, aby zawierała pole wpisu tajnego:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.
Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.
Utwórz plik o nazwie z TodoItemDTO.cs
następującym kodem:
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);
}
Zastąp zawartość Program.cs
pliku następującym kodem, aby użyć tego modelu DTO:
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();
}
Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.
Rozwiązywanie problemów z ukończonym przykładem
Jeśli napotkasz problem, nie możesz go rozwiązać, porównaj kod z ukończonym projektem. Wyświetl lub pobierz ukończony projekt (jak pobrać).
Następne kroki
- Skonfiguruj opcje serializacji JSON.
- Obsługa błędów i wyjątków: strona wyjątków dla deweloperów jest domyślnie włączona w środowisku projektowym dla minimalnych aplikacji interfejsu API. Aby uzyskać informacje na temat obsługi błędów i wyjątków, zobacz Obsługa błędów w interfejsach API platformy ASP.NET Core.
- Przykład testowania minimalnej aplikacji interfejsu API można znaleźć w tym przykładzie usługi GitHub.
- Obsługa interfejsu OpenAPI w minimalnych interfejsach API.
- Szybki start: publikowanie na platformie Azure.
- Organizowanie minimalnych interfejsów API ASP.NET Core.