Udostępnij za pośrednictwem


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ę artykułu w.NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję artykułu w.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ę artykułu w.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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz ukończone zadania 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

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.

    Visual Studio Tworzenie nowego projektu

  • Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.

  • W oknie dialogowym Dodatkowe informacje:

    • Wybierz .NET 9.0
    • Usuń zaznaczenie Nie używaj instrukcji najwyższego poziomu
    • Wybierz pozycję Utwórz

    Dodatkowe informacje

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();

Poprzedni kod:

  • 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:

Ten projekt jest skonfigurowany do używania protokołu SSL. Aby uniknąć ostrzeżeń SSL w przeglądarce, możesz zaufać certyfikatowi z podpisem własnym wygenerowanemu przez usługę IIS Express. Czy chcesz ufać certyfikatowi SSL usług IIS Express?

Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.

Zostanie wyświetlone następujące okno dialogowe:

Okno dialogowe ostrzeżenia o zabezpieczeniach

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

Aby zapewnić wsparcie dla bazy danych i diagnostyki używanych w tym samouczku, należy dodać pakiety NuGet.

  • W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
  • Wybierz kartę Przeglądaj.
  • Wybierz Uwzględnij wersję przedpremierową.
  • 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 TodoDb.cs z 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ą Endpoints Explorer oraz pliki .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 Wyświetl>Inne okna>Eksplorator punktów końcowych.

  • Kliknij prawym przyciskiem myszy punkt końcowy POST i wybierz polecenie Generuj żądanie.

    Menu kontekstowe Eksploratora punktów końcowych z wyróżnionym elementem menu Generowanie żądania.

    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 „(c0/)” stanowi ogranicznik żądania: to, co pojawia się po nim, dotyczy 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.

    Okno pliku .http z wyróżnionym linkiem uruchamiania.

    Żądanie POST jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .

    Okno pliku .http z odpowiedzią z żądania POST.

Sprawdź punkty końcowe 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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz wszystkie ukończone zadania 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 końcowe punkty dostępu z przeglądarki lub korzystając z 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ąca TodoApi.http zawartość:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Zamień {id} na 1.

  • 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ługiwanych wyjątków. 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. Zwracając item, zwraca odpowiedź HTTP 200.

Sprawdź punkt końcowy 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 MapPost, z tą różnicą, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca 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 zadanie, które ma Id = 1, 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ągiem 1.

  • Dodaj następujące wiersze bezpośrednio po wierszu żądania PUT:

    Content-Type: application/json
    
    {
      "id": 1,
      "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.

  • Zamień znacznik {id} w wierszu żądania DELETE na 1. Żą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 MapGroup API

Przykładowy kod aplikacji przy każdym konfigurowaniu punktu końcowego powtarza prefiks adresu URL todoitems. 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"); do skonfigurowania grupy przy użyciu prefiksu adresu URL /todoitems.
  • Zmienia wszystkie app.Map<HttpVerb> metody na todoItems.Map<HttpVerb>.
  • Usuwa prefiks /todoitems adresu URL z Map<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.

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 i są definiowane przez TypedResults element:

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 publikowaniu

Obecnie przykładowa aplikacja uwidacznia cały Todo obiekt. 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, a jednym z głównych jest bezpieczeństwo. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

DTO może być użyty do:

  • Zapobiegaj nadmiernemu publikowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczaj grafy 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 tajne:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Pole tajne musi być schowane w tej aplikacji, ale aplikacja administracyjna może je uwidocznić.

Sprawdź, czy możesz wysłać i pobrać pole tajnego.

Utwórz plik o nazwie TodoItemDTO.cs z 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 wysyłać i pobierać wszystkie pola z wyjątkiem tajnego pola.

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

Dowiedz się więcej

Zobacz krótkie informacje o minimalnych interfejsach API

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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz ukończone zadania 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

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.

    Visual Studio Tworzenie nowego projektu

  • Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.

  • W oknie dialogowym Dodatkowe informacje:

    • Wybierz pozycję .NET 7.0
    • Usuń zaznaczenie Nie używaj instrukcji najwyższego poziomu
    • Wybierz pozycję Utwórz

    Dodatkowe informacje

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();

Poprzedni kod:

  • 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:

Ten projekt jest skonfigurowany do używania protokołu SSL. Aby uniknąć ostrzeżeń SSL w przeglądarce, możesz zaufać certyfikatowi z podpisem własnym wygenerowanemu przez usługę IIS Express. Czy chcesz ufać certyfikatowi SSL usług IIS Express?

Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.

Zostanie wyświetlone następujące okno dialogowe:

Okno dialogowe ostrzeżenia o zabezpieczeniach

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

Aby zapewnić wsparcie dla bazy danych i diagnostyki używanych w tym samouczku, należy dodać pakiety NuGet.

  • 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 prawym okienku.
  • 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 powyższymi instrukcjami, aby dodać pakiet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore z najnowszą 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 TodoDb.cs z 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 UI do testowania API za pomocą Swaggera

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 NSwag.AspNetCore dla platformy .NET, który integruje narzędzia Swagger do generowania interfejsu użytkownika do testowania zgodnie ze specyfikacją 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.

Zainstaluj narzędzia Swagger

  • Uruchom następujące polecenie:

    dotnet add package NSwag.AspNetCore
    

Poprzednie polecenie dodaje pakiet NSwag.AspNetCore, który zawiera narzędzia do generowania dokumentów i interfejsu użytkownika Swagger.

Konfigurowanie middleware Swagger

  • Dodaj następujący wyróżniony kod przed app zdefiniowaną w wierszu var 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 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 dostarczania bardziej szczegółowych informacji o interfejsie API, zobacz Rozpoczynanie pracy z NSwag i ASP.NET Core

  • Po zdefiniowaniu app w wierszu var app = builder.Build();, dodaj następujący wyróżniony kod w kolejnym wierszu.

    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 Swagger do obsługi wygenerowanego dokumentu JSON i Swagger UI. Program Swagger jest włączony tylko w środowisku projektowym. Włączenie Swagger w środowisku produkcyjnym może ujawnić potencjalnie poufne szczegóły dotyczące 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.

    Strona testowania interfejsu API wygenerowanego programu Swagger

  • Na stronie testowania API Swagger wybierz Post /todoitems>Wypróbuj to.

  • 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.

    Swagger z POST

Program Swagger udostępnia okienko Odpowiedzi poniżej przycisku Wykonaj .

Swagger z odpowiedzią POST

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 Git for Windows.
  • Adres URL żądania: uproszczona reprezentacja żądania HTTP wykonanego przez kod JavaScript interfejsu użytkownika 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 zostało ustawione na 1.
  • Kod odpowiedzi: zwrócono kod stanu 201 HTTP , wskazujący, że żądanie zostało pomyślnie przetworzone i spowodowało utworzenie nowego zasobu.

Sprawdź punkty końcowe 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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz wszystkie ukończone zadania 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 GET /todoitems>Wypróbuj>Wykonaj.

  • Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI http://localhost:<port>/todoitems. Na przykład http://localhost:5001/todoitems

Wywołanie GET /todoitems generuje odpowiedź podobną 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ługiwanych wyjątków. 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. Zwracając item, zwraca odpowiedź HTTP 200.

Sprawdź punkt końcowy 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 MapPost, z tą różnicą, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca 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 zadanie, które ma Id = 1, i ustaw jego nazwę na "feed fish".

Użyj narzędzia Swagger, aby wysłać żądanie PUT:

  • Wybierz Put /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 narzędzia 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 MapGroup API

Przykładowy kod aplikacji przy każdym konfigurowaniu punktu końcowego powtarza prefiks adresu URL todoitems. 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"); do skonfigurowania grupy przy użyciu prefiksu adresu URL /todoitems.
  • Zmienia wszystkie app.Map<HttpVerb> metody na todoItems.Map<HttpVerb>.
  • Usuwa prefiks /todoitems adresu URL z Map<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.

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 i są definiowane przez TypedResults element:

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 publikowaniu

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, a jednym z głównych jest bezpieczeństwo. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

DTO może być użyty do:

  • Zapobiegaj nadmiernemu publikowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczaj grafy 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 tajne:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Pole tajne musi być schowane w tej aplikacji, ale aplikacja administracyjna może je uwidocznić.

Sprawdź, czy możesz wysłać i pobrać pole tajnego.

Utwórz plik o nazwie TodoItemDTO.cs z 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 wysyłać i pobierać wszystkie pola z wyjątkiem tajnego pola.

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

Dowiedz się więcej

Zobacz krótkie informacje o minimalnych interfejsach API

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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz ukończone zadania 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

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.

    Visual Studio Tworzenie nowego projektu

  • Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.

  • W oknie dialogowym Dodatkowe informacje:

    • Wybierz pozycję .NET 6.0
    • Usuń zaznaczenie 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();

Poprzedni kod:

  • 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:

Ten projekt jest skonfigurowany do używania protokołu SSL. Aby uniknąć ostrzeżeń SSL w przeglądarce, możesz zaufać certyfikatowi z podpisem własnym wygenerowanemu przez usługę IIS Express. Czy chcesz ufać certyfikatowi SSL usług IIS Express?

Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.

Zostanie wyświetlone następujące okno dialogowe:

Okno dialogowe ostrzeżenia o zabezpieczeniach

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

Aby zapewnić wsparcie dla bazy danych i diagnostyki używanych w tym samouczku, należy dodać pakiety NuGet.

  • 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 prawym okienku.
  • 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 powyższymi instrukcjami, aby dodać pakiet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore z najnowszą 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 TodoDb.cs z 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 UI do testowania API za pomocą Swaggera

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 NSwag.AspNetCore dla platformy .NET, który integruje narzędzia Swagger do generowania interfejsu użytkownika do testowania zgodnie ze specyfikacją 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.

Zainstaluj narzędzia Swagger

  • Uruchom następujące polecenie:

    dotnet add package NSwag.AspNetCore
    

Poprzednie polecenie dodaje pakiet NSwag.AspNetCore, który zawiera narzędzia do generowania dokumentów i interfejsu użytkownika Swagger.

Konfigurowanie middleware 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 wierszu var 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 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 dostarczania bardziej szczegółowych informacji o interfejsie API, zobacz Rozpoczynanie pracy z NSwag i ASP.NET Core

  • Po zdefiniowaniu app w wierszu var app = builder.Build();, dodaj następujący wyróżniony kod w kolejnym wierszu.

    
    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 Swagger do obsługi wygenerowanego dokumentu JSON i Swagger UI. Program Swagger jest włączony tylko w środowisku projektowym. Włączenie Swagger w środowisku produkcyjnym może ujawnić potencjalnie poufne szczegóły dotyczące 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.

    Strona testowania interfejsu API wygenerowanego programu Swagger

  • Na stronie testowania API Swagger wybierz Post /todoitems>Wypróbuj to.

  • 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.

    Swagger dla danych Post

Program Swagger udostępnia okienko Odpowiedzi poniżej przycisku Wykonaj .

Okienko programu Swagger z funkcją Post resonse

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 Git for Windows.
  • Adres URL żądania: uproszczona reprezentacja żądania HTTP wykonanego przez kod JavaScript interfejsu użytkownika 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 zostało ustawione na 1.
  • Kod odpowiedzi: zwrócono kod stanu 201 HTTP , wskazujący, że żądanie zostało pomyślnie przetworzone i spowodowało utworzenie nowego zasobu.

Sprawdź punkty końcowe 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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz wszystkie ukończone zadania 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 GET /todoitems>Wypróbuj>Wykonaj.

  • Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI http://localhost:<port>/todoitems. Na przykład http://localhost:5001/todoitems

Wywołanie GET /todoitems generuje odpowiedź podobną 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ługiwanych wyjątków. 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. Zwracając item, zwraca odpowiedź HTTP 200.

Sprawdź punkt końcowy 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 MapPost, z tą różnicą, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca 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 zadanie, które ma Id = 1, i ustaw jego nazwę na "feed fish".

Użyj narzędzia Swagger, aby wysłać żądanie PUT:

  • Wybierz Put /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 narzędzia 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 publikowaniu

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, a jednym z głównych jest bezpieczeństwo. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

DTO może być użyty do:

  • Zapobiegaj nadmiernemu publikowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczaj grafy 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 tajne:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Pole tajne musi być schowane w tej aplikacji, ale aplikacja administracyjna może je uwidocznić.

Sprawdź, czy możesz wysłać i pobrać pole tajnego.

Utwórz plik o nazwie TodoItemDTO.cs z 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 wysyłać i pobierać wszystkie pola z wyjątkiem tajnego pola.

Testowanie minimalnego interfejsu API

Przykład testowania minimalnej aplikacji API można znaleźć w tym przykładzie na GitHubie.

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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz ukończone zadania 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

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.

    Visual Studio Tworzenie nowego projektu

  • Nadaj projektowi nazwę TodoApi i wybierz pozycję Dalej.

  • W oknie dialogowym Dodatkowe informacje:

    • Wybierz pozycję .NET 8.0 (obsługa długoterminowa)
    • Usuń zaznaczenie Nie używaj instrukcji najwyższego poziomu
    • Wybierz pozycję Utwórz

    Dodatkowe informacje

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();

Poprzedni kod:

  • 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:

Ten projekt jest skonfigurowany do używania protokołu SSL. Aby uniknąć ostrzeżeń SSL w przeglądarce, możesz zaufać certyfikatowi z podpisem własnym wygenerowanemu przez usługę IIS Express. Czy chcesz ufać certyfikatowi SSL usług IIS Express?

Wybierz pozycję Tak , jeśli ufasz certyfikatowi SSL usług IIS Express.

Zostanie wyświetlone następujące okno dialogowe:

Okno dialogowe ostrzeżenia o zabezpieczeniach

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

Aby zapewnić wsparcie dla bazy danych i diagnostyki używanych w tym samouczku, należy dodać pakiety NuGet.

  • 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 TodoDb.cs z 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ą Endpoints Explorer oraz pliki .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 Wyświetl>Inne okna>Eksplorator punktów końcowych.

  • Kliknij prawym przyciskiem myszy punkt końcowy POST i wybierz polecenie Generuj żądanie.

    Menu kontekstowe Eksploratora punktów końcowych z wyróżnionym elementem menu Generowanie żądania.

    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 „(c0/)” stanowi ogranicznik żądania: to, co pojawia się po nim, dotyczy 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.

    Okno pliku .http z wyróżnionym linkiem uruchamiania.

    Żądanie POST jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedź .

    Okno pliku .http z odpowiedzią z żądania POST.

Sprawdź punkty końcowe 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 Pobierz wszystkie zadania do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobierz wszystkie ukończone zadania 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 końcowe punkty dostępu z przeglądarki lub korzystając z 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ąca TodoApi.http zawartość:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Zamień {id} na 1.

  • 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ługiwanych wyjątków. 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. Zwracając item, zwraca odpowiedź HTTP 200.

Sprawdź punkt końcowy 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 MapPost, z tą różnicą, że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca 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 zadanie, które ma Id = 1, 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ągiem 1.

  • 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.

  • Zamień znacznik {id} w wierszu żądania DELETE na 1. Żą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 MapGroup API

Przykładowy kod aplikacji przy każdym konfigurowaniu punktu końcowego powtarza prefiks adresu URL todoitems. 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"); do skonfigurowania grupy przy użyciu prefiksu adresu URL /todoitems.
  • Zmienia wszystkie app.Map<HttpVerb> metody na todoItems.Map<HttpVerb>.
  • Usuwa prefiks /todoitems adresu URL z Map<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.

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 i są definiowane przez TypedResults element:

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 publikowaniu

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, a jednym z głównych jest bezpieczeństwo. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

DTO może być użyty do:

  • Zapobiegaj nadmiernemu publikowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczaj grafy 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 tajne:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Pole tajne musi być schowane w tej aplikacji, ale aplikacja administracyjna może je uwidocznić.

Sprawdź, czy możesz wysłać i pobrać pole tajnego.

Utwórz plik o nazwie TodoItemDTO.cs z 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 wysyłać i pobierać wszystkie pola z wyjątkiem tajnego pola.

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

Dowiedz się więcej

Zobacz krótkie informacje o minimalnych interfejsach API