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

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Autor : Rick Anderson i Tom Dykstra

Minimalne interfejsy API są tworzone w celu tworzenia interfejsów API HTTP z minimalnymi zależnościami. Są one idealne dla mikrousług i aplikacji, które chcą uwzględniać tylko minimalne pliki, funkcje i zależności w ASP.NET Core.

W tym samouczku przedstawiono podstawy tworzenia minimalnego interfejsu API przy użyciu platformy ASP.NET Core. Innym podejściem do tworzenia interfejsów API w programie ASP.NET Core jest użycie kontrolerów. Aby uzyskać pomoc dotyczącą wybierania między minimalnymi interfejsami API i interfejsami API opartymi na kontrolerach, zobacz Omówienie interfejsów API. Aby zapoznać się z samouczkiem dotyczącym tworzenia projektu interfejsu API na podstawie kontrolerów zawierających więcej funkcji, zobacz Tworzenie internetowego interfejsu API.

Omówienie

Ten samouczek tworzy następujący interfejs API:

Interfejs API opis Treść żądania Treść odpowiedzi
GET /todoitems Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
POST /todoitems Dodawanie nowego elementu Element do wykonania Element do wykonania
PUT /todoitems/{id} Aktualizowanie istniejącego elementu Element do wykonania Brak
DELETE /todoitems/{id}     Usuwanie elementu Brak Brak

Wymagania wstępne

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

Powyższy kod ma następujące działanie:

  • Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
  • Tworzy punkt końcowy / HTTP GET, który zwraca wartość Hello World!:

Uruchom aplikację

Naciśnij Ctrl+F5, aby uruchomić bez debugera.

Program Visual Studio wyświetla następujące okno dialogowe:

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

Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.

  • W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
  • Wybierz kartę Przeglądaj.
  • Wybierz pozycję Uwzględnij wersję P.
  • Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję Microsoft.EntityFrameworkCore.InMemory.
  • Zaznacz pole wyboru Projekt w okienku po prawej stronie, a następnie wybierz pozycję Zainstaluj.
  • Postępuj zgodnie z poprzednimi instrukcjami, aby dodać Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pakiet.

Klasy kontekstu modelu i bazy danych

  • W folderze projektu utwórz plik o nazwie Todo.cs z następującym kodem:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.

  • Utwórz plik o nazwie z TodoDb.cs następującym kodem:
using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .

Dodawanie kodu interfejsu API

  • Zastąp zawartość pliku Program.cs następującym kodem:
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.

W tym samouczku używane są pliki Endpoints Explorer i .http do testowania interfejsu API.

Testowanie publikowania danych

Poniższy kod w pliku Program.cs tworzy punkt końcowy /todoitems HTTP POST, który dodaje dane do bazy danych w pamięci:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego / .

Punkt końcowy POST będzie używany do dodawania danych do aplikacji.

  • Wybierz pozycję Wyświetl>inne eksploratora punktów końcowych systemu Windows.>

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

    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 (###) wiersz jest ogranicznikiem żądania: co następuje po nim dla innego żądania.
  • Żądanie POST wymaga nagłówków i treści. Aby zdefiniować te części żądania, dodaj następujące wiersze bezpośrednio po wierszu żądania POST:

    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    

    Powyższy kod dodaje nagłówek Content-Type i treść żądania JSON. Plik TodoApi.http powinien teraz wyglądać podobnie do poniższego przykładu, ale z numerem portu:

    @TodoApi_HostAddress = https://localhost:7057
    
    Post {{TodoApi_HostAddress}}/todoitems
    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    
    ###
    
  • Uruchom aplikację.

  • Wybierz link Wyślij żądanie powyżej POST wiersza żądania.

    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.

Sprawdzanie punktów końcowych GET

Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet:

Interfejs API opis Treść żądania Treść odpowiedzi
GET /todoitems Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie wszystkich ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Testowanie punktów końcowych GET

Przetestuj GET aplikację, wywołując punkty końcowe z przeglądarki lub przy użyciu Eksploratora punktów końcowych. Poniższe kroki dotyczą Eksploratora punktów końcowych.

  • W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy pierwszy punkt końcowy GET i wybierz polecenie Generuj żądanie.

    Do pliku zostanie dodana następująca TodoApi.http zawartość:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Wybierz link Wyślij żądanie powyżej nowego GET wiersza żądania.

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

  • Treść odpowiedzi jest podobna do następującego kodu JSON:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • W Eksploratorze punktów końcowych kliknij prawym przyciskiem /todoitems/{id} myszy punkt końcowy GET i wybierz pozycję Generuj żądanie. Do pliku zostanie dodana następują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ługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.

Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id} może zwrócić dwie różne wartości stanu:

  • Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
  • W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie item wyników w odpowiedzi HTTP 200.

Badanie punktu końcowego PUT

Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Ta metoda jest podobna do metody , z tą różnicą MapPost , że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.

Testowanie punktu końcowego PUT

W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.

Zaktualizuj element to-do, który ma Id = 1 wartość , i ustaw jego nazwę na "feed fish".

  • W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy punkt końcowy PUT i wybierz pozycję Generuj żądanie.

    Do pliku zostanie dodana następująca TodoApi.http zawartość:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • W wierszu żądania PUT zastąp ciąg {id} cią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.httpelementu .

  • Zastąp element {id} w wierszu żądania DELETE ciągiem 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 interfejsu API grupy map

Przykładowy kod aplikacji powtarza prefiks adresu URL za każdym razem, gdy konfiguruje todoitems punkt końcowy. Interfejsy API często mają grupy punktów końcowych z typowym prefiksem adresu URL, a MapGroup metoda jest dostępna w celu ułatwienia organizowania takich grup. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata.

Zastąp zawartość Program.cs następującym kodem:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>
    await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Powyższy kod ma następujące zmiany:

  • Dodaje var todoItems = app.MapGroup("/todoitems"); element do skonfigurowania grupy przy użyciu prefiksu /todoitemsadresu URL .
  • 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 (TypdResults a wyniki).

Metody Map<HttpVerb> mogą wywoływać metody obsługi tras zamiast używać lambd. Aby zobaczyć przykład, zaktualizuj Program.cs przy użyciu następującego kodu:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Kod Map<HttpVerb> wywołuje teraz metody zamiast lambd:

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Te metody zwracają obiekty, które implementują IResult obiekty i są definiowane przez TypedResultselement :

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Testy jednostkowe mogą wywoływać te metody i testować, czy zwracają prawidłowy typ. Jeśli na przykład metoda to GetAllTodos:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

Kod testu jednostkowego może sprawdzić, czy obiekt typu Ok<Todo[]> jest zwracany z metody obsługi. Na przykład:

public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check for the correct returned type
    Assert.IsType<Ok<Todo[]>>(result);
}

Zapobieganie nadmiernemu delegowaniu

Obecnie przykładowa aplikacja uwidacznia cały Todo obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

Cel DTO może służyć do:

  • Zapobiegaj nadmiernemu delegowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.

Aby zademonstrować podejście DTO, zaktualizuj klasę Todo tak, aby zawierała pole wpisu tajnego:

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

Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.

Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.

Utwórz plik o nazwie z TodoItemDTO.cs następującym kodem:

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

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Zastąp zawartość Program.cs pliku następującym kodem, aby użyć tego modelu DTO:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db) {
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(new TodoItemDTO(todo))
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    todoItemDTO = new TodoItemDTO(todoItem);

    return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}

static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.

Rozwiązywanie problemów z ukończonym przykładem

Jeśli napotkasz problem, nie możesz go rozwiązać, porównaj kod z ukończonym projektem. Wyświetl lub pobierz ukończony projekt (jak pobrać).

Następne kroki

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 Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
POST /todoitems Dodawanie nowego elementu Element do wykonania Element do wykonania
PUT /todoitems/{id} Aktualizowanie istniejącego elementu Element do wykonania Brak
DELETE /todoitems/{id}     Usuwanie elementu Brak Brak

Wymagania wstępne

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

Powyższy kod ma następujące działanie:

  • Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
  • Tworzy punkt końcowy / HTTP GET, który zwraca wartość Hello World!:

Uruchom aplikację

Naciśnij Ctrl+F5, aby uruchomić bez debugera.

Program Visual Studio wyświetla następujące okno dialogowe:

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

Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.

  • W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
  • Wybierz kartę Przeglądaj.
  • Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję Microsoft.EntityFrameworkCore.InMemory.
  • Zaznacz pole wyboru Project (Projekt) w okienku po prawej stronie.
  • Z listy rozwijanej Wersja wybierz najnowszą dostępną wersję 7, na przykład 7.0.17, a następnie wybierz pozycję Zainstaluj.
  • Postępuj zgodnie z poprzednimi instrukcjami, aby dodać Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pakiet z najnowszą dostępną wersją 7.

Klasy kontekstu modelu i bazy danych

W folderze projektu utwórz plik o nazwie Todo.cs z następującym kodem:

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

Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.

Utwórz plik o nazwie z TodoDb.cs następującym kodem:

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .

Dodawanie kodu interfejsu API

Zastąp zawartość pliku Program.cs następującym kodem:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.

Tworzenie interfejsu użytkownika testowania interfejsu API za pomocą struktury Swagger

Dostępnych jest wiele dostępnych internetowych narzędzi do testowania interfejsu API, które można wybrać, i możesz wykonać kroki wprowadzające do testowania interfejsu API przy użyciu własnego preferowanego narzędzia.

W tym samouczku wykorzystano pakiet .NET NSwag.AspNetCore, który integruje narzędzia Swagger do generowania interfejsu użytkownika testowania zgodnie ze specyfikacją interfejsu OpenAPI:

  • NSwag: biblioteka platformy .NET, która integruje program Swagger bezpośrednio z aplikacjami ASP.NET Core, zapewniając oprogramowanie pośredniczące i konfigurację.
  • Swagger: zestaw narzędzi typu open source, takich jak OpenAPIGenerator i SwaggerUI, które generują strony testowania interfejsu API zgodne ze specyfikacją interfejsu OpenAPI.
  • Specyfikacja interfejsu OpenAPI: dokument opisujący możliwości interfejsu API na podstawie adnotacji XML i atrybutów w kontrolerach i modelach.

Aby uzyskać więcej informacji na temat korzystania z interfejsu OpenAPI i NSwag z ASP.NET, zobacz dokumentację internetowego interfejsu API platformy ASP.NET Core w programie Swagger/OpenAPI.

Instalowanie narzędzi struktury Swagger

  • Uruchom następujące polecenie:

    dotnet add package NSwag.AspNetCore
    

Poprzednie polecenie dodaje pakiet NSwag.AspNetCore zawierający narzędzia do generowania dokumentów i interfejsu użytkownika struktury Swagger.

Konfigurowanie oprogramowania pośredniczącego programu Swagger

  • Dodaj następujący wyróżniony kod przed app zdefiniowaną w 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 struktury Swagger do usług aplikacji i konfiguruje go w celu udostępnienia dodatkowych informacji o interfejsie API, takich jak jego tytuł i wersja. Aby uzyskać więcej informacji na temat zapewniania bardziej niezawodnych szczegółów interfejsu API, zobacz Rozpoczynanie pracy z siecią NSwag i ASP.NET Core

  • Dodaj następujący wyróżniony kod do następnego wiersza po app zdefiniowaniu w wierszu var app = builder.Build();

    var app = builder.Build();
    if (app.Environment.IsDevelopment())
    {
        app.UseOpenApi();
        app.UseSwaggerUi(config =>
        {
            config.DocumentTitle = "TodoAPI";
            config.Path = "/swagger";
            config.DocumentPath = "/swagger/{documentName}/swagger.json";
            config.DocExpansion = "list";
        });
    }
    

    Poprzedni kod umożliwia oprogramowanie pośredniczące struktury Swagger do obsługi wygenerowanego dokumentu JSON i interfejsu użytkownika programu Swagger. Program Swagger jest włączony tylko w środowisku projektowym. Włączenie struktury Swagger w środowisku produkcyjnym może spowodować ujawnienie potencjalnie poufnych szczegółów dotyczących struktury i implementacji interfejsu API.

Testowanie publikowania danych

Poniższy kod w pliku Program.cs tworzy punkt końcowy /todoitems HTTP POST, który dodaje dane do bazy danych w pamięci:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego / .

Punkt końcowy POST będzie używany do dodawania danych do aplikacji.

  • Gdy aplikacja jest nadal uruchomiona, w przeglądarce przejdź do https://localhost:<port>/swagger strony testowania interfejsu API wygenerowanej przez program Swagger.

    Strona testowania interfejsu API wygenerowanego programu Swagger

  • Na stronie Testowanie interfejsu API programu Swagger wybierz pozycję Opublikuj /todoitems>Wypróbuj.

  • Zwróć uwagę, że pole Treść żądania zawiera wygenerowany przykładowy format odzwierciedlający parametry interfejsu API.

  • W treści żądania wprowadź kod JSON dla elementu do wykonania bez określania opcjonalnego elementu id:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Wybierz polecenie Wykonaj.

    Swagger z postem

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

Struktura 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 systemu Windows.
  • Adres URL żądania: uproszczona reprezentacja żądania HTTP wykonanego przez kod JavaScript interfejsu użytkownika struktury Swagger dla wywołania interfejsu API. Rzeczywiste żądania mogą zawierać szczegóły, takie jak nagłówki i parametry zapytania oraz treść żądania.
  • Odpowiedź serwera: zawiera treść odpowiedzi i nagłówki. Treść odpowiedzi pokazuje, że id ustawiono wartość 1.
  • Kod odpowiedzi: zwrócono kod stanu 201 HTTP , wskazujący, że żądanie zostało pomyślnie przetworzone i spowodowało utworzenie nowego zasobu.

Sprawdzanie punktów końcowych GET

Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet:

Interfejs API opis Treść żądania Treść odpowiedzi
GET /todoitems Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie wszystkich ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Testowanie punktów końcowych GET

Przetestuj aplikację, wywołując punkty końcowe z przeglądarki lub programu Swagger.

  • W programie Swagger wybierz pozycję GET /todoitems>Wypróbuj wykonanie.>

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

Wywołanie metody w celu GET /todoitems utworzenia odpowiedzi podobnej do następującej:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Wywołaj metodę GET /todoitems/{id} w programie Swagger, aby zwrócić dane z określonego identyfikatora:

    • Wybierz pozycję GET /todoitems>Wypróbuj.
    • Ustaw pole id na 1 i wybierz pozycję Wykonaj.
  • Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI https://localhost:<port>/todoitems/1. Na przykład https://localhost:5001/todoitems/1

  • Odpowiedź jest podobna do następującej:

    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

Ta aplikacja używa bazy danych w pamięci. Jeśli aplikacja zostanie ponownie uruchomiona, żądanie GET nie zwraca żadnych danych. Jeśli żadne dane nie są zwracane, prześlij dane POST do aplikacji i spróbuj ponownie wysłać żądanie GET.

Wartości zwracane

ASP.NET Core automatycznie serializuje obiekt w formacie JSON i zapisuje kod JSON w treści komunikatu odpowiedzi. Kod odpowiedzi dla tego typu zwracanego to 200 OK, zakładając, że nie ma żadnych nieobsługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.

Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id} może zwrócić dwie różne wartości stanu:

  • Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
  • W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie item wyników w odpowiedzi HTTP 200.

Badanie punktu końcowego PUT

Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Ta metoda jest podobna do metody , z tą różnicą MapPost , że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.

Testowanie punktu końcowego PUT

W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.

Zaktualizuj element to-do, który ma Id = 1 wartość , i ustaw jego nazwę na "feed fish".

Użyj struktury Swagger, aby wysłać żądanie PUT:

  • Wybierz pozycję Umieść /todoitems/{id}>Wypróbuj.

  • Ustaw pole id na 1.

  • Ustaw treść żądania na następujący kod JSON:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Wybierz polecenie Wykonaj.

Sprawdzanie i testowanie punktu końcowego DELETE

Przykładowa aplikacja implementuje pojedynczy punkt końcowy DELETE przy użyciu polecenia MapDelete:

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

Użyj struktury Swagger, aby wysłać żądanie DELETE:

  • Wybierz pozycję USUŃ /todoitems/{id}>Wypróbuj.

  • Ustaw pole ID na 1 i wybierz pozycję Wykonaj.

    Żądanie DELETE jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedzi . Treść odpowiedzi jest pusta, a kod stanu odpowiedzi serwera to 204.

Korzystanie z interfejsu API grupy map

Przykładowy kod aplikacji powtarza prefiks adresu URL za każdym razem, gdy konfiguruje todoitems punkt końcowy. Interfejsy API często mają grupy punktów końcowych z typowym prefiksem adresu URL, a MapGroup metoda jest dostępna w celu ułatwienia organizowania takich grup. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata.

Zastąp zawartość Program.cs następującym kodem:

using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
    config.DocumentName = "TodoAPI";
    config.Title = "TodoAPI v1";
    config.Version = "v1";
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi(config =>
    {
        config.DocumentTitle = "TodoAPI";
        config.Path = "/swagger";
        config.DocumentPath = "/swagger/{documentName}/swagger.json";
        config.DocExpansion = "list";
    });
}

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>
    await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Powyższy kod ma następujące zmiany:

  • Dodaje var todoItems = app.MapGroup("/todoitems"); element do skonfigurowania grupy przy użyciu prefiksu /todoitemsadresu URL .
  • 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 (TypdResults a wyniki).

Metody Map<HttpVerb> mogą wywoływać metody obsługi tras zamiast używać lambd. Aby zobaczyć przykład, zaktualizuj Program.cs przy użyciu następującego kodu:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Kod Map<HttpVerb> wywołuje teraz metody zamiast lambd:

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Te metody zwracają obiekty, które implementują IResult obiekty i są definiowane przez TypedResultselement :

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Testy jednostkowe mogą wywoływać te metody i testować, czy zwracają prawidłowy typ. Jeśli na przykład metoda to GetAllTodos:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

Kod testu jednostkowego może sprawdzić, czy obiekt typu Ok<Todo[]> jest zwracany z metody obsługi. Na przykład:

public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check for the correct returned type
    Assert.IsType<Ok<Todo[]>>(result);
}

Zapobieganie nadmiernemu delegowaniu

Obecnie przykładowa aplikacja uwidacznia cały Todo obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

Cel DTO może służyć do:

  • Zapobiegaj nadmiernemu delegowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.

Aby zademonstrować podejście DTO, zaktualizuj klasę Todo tak, aby zawierała pole wpisu tajnego:

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

Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.

Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.

Utwórz plik o nazwie z TodoItemDTO.cs następującym kodem:

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

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Zastąp zawartość Program.cs pliku następującym kodem, aby użyć tego modelu DTO:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

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

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

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}


class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.

Rozwiązywanie problemów z ukończonym przykładem

Jeśli napotkasz problem, nie możesz go rozwiązać, porównaj kod z ukończonym projektem. Wyświetl lub pobierz ukończony projekt (jak pobrać).

Następne kroki

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 Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
POST /todoitems Dodawanie nowego elementu Element do wykonania Element do wykonania
PUT /todoitems/{id} Aktualizowanie istniejącego elementu Element do wykonania Brak
DELETE /todoitems/{id}     Usuwanie elementu Brak Brak

Wymagania wstępne

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 pola Wyboru Nie używaj instrukcji najwyższego poziomu
    • Wybierz pozycję Utwórz

Analizowanie kodu

Plik Program.cs zawiera następujący kod:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Powyższy kod ma następujące działanie:

  • Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
  • Tworzy punkt końcowy / HTTP GET, który zwraca wartość Hello World!:

Uruchom aplikację

Naciśnij Ctrl+F5, aby uruchomić bez debugera.

Program Visual Studio wyświetla następujące okno dialogowe:

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

Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.

  • W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
  • Wybierz kartę Przeglądaj.
  • Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję Microsoft.EntityFrameworkCore.InMemory.
  • Zaznacz pole wyboru Project (Projekt) w okienku po prawej stronie.
  • Z listy rozwijanej Wersja wybierz najnowszą dostępną wersję 7, na przykład 6.0.28, a następnie wybierz pozycję Zainstaluj.
  • Postępuj zgodnie z poprzednimi instrukcjami, aby dodać Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pakiet z najnowszą dostępną wersją 7.

Klasy kontekstu modelu i bazy danych

W folderze projektu utwórz plik o nazwie Todo.cs z następującym kodem:

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

Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.

Utwórz plik o nazwie z TodoDb.cs następującym kodem:

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .

Dodawanie kodu interfejsu API

Zastąp zawartość pliku Program.cs następującym kodem:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.

Tworzenie interfejsu użytkownika testowania interfejsu API za pomocą struktury Swagger

Dostępnych jest wiele dostępnych internetowych narzędzi do testowania interfejsu API, które można wybrać, i możesz wykonać kroki wprowadzające do testowania interfejsu API przy użyciu własnego preferowanego narzędzia.

W tym samouczku wykorzystano pakiet .NET NSwag.AspNetCore, który integruje narzędzia Swagger do generowania interfejsu użytkownika testowania zgodnie ze specyfikacją interfejsu OpenAPI:

  • NSwag: biblioteka platformy .NET, która integruje program Swagger bezpośrednio z aplikacjami ASP.NET Core, zapewniając oprogramowanie pośredniczące i konfigurację.
  • Swagger: zestaw narzędzi typu open source, takich jak OpenAPIGenerator i SwaggerUI, które generują strony testowania interfejsu API zgodne ze specyfikacją interfejsu OpenAPI.
  • Specyfikacja interfejsu OpenAPI: dokument opisujący możliwości interfejsu API na podstawie adnotacji XML i atrybutów w kontrolerach i modelach.

Aby uzyskać więcej informacji na temat korzystania z interfejsu OpenAPI i NSwag z ASP.NET, zobacz dokumentację internetowego interfejsu API platformy ASP.NET Core w programie Swagger/OpenAPI.

Instalowanie narzędzi struktury Swagger

  • Uruchom następujące polecenie:

    dotnet add package NSwag.AspNetCore
    

Poprzednie polecenie dodaje pakiet NSwag.AspNetCore zawierający narzędzia do generowania dokumentów i interfejsu użytkownika struktury Swagger.

Konfigurowanie oprogramowania pośredniczącego programu Swagger

  • W Program.cs dodaj następujące using instrukcje u góry:

    using NSwag.AspNetCore;
    
  • Dodaj następujący wyróżniony kod przed app zdefiniowaną w 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 struktury Swagger do usług aplikacji i konfiguruje go w celu udostępnienia dodatkowych informacji o interfejsie API, takich jak jego tytuł i wersja. Aby uzyskać więcej informacji na temat zapewniania bardziej niezawodnych szczegółów interfejsu API, zobacz Rozpoczynanie pracy z siecią NSwag i ASP.NET Core

  • Dodaj następujący wyróżniony kod do następnego wiersza po app zdefiniowaniu w wierszu var app = builder.Build();

    
    var app = builder.Build();
    
    if (app.Environment.IsDevelopment())
    {
        app.UseOpenApi();
        app.UseSwaggerUi(config =>
        {
            config.DocumentTitle = "TodoAPI";
            config.Path = "/swagger";
            config.DocumentPath = "/swagger/{documentName}/swagger.json";
            config.DocExpansion = "list";
        });
    }
    
    

    Poprzedni kod umożliwia oprogramowanie pośredniczące struktury Swagger do obsługi wygenerowanego dokumentu JSON i interfejsu użytkownika programu Swagger. Program Swagger jest włączony tylko w środowisku projektowym. Włączenie struktury Swagger w środowisku produkcyjnym może spowodować ujawnienie potencjalnie poufnych szczegółów dotyczących struktury i implementacji interfejsu API.

Testowanie publikowania danych

Poniższy kod w pliku Program.cs tworzy punkt końcowy /todoitems HTTP POST, który dodaje dane do bazy danych w pamięci:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego / .

Punkt końcowy POST będzie używany do dodawania danych do aplikacji.

  • Gdy aplikacja jest nadal uruchomiona, w przeglądarce przejdź do https://localhost:<port>/swagger strony testowania interfejsu API wygenerowanej przez program Swagger.

    Strona testowania interfejsu API wygenerowanego programu Swagger

  • Na stronie Testowanie interfejsu API programu Swagger wybierz pozycję Opublikuj /todoitems>Wypróbuj.

  • Zwróć uwagę, że pole Treść żądania zawiera wygenerowany przykładowy format odzwierciedlający parametry interfejsu API.

  • W treści żądania wprowadź kod JSON dla elementu do wykonania bez określania opcjonalnego elementu id:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Wybierz polecenie Wykonaj.

    Struktura Swagger z danymi 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 systemu Windows.
  • Adres URL żądania: uproszczona reprezentacja żądania HTTP wykonanego przez kod JavaScript interfejsu użytkownika struktury Swagger dla wywołania interfejsu API. Rzeczywiste żądania mogą zawierać szczegóły, takie jak nagłówki i parametry zapytania oraz treść żądania.
  • Odpowiedź serwera: zawiera treść odpowiedzi i nagłówki. Treść odpowiedzi pokazuje, że id ustawiono wartość 1.
  • Kod odpowiedzi: zwrócono kod stanu 201 HTTP , wskazujący, że żądanie zostało pomyślnie przetworzone i spowodowało utworzenie nowego zasobu.

Sprawdzanie punktów końcowych GET

Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet:

Interfejs API opis Treść żądania Treść odpowiedzi
GET /todoitems Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie wszystkich ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
app.MapGet("/", () => "Hello World!");

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Testowanie punktów końcowych GET

Przetestuj aplikację, wywołując punkty końcowe z przeglądarki lub programu Swagger.

  • W programie Swagger wybierz pozycję GET /todoitems>Wypróbuj wykonanie.>

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

Wywołanie metody w celu GET /todoitems utworzenia odpowiedzi podobnej do następującej:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Wywołaj metodę GET /todoitems/{id} w programie Swagger, aby zwrócić dane z określonego identyfikatora:

    • Wybierz pozycję GET /todoitems>Wypróbuj.
    • Ustaw pole id na 1 i wybierz pozycję Wykonaj.
  • Alternatywnie wywołaj metodę GET /todoitems z przeglądarki, wprowadzając identyfikator URI https://localhost:<port>/todoitems/1. Na przykład: https://localhost:5001/todoitems/1

  • Odpowiedź jest podobna do następującej:

    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

Ta aplikacja używa bazy danych w pamięci. Jeśli aplikacja zostanie ponownie uruchomiona, żądanie GET nie zwraca żadnych danych. Jeśli żadne dane nie są zwracane, prześlij dane POST do aplikacji i spróbuj ponownie wysłać żądanie GET.

Wartości zwracane

ASP.NET Core automatycznie serializuje obiekt w formacie JSON i zapisuje kod JSON w treści komunikatu odpowiedzi. Kod odpowiedzi dla tego typu zwracanego to 200 OK, zakładając, że nie ma żadnych nieobsługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.

Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id} może zwrócić dwie różne wartości stanu:

  • Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
  • W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie item wyników w odpowiedzi HTTP 200.

Badanie punktu końcowego PUT

Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Ta metoda jest podobna do metody , z tą różnicą MapPost , że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.

Testowanie punktu końcowego PUT

W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.

Zaktualizuj element to-do, który ma Id = 1 wartość , i ustaw jego nazwę na "feed fish".

Użyj struktury Swagger, aby wysłać żądanie PUT:

  • Wybierz pozycję Umieść /todoitems/{id}>Wypróbuj.

  • Ustaw pole id na 1.

  • Ustaw treść żądania na następujący kod JSON:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Wybierz polecenie Wykonaj.

Sprawdzanie i testowanie punktu końcowego DELETE

Przykładowa aplikacja implementuje pojedynczy punkt końcowy DELETE przy użyciu polecenia MapDelete:

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

Użyj struktury Swagger, aby wysłać żądanie DELETE:

  • Wybierz pozycję USUŃ /todoitems/{id}>Wypróbuj.

  • Ustaw pole ID na 1 i wybierz pozycję Wykonaj.

    Żądanie DELETE jest wysyłane do aplikacji, a odpowiedź jest wyświetlana w okienku Odpowiedzi . Treść odpowiedzi jest pusta, a kod stanu odpowiedzi serwera to 204.

Zapobieganie nadmiernemu delegowaniu

Obecnie przykładowa aplikacja uwidacznia cały Todo obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

Cel DTO może służyć do:

  • Zapobiegaj nadmiernemu delegowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.

Aby zademonstrować podejście DTO, zaktualizuj klasę Todo tak, aby zawierała pole wpisu tajnego:

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

Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.

Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.

Utwórz plik o nazwie z TodoItemDTO.cs następującym kodem:

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

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Zastąp zawartość Program.cs pliku następującym kodem, aby użyć tego modelu DTO:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

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

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

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}


class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.

Testowanie minimalnego interfejsu API

Przykład testowania minimalnej aplikacji interfejsu API można znaleźć w tym przykładzie usługi GitHub.

Publikowanie na platformie Azure

Aby uzyskać informacje na temat wdrażania na platformie Azure, zobacz Szybki start: wdrażanie aplikacji internetowej ASP.NET.

Dodatkowe zasoby

Minimalne interfejsy API są tworzone w celu tworzenia interfejsów API HTTP z minimalnymi zależnościami. Są one idealne dla mikrousług i aplikacji, które chcą uwzględniać tylko minimalne pliki, funkcje i zależności w ASP.NET Core.

W tym samouczku przedstawiono podstawy tworzenia minimalnego interfejsu API przy użyciu platformy ASP.NET Core. Innym podejściem do tworzenia interfejsów API w programie ASP.NET Core jest użycie kontrolerów. Aby uzyskać pomoc dotyczącą wybierania między minimalnymi interfejsami API i interfejsami API opartymi na kontrolerach, zobacz Omówienie interfejsów API. Aby zapoznać się z samouczkiem dotyczącym tworzenia projektu interfejsu API na podstawie kontrolerów zawierających więcej funkcji, zobacz Tworzenie internetowego interfejsu API.

Omówienie

Ten samouczek tworzy następujący interfejs API:

Interfejs API opis Treść żądania Treść odpowiedzi
GET /todoitems Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
POST /todoitems Dodawanie nowego elementu Element do wykonania Element do wykonania
PUT /todoitems/{id} Aktualizowanie istniejącego elementu Element do wykonania Brak
DELETE /todoitems/{id}     Usuwanie elementu Brak Brak

Wymagania wstępne

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

Powyższy kod ma następujące działanie:

  • Tworzy obiekt WebApplicationBuilder i WebApplication ze wstępnie skonfigurowanymi wartościami domyślnymi.
  • Tworzy punkt końcowy / HTTP GET, który zwraca wartość Hello World!:

Uruchom aplikację

Naciśnij Ctrl+F5, aby uruchomić bez debugera.

Program Visual Studio wyświetla następujące okno dialogowe:

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

Pakiety NuGet należy dodać do obsługi bazy danych i diagnostyki używanej w tym samouczku.

  • W menu Narzędzia wybierz pozycję NuGet Menedżer pakietów > Zarządzaj pakietami NuGet dla rozwiązania.
  • Wybierz kartę Przeglądaj.
  • Wprowadź ciąg Microsoft.EntityFrameworkCore.InMemory w polu wyszukiwania, a następnie wybierz pozycję Microsoft.EntityFrameworkCore.InMemory.
  • Zaznacz pole wyboru Projekt w okienku po prawej stronie, a następnie wybierz pozycję Zainstaluj.
  • Postępuj zgodnie z poprzednimi instrukcjami, aby dodać Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pakiet.

Klasy kontekstu modelu i bazy danych

  • W folderze projektu utwórz plik o nazwie Todo.cs z następującym kodem:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Powyższy kod tworzy model dla tej aplikacji. Model to klasa reprezentująca dane, którymi zarządza aplikacja.

  • Utwórz plik o nazwie z TodoDb.cs następującym kodem:
using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Powyższy kod definiuje kontekst bazy danych, który jest główną klasą, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa pochodzi z Microsoft.EntityFrameworkCore.DbContext klasy .

Dodawanie kodu interfejsu API

  • Zastąp zawartość pliku Program.cs następującym kodem:
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Poniższy wyróżniony kod dodaje kontekst bazy danych do kontenera wstrzykiwania zależności (DI) i umożliwia wyświetlanie wyjątków związanych z bazą danych:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontener DI zapewnia dostęp do kontekstu bazy danych i innych usług.

W tym samouczku używane są pliki Endpoints Explorer i .http do testowania interfejsu API.

Testowanie publikowania danych

Poniższy kod w pliku Program.cs tworzy punkt końcowy /todoitems HTTP POST, który dodaje dane do bazy danych w pamięci:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Uruchom aplikację. Przeglądarka wyświetla błąd 404, ponieważ nie ma już punktu końcowego / .

Punkt końcowy POST będzie używany do dodawania danych do aplikacji.

  • Wybierz pozycję Wyświetl>inne eksploratora punktów końcowych systemu Windows.>

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

    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 (###) wiersz jest ogranicznikiem żądania: co następuje po nim dla innego żądania.
  • Żądanie POST wymaga nagłówków i treści. Aby zdefiniować te części żądania, dodaj następujące wiersze bezpośrednio po wierszu żądania POST:

    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    

    Powyższy kod dodaje nagłówek Content-Type i treść żądania JSON. Plik TodoApi.http powinien teraz wyglądać podobnie do poniższego przykładu, ale z numerem portu:

    @TodoApi_HostAddress = https://localhost:7057
    
    Post {{TodoApi_HostAddress}}/todoitems
    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    
    ###
    
  • Uruchom aplikację.

  • Wybierz link Wyślij żądanie powyżej POST wiersza żądania.

    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.

Sprawdzanie punktów końcowych GET

Przykładowa aplikacja implementuje kilka punktów końcowych GET, wywołując metodę MapGet:

Interfejs API opis Treść żądania Treść odpowiedzi
GET /todoitems Pobieranie wszystkich elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/complete Pobieranie wszystkich ukończonych elementów do wykonania Brak Tablica elementów do wykonania
GET /todoitems/{id} Pobieranie elementu według identyfikatora Brak Element do wykonania
app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Testowanie punktów końcowych GET

Przetestuj GET aplikację, wywołując punkty końcowe z przeglądarki lub przy użyciu Eksploratora punktów końcowych. Poniższe kroki dotyczą Eksploratora punktów końcowych.

  • W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy pierwszy punkt końcowy GET i wybierz polecenie Generuj żądanie.

    Do pliku zostanie dodana następująca TodoApi.http zawartość:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Wybierz link Wyślij żądanie powyżej nowego GET wiersza żądania.

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

  • Treść odpowiedzi jest podobna do następującego kodu JSON:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • W Eksploratorze punktów końcowych kliknij prawym przyciskiem /todoitems/{id} myszy punkt końcowy GET i wybierz pozycję Generuj żądanie. Do pliku zostanie dodana następują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ługiwane wyjątki. Nieobsługiwane wyjątki są tłumaczone na błędy 5xx.

Typy zwracane mogą reprezentować szeroki zakres kodów stanu HTTP. Na przykład GET /todoitems/{id} może zwrócić dwie różne wartości stanu:

  • Jeśli żaden element nie pasuje do żądanego identyfikatora, metoda zwraca kod błędu stanuNotFound 404.
  • W przeciwnym razie metoda zwraca wartość 200 z treścią odpowiedzi JSON. Zwracanie item wyników w odpowiedzi HTTP 200.

Badanie punktu końcowego PUT

Przykładowa aplikacja implementuje pojedynczy punkt końcowy PUT przy użyciu polecenia MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Ta metoda jest podobna do metody , z tą różnicą MapPost , że używa protokołu HTTP PUT. Pomyślna odpowiedź zwraca wartość 204 (brak zawartości). Zgodnie ze specyfikacją PROTOKOŁU HTTP żądanie PUT wymaga od klienta wysłania całej zaktualizowanej jednostki, a nie tylko zmian. Aby obsługiwać aktualizacje częściowe, użyj poprawki HTTP PATCH.

Testowanie punktu końcowego PUT

W tym przykładzie użyto bazy danych w pamięci, która musi zostać zainicjowana przy każdym uruchomieniu aplikacji. Przed wykonaniem wywołania PUT musi istnieć element w bazie danych. Wywołaj metodę GET, aby upewnić się, że istnieje element w bazie danych przed wykonaniem wywołania PUT.

Zaktualizuj element to-do, który ma Id = 1 wartość , i ustaw jego nazwę na "feed fish".

  • W Eksploratorze punktów końcowych kliknij prawym przyciskiem myszy punkt końcowy PUT i wybierz pozycję Generuj żądanie.

    Do pliku zostanie dodana następująca TodoApi.http zawartość:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • W wierszu żądania PUT zastąp ciąg {id} cią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.httpelementu .

  • Zastąp element {id} w wierszu żądania DELETE ciągiem 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 interfejsu API grupy map

Przykładowy kod aplikacji powtarza prefiks adresu URL za każdym razem, gdy konfiguruje todoitems punkt końcowy. Interfejsy API często mają grupy punktów końcowych z typowym prefiksem adresu URL, a MapGroup metoda jest dostępna w celu ułatwienia organizowania takich grup. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata.

Zastąp zawartość Program.cs następującym kodem:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>
    await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Powyższy kod ma następujące zmiany:

  • Dodaje var todoItems = app.MapGroup("/todoitems"); element do skonfigurowania grupy przy użyciu prefiksu /todoitemsadresu URL .
  • 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 (TypdResults a wyniki).

Metody Map<HttpVerb> mogą wywoływać metody obsługi tras zamiast używać lambd. Aby zobaczyć przykład, zaktualizuj Program.cs przy użyciu następującego kodu:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Kod Map<HttpVerb> wywołuje teraz metody zamiast lambd:

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Te metody zwracają obiekty, które implementują IResult obiekty i są definiowane przez TypedResultselement :

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Testy jednostkowe mogą wywoływać te metody i testować, czy zwracają prawidłowy typ. Jeśli na przykład metoda to GetAllTodos:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

Kod testu jednostkowego może sprawdzić, czy obiekt typu Ok<Todo[]> jest zwracany z metody obsługi. Na przykład:

public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check for the correct returned type
    Assert.IsType<Ok<Todo[]>>(result);
}

Zapobieganie nadmiernemu delegowaniu

Obecnie przykładowa aplikacja uwidacznia cały Todo obiekt. Aplikacje produkcyjne W aplikacjach produkcyjnych podzbiór modelu jest często używany do ograniczania danych, które mogą być wprowadzane i zwracane. Istnieje wiele powodów, dla których jest to ważne. Podzbiór modelu jest zwykle nazywany obiektem transferu danych (DTO), modelem wejściowym lub modelem widoku. DTO jest używane w tym artykule.

Cel DTO może służyć do:

  • Zapobiegaj nadmiernemu delegowaniu.
  • Ukryj właściwości, których klienci nie powinni wyświetlać.
  • Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
  • Spłaszczane wykresy obiektów zawierające zagnieżdżone obiekty. Spłaszczone grafy obiektów mogą być wygodniejsze dla klientów.

Aby zademonstrować podejście DTO, zaktualizuj klasę Todo tak, aby zawierała pole wpisu tajnego:

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

Pole wpisu tajnego musi być ukryte w tej aplikacji, ale aplikacja administracyjna może ją uwidocznić.

Sprawdź, czy możesz opublikować i pobrać pole wpisu tajnego.

Utwórz plik o nazwie z TodoItemDTO.cs następującym kodem:

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

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Zastąp zawartość Program.cs pliku następującym kodem, aby użyć tego modelu DTO:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db) {
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(new TodoItemDTO(todo))
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    todoItemDTO = new TodoItemDTO(todoItem);

    return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}

static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Sprawdź, czy możesz publikować i pobierać wszystkie pola z wyjątkiem pola wpisu tajnego.

Rozwiązywanie problemów z ukończonym przykładem

Jeśli napotkasz problem, nie możesz go rozwiązać, porównaj kod z ukończonym projektem. Wyświetl lub pobierz ukończony projekt (jak pobrać).

Następne kroki

Dowiedz się więcej

Zobacz krótkie informacje o minimalnych interfejsach API