Delen via


Zelfstudie: Een minimale API maken met ASP.NET Core

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

Door Rick Anderson en Tom Dykstra

Minimale API's zijn ontworpen om HTTP-API's te maken met minimale afhankelijkheden. Ze zijn ideaal voor microservices en apps die alleen de minimale bestanden, functies en afhankelijkheden in ASP.NET Core willen opnemen.

In deze zelfstudie leert u de basisbeginselen van het bouwen van een minimale API met ASP.NET Core. Een andere benadering voor het maken van API's in ASP.NET Core is het gebruik van controllers. Zie het overzicht van API'svoor hulp bij het kiezen tussen minimale API's en api's op basis van een controller. Zie Een web-API-maken voor een tutorial over het opstellen van een API-project met meer functies, gebaseerd op controllers.

Overzicht

Bij deze handleiding maken we de volgende API:

API Beschrijving Aanvraaginhoud Hoofdtekst van antwoord
GET /todoitems Alle to-do items ophalen Geen Matrix van to-do items
GET /todoitems/complete Voltooide to-do items verkrijgen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen met ID Geen Actiepunt
POST /todoitems Een nieuw item toevoegen Takenitem Te doen taak
PUT /todoitems/{id} Een bestaand item bijwerken Actiepunt Geen
DELETE /todoitems/{id}     Een item verwijderen Geen Geen

Voorwaarden

Een API-project maken

  • Start Visual Studio 2022 en selecteer Een nieuw project maken.

  • In het dialoogvenster Een Nieuw Project Maken:

    • Typ Empty in het zoekvak Zoeken naar sjablonen.
    • Selecteer de sjabloon ASP.NET Core Empty en klik op Volgende.

    Visual Studio Een nieuw project maken

  • Geef het project de naam TodoApi- en selecteer Volgende.

  • In het dialoogvenster Aanvullende informatie:

    • Selecteer .NET 9.0
    • Schakel Gebruik geen instructies op het hoogste niveau uit
    • Selecteer Creëren

    Aanvullende informatie

De code onderzoeken

Het bestand Program.cs bevat de volgende code:

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

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

app.Run();

De voorgaande code:

  • Hiermee maakt u een WebApplicationBuilder en een WebApplication met vooraf geconfigureerde standaardinstellingen.
  • Hiermee maakt u een HTTP GET-eindpunt / dat Hello World!retourneert:

De app uitvoeren

Druk op Ctrl+F5 om uit te voeren zonder het foutopsporingsprogramma.

Visual Studio geeft het volgende dialoogvenster weer:

Dit project is geconfigureerd voor het gebruik van SSL. Als u SSL-waarschuwingen in de browser wilt voorkomen, kunt u ervoor kiezen om het zelfondertekende certificaat te vertrouwen dat IIS Express heeft gegenereerd. Wilt u het IIS Express SSL-certificaat vertrouwen?

Selecteer Ja als u het IIS Express SSL-certificaat vertrouwt.

Het volgende dialoogvenster wordt weergegeven:

beveiligingswaarschuwingsdialoogvenster

Selecteer Ja als u akkoord gaat met het vertrouwen van het ontwikkelingscertificaat.

Zie Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certificaatfoutvoor meer informatie over het vertrouwen van de Firefox-browser.

Visual Studio start de Kestrel webserver en opent een browservenster.

Hello World! wordt weergegeven in de browser. Het bestand Program.cs bevat een minimale maar volledige app.

Sluit het browservenster.

NuGet-pakketten toevoegen

NuGet-pakketten moeten worden toegevoegd ter ondersteuning van de database en diagnostische gegevens die in deze zelfstudie worden gebruikt.

  • Selecteer in het menu ToolsNuGet Package Manager > NuGet-pakketten beheren voor Solution.
  • Selecteer het tabblad Bladeren.
  • Selecteer Inclusief Prelease.
  • Voer Microsoft.EntityFrameworkCore.InMemory- in het zoekvak in en selecteer vervolgens Microsoft.EntityFrameworkCore.InMemory.
  • Vink het selectievakje Project in het rechterpaneel aan en selecteer vervolgens Installeren.
  • Volg de voorgaande instructies om het Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore-pakket toe te voegen.

De contextklassen van het model en de database

  • Maak in de projectmap een bestand met de naam Todo.cs met de volgende code:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Met de voorgaande code wordt het model voor deze app gemaakt. Een model is een klasse die gegevens vertegenwoordigt die door de app worden beheerd.

  • Maak een bestand met de naam TodoDb.cs met de volgende code:
using Microsoft.EntityFrameworkCore;

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

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

De voorgaande code definieert de databasecontext, de belangrijkste klasse die de functionaliteit van Entity Framework coördineert voor een gegevensmodel. Deze klasse is afgeleid van de klasse Microsoft.EntityFrameworkCore.DbContext.

De API-code toevoegen

  • Vervang de inhoud van het bestand Program.cs door de volgende code:
using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

Met de volgende gemarkeerde code wordt de databasecontext toegevoegd aan de afhankelijkheidsinjectie (DI) container en worden databasegerelateerde uitzonderingen weergegeven:

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

De DI-container biedt toegang tot de databasecontext en andere services.

In deze zelfstudie worden Endpoints Explorer en .http-bestanden gebruikt om de API te testen.

Gegevens voor plaatsing testen

Met de volgende code in Program.cs wordt een HTTP POST-eindpunt gemaakt /todoitems waarmee gegevens worden toegevoegd aan de in-memory database:

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

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

Voer de app uit. In de browser wordt een 404-fout weergegeven omdat er geen / eindpunt meer is.

Het POST-eindpunt wordt gebruikt om gegevens toe te voegen aan de app.

  • Selecteer Weergave>Andere vensters>Endpoints Explorer.

  • Klik met de rechtermuisknop op het eindpunt POST en selecteer Aanvraag genereren.

    contextmenu van Endpoints Explorer waarin het menu-item Aanvraag genereren is gemarkeerd.

    Er wordt een nieuw bestand gemaakt in de projectmap met de naam TodoApi.http, met inhoud die vergelijkbaar is met het volgende voorbeeld:

    @TodoApi_HostAddress = https://localhost:7031
    
    Post {{TodoApi_HostAddress}}/todoitems
    
    ###
    
    • Met de eerste regel maakt u een variabele die wordt gebruikt voor alle eindpunten.
    • De volgende regel definieert een POST-aanvraag.
    • De regel met de drievoudige hashtag (###) is een scheidingsteken voor aanvragen: wat erna komt, is voor een andere aanvraag.
  • De POST-aanvraag heeft headers en een hoofdtekst nodig. Als u deze onderdelen van de aanvraag wilt definiëren, voegt u de volgende regels toe direct na de POST-aanvraagregel:

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

    Met de voorgaande code worden een header voor het inhoudstype en een hoofdtekst van een JSON-aanvraag toegevoegd. Het todoApi.http-bestand moet er nu uitzien zoals in het volgende voorbeeld, maar met uw poortnummer:

    @TodoApi_HostAddress = https://localhost:7057
    
    Post {{TodoApi_HostAddress}}/todoitems
    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    
    ###
    
  • Voer de app uit.

  • Selecteer de koppeling Aanvraag verzenden boven de POST aanvraagregel.

    .http-bestandvenster met run link gemarkeerd.

    De POST-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord.

    .http-bestandvenster met reactie van de POST-aanvraag.

De GET-eindpunten onderzoeken

De voorbeeld-app implementeert verschillende GET-eindpunten door MapGetaan te roepen:

API Beschrijving Aanvraaginhoud Hoofdtekst van antwoord
GET /todoitems Alle to-do items verkrijgen Geen Matrix van to-do items
GET /todoitems/complete Alle voltooide to-do items ophalen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen met ID Geen Takenitem
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());

De GET-eindpunten testen

Test de app door de GET-eindpunten aan te roepen vanuit een browser of met behulp van Endpoints Explorer. De volgende stappen zijn bedoeld voor Endpoints Explorer.

  • Klik in Endpoints Explorermet de rechtermuisknop op het eerste GET--eindpunt en selecteer Aanvraag genereren.

    De volgende inhoud wordt toegevoegd aan het TodoApi.http-bestand:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Selecteer de koppeling Aanvraag verzenden boven de nieuwe GET aanvraagregel.

    De GET-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord.

  • De hoofdtekst van het antwoord is vergelijkbaar met de volgende JSON:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • Klik in Endpoints Explorermet de rechtermuisknop op het eindpunt /todoitems/{id}GET en selecteer Aanvraag genereren. De volgende inhoud wordt toegevoegd aan het TodoApi.http-bestand:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Vervang {id} door 1.

  • Selecteer de aanvraag verzenden koppeling die zich boven de nieuwe GET-aanvraagregel bevindt.

    De GET-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord.

  • De hoofdtekst van het antwoord is vergelijkbaar met de volgende JSON:

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

Deze app maakt gebruik van een in-memory database. Als de app opnieuw wordt opgestart, retourneert de GET-aanvraag geen gegevens. Als er geen gegevens worden geretourneerd, POST gegevens naar de app en probeer de GET-aanvraag opnieuw.

Retourwaarden

ASP.NET Core het object automatisch serialiseert naar JSON- en schrijft de JSON naar de hoofdtekst van het antwoordbericht. De antwoordcode voor dit retourtype is 200 OK, ervan uitgaande dat er geen onverwerkte uitzonderingen zijn. Niet-verwerkte uitzonderingen worden omgezet in 5xx-fouten.

De retourtypen kunnen een breed scala aan HTTP-statuscodes vertegenwoordigen. GET /todoitems/{id} kan bijvoorbeeld twee verschillende statuswaarden retourneren:

  • Als er geen item overeenkomt met de aangevraagde id, retourneert de methode een 404-statusNotFound foutcode.
  • Anders retourneert de methode 200 met een JSON-antwoordtekst. Het retourneren van item resulteert in een HTTP 200-antwoord.

Het PUT-eindpunt onderzoeken

De voorbeeld-app implementeert één PUT-eindpunt met behulp van 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();
});

Deze methode is vergelijkbaar met de methode MapPost, met uitzondering van HTTP PUT. Een geslaagd antwoord retourneert 204 (Geen inhoud). Volgens de HTTP-specificatie vereist een PUT-aanvraag dat de client de volledige bijgewerkte entiteit verzendt, niet alleen de wijzigingen. Gebruik HTTP PATCH-om gedeeltelijke updates te ondersteunen.

Het PUT-eindpunt testen

In dit voorbeeld wordt een in-memory database gebruikt die telkens wanneer de app wordt gestart, moet worden geïnitialiseerd. Er moet een item in de database staan voordat u een PUT-aanroep uitvoert. Roep GET aan om ervoor te zorgen dat er een item in de database staat voordat u een PUT-aanroep doet.

Werk het to-do item met Id = 1 bij en stel de naam ervan in op "feed fish".

  • Klik in Endpoints Explorermet de rechtermuisknop op het PUT-eindpunt en selecteer Aanvraag genereren.

    De volgende inhoud wordt toegevoegd aan het TodoApi.http-bestand:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Vervang {id} in de PUT-aanvraagregel door 1.

  • Voeg de volgende regels toe direct na de PUT-aanvraagregel:

    Content-Type: application/json
    
    {
      "name": "feed fish",
      "isComplete": false
    }
    

    Met de voorgaande code worden een header voor het inhoudstype en een hoofdtekst van een JSON-aanvraag toegevoegd.

  • Selecteer de koppeling Aanvraag verzenden boven de nieuwe PUT-aanvraagregel.

    De PUT-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord. De hoofdtekst van het antwoord is leeg en de statuscode is 204.

Het DELETE-eindpunt onderzoeken en testen

De voorbeeld-app implementeert één DELETE-eindpunt met behulp van 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();
});
  • Klik in Endpoints Explorermet de rechtermuisknop op het eindpunt DELETE en selecteer Aanvraag genereren.

    Er wordt een DELETE-aanvraag toegevoegd aan TodoApi.http.

  • Vervang {id} in de regel van de DELETE-aanvraag door 1. De DELETE-aanvraag moet eruitzien als in het volgende voorbeeld:

    DELETE {{TodoApi_HostAddress}}/todoitems/1
    
    ###
    
  • Selecteer de koppeling Aanvraag verzenden voor de DELETE-aanvraag.

    De DELETE-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord. De hoofdtekst van het antwoord is leeg en de statuscode is 204.

De MapGroup-API gebruiken

De code van de voorbeeld-app herhaalt het todoitems URL-voorvoegsel telkens wanneer er een eindpunt wordt ingesteld. API's hebben vaak groepen eindpunten met een gemeenschappelijk URL-voorvoegsel en de MapGroup methode is beschikbaar om dergelijke groepen te organiseren. Het vermindert terugkerende code en maakt het mogelijk om hele groepen eindpunten aan te passen met één aanroep naar methoden zoals RequireAuthorization en WithMetadata.

Vervang de inhoud van Program.cs door de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

De voorgaande code heeft de volgende wijzigingen:

  • Voegt var todoItems = app.MapGroup("/todoitems"); toe om de groep in te stellen met behulp van het URL-voorvoegsel /todoitems.
  • Hiermee worden alle app.Map<HttpVerb> methoden gewijzigd in todoItems.Map<HttpVerb>.
  • Hiermee verwijdert u het URL-voorvoegsel /todoitems uit de aanroepen van de Map<HttpVerb> methode.

Test de eindpunten om te controleren of ze hetzelfde werken.

De TypedResults-API gebruiken

Het retourneren van TypedResults in plaats van Results heeft verschillende voordelen, waaronder testbaarheid en het automatisch retourneren van de metagegevens van het antwoordtype voor OpenAPI om het eindpunt te beschrijven. Voor meer informatie, zie TypedResults versus Results.

De Map<HttpVerb> methoden kunnen methoden voor routehandlers aanroepen in plaats van lambdas te gebruiken. Als u een voorbeeld wilt zien, werkt u Program.cs bij met de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

app.Run();

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

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

    return TypedResults.NotFound();
}

De Map<HttpVerb>-code roept nu methoden aan in plaats van lambdas:

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

Deze methoden retourneren objecten die IResult implementeren en worden gedefinieerd door TypedResults:

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

Eenheidstests kunnen deze methoden aanroepen en testen of ze het juiste type retourneren. Als de methode bijvoorbeeld is GetAllTodos:

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

Code voor eenheidstests kan controleren of een object van het type OK<Todo[]> wordt geretourneerd door de handlermethode. Bijvoorbeeld:

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

Overposten voorkomen

Op dit moment wordt in de voorbeeld-app het hele Todo-object weergegeven. Productie-apps In productietoepassingen wordt vaak een subset van het model gebruikt om de gegevens te beperken die kunnen worden ingevoerd en geretourneerd. Er zijn meerdere redenen achter deze en beveiliging is een belangrijke. De subset van een model wordt meestal aangeduid als een DTO (Data Transfer Object), invoermodel of weergavemodel. DTO- wordt in dit artikel gebruikt.

Een DTO kan worden gebruikt voor het volgende:

  • Voorkom overboeking.
  • Eigenschappen verbergen die klanten niet zouden moeten zien.
  • Laat sommige eigenschappen weg om de nettolading te verkleinen.
  • Platte objectgrafieken die geneste objecten bevatten. Platgemaakte objectgrafieken kunnen handiger zijn voor clients.

Als u de DTO-benadering wilt demonstreren, werkt u de Todo klasse bij om een geheim veld op te nemen:

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

Het geheime veld moet worden verborgen voor deze app, maar een beheer-app kan ervoor kiezen om het beschikbaar te maken.

Controleer of u het geheime veld kunt posten en ophalen.

Maak een bestand met de naam TodoItemDTO.cs met de volgende code:

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

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

Vervang de inhoud van het Program.cs-bestand door de volgende code om dit DTO-model te gebruiken:

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

Controleer of u alle velden kunt posten en ophalen, behalve het geheime veld.

Problemen oplossen met het voltooide voorbeeld

Als u een probleem ondervindt dat u niet kunt oplossen, vergelijkt u de code met het voltooide project. Bekijk of download het voltooide project (hoe te downloaden).

Volgende stappen

Meer informatie

Zie Snelle referentie voor minimale API's

Minimale API's zijn ontworpen om HTTP-API's te maken met minimale afhankelijkheden. Ze zijn ideaal voor microservices en apps die alleen de minimale bestanden, functies en afhankelijkheden in ASP.NET Core willen opnemen.

In deze zelfstudie leert u de basisbeginselen van het bouwen van een minimale API met ASP.NET Core. Een andere benadering voor het maken van API's in ASP.NET Core is het gebruik van controllers. Zie het overzicht van API'svoor hulp bij het kiezen tussen minimale API's en api's op basis van een controller. Zie Een web-API-maken voor een handleiding over het creëren van een API-project gebaseerd op controllers met meer functionaliteiten.

Overzicht

In deze handleiding maakt u de volgende API:

API Beschrijving Aanvraaginhoud Hoofdtekst van antwoord
GET /todoitems Haal alle to-do items op. Geen Matrix van to-do items
GET /todoitems/complete Voltooide to-do items ophalen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen via ID Geen Actiepunt
POST /todoitems Een nieuw item toevoegen Actiepunt Takenitem
PUT /todoitems/{id} Een bestaand item bijwerken Takenitem Geen
DELETE /todoitems/{id}     Een item verwijderen Geen Geen

Voorwaarden

Een API-project maken

  • Start Visual Studio 2022 en selecteer Een nieuw project maken.

  • In het dialoogvenster Een nieuw project maken:

    • Typ Empty in het zoekvak Zoeken naar sjablonen.
    • Selecteer de sjabloon ASP.NET Core Empty en selecteer Volgende.

    Visual Studio Een nieuw project maken

  • Geef het project de naam TodoApi- en selecteer Volgende.

  • In het dialoogvenster Aanvullende informatie:

    • Selecteer .NET 7.0-
    • Schakel Gebruik geen instructies op het hoogste niveau uit
    • Selecteer Creëer

    Aanvullende informatie

De code onderzoeken

Het bestand Program.cs bevat de volgende code:

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

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

app.Run();

De voorgaande code:

  • Hiermee maakt u een WebApplicationBuilder en een WebApplication met vooraf geconfigureerde standaardinstellingen.
  • Hiermee maakt u een HTTP GET-eindpunt / dat Hello World!retourneert:

De app uitvoeren

Druk op Ctrl+F5 om uit te voeren zonder het foutopsporingsprogramma.

Visual Studio geeft het volgende dialoogvenster weer:

Dit project is geconfigureerd voor het gebruik van SSL. Als u SSL-waarschuwingen in de browser wilt voorkomen, kunt u ervoor kiezen om het zelfondertekende certificaat te vertrouwen dat IIS Express heeft gegenereerd. Wilt u het IIS Express SSL-certificaat vertrouwen?

Selecteer Ja als u het IIS Express SSL-certificaat vertrouwt.

Het volgende dialoogvenster wordt weergegeven:

beveiligingswaarschuwingsdialoogvenster

Selecteer Ja als u akkoord gaat met het vertrouwen van het ontwikkelingscertificaat.

Zie Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certificaatfoutvoor meer informatie over het vertrouwen van de Firefox-browser.

Visual Studio start de Kestrel webserver en opent een browservenster.

Hello World! wordt weergegeven in de browser. Het bestand Program.cs bevat een minimale maar volledige app.

NuGet-pakketten toevoegen

NuGet-pakketten moeten worden toegevoegd ter ondersteuning van de database en diagnostische gegevens die in deze zelfstudie worden gebruikt.

  • Selecteer in het menu ToolsNuGet Package Manager > NuGet-pakketten beheren voor Solution.
  • Selecteer het tabblad Bladeren.
  • Voer Microsoft.EntityFrameworkCore.InMemory- in het zoekvak in en selecteer vervolgens Microsoft.EntityFrameworkCore.InMemory.
  • Selecteer het selectievakje Project in het rechterdeelvenster.
  • Selecteer in het dropdownmenu versie de meest recente beschikbare versie 7, bijvoorbeeld 7.0.17, en selecteer vervolgens Installeren.
  • Volg de voorgaande instructies om het Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore-pakket toe te voegen met de nieuwste versie 7 die beschikbaar is.

De contextklassen van het model en de database

Maak in de projectmap een bestand met de naam Todo.cs met de volgende code:

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

Met de voorgaande code wordt het model voor deze app gemaakt. Een model is een klasse die gegevens vertegenwoordigt die door de app worden beheerd.

Maak een bestand met de naam TodoDb.cs met de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

De voorgaande code definieert de databasecontext, de belangrijkste klasse die de functionaliteit van Entity Framework coördineert voor een gegevensmodel. Deze klasse is afgeleid van de klasse Microsoft.EntityFrameworkCore.DbContext.

De API-code toevoegen

Vervang de inhoud van het bestand Program.cs door de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

Met de volgende gemarkeerde code wordt de databasecontext toegevoegd aan de afhankelijkheidsinjectie (DI) container en worden databasegerelateerde uitzonderingen weergegeven:

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

De DI-container biedt toegang tot de databasecontext en andere services.

API-testinterface maken met Swagger

Er zijn veel beschikbare hulpprogramma's voor het testen van web-API's waaruit u kunt kiezen en u kunt de inleidende API-teststappen van deze zelfstudie volgen met uw eigen favoriete hulpprogramma.

In deze zelfstudie wordt gebruikgemaakt van het .NET-pakket NSwag.AspNetCore, waarmee Swagger-hulpprogramma's worden geïntegreerd voor het genereren van een testgebruikersinterface die voldoet aan de OpenAPI-specificatie:

  • NSwag: Een .NET-bibliotheek die Swagger rechtstreeks integreert in ASP.NET Core-toepassingen, waardoor middleware en configuratie worden geboden.
  • Swagger: Een set opensource-hulpprogramma's zoals OpenAPIGenerator en SwaggerUI die API-testpagina's genereren die voldoen aan de OpenAPI-specificatie.
  • OpenAPI-specificatie: een document dat de mogelijkheden van de API beschrijft, op basis van de XML- en kenmerkaantekeningen binnen de controllers en modellen.

Zie ASP.NET Core-web-API-documentatie met Swagger/OpenAPI-voor meer informatie over het gebruik van OpenAPI en NSwag met ASP.NET.

Swagger-hulpmiddelen installeren

  • Voer de volgende opdracht uit:

    dotnet add package NSwag.AspNetCore
    

Met de vorige opdracht wordt het pakket NSwag.AspNetCore toegevoegd, dat hulpprogramma's bevat voor het genereren van Swagger-documenten en de gebruikersinterface.

Swagger-middleware configureren

  • Voeg de volgende gemarkeerde code toe voordat app is gedefinieerd in regel 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();
    

In de vorige code:

  • builder.Services.AddEndpointsApiExplorer();: hiermee schakelt u de API Explorer in. Dit is een service die metagegevens over de HTTP-API biedt. De API Explorer wordt door Swagger gebruikt om het Swagger-document te genereren.

  • builder.Services.AddOpenApiDocument(config => {...});: voegt de Swagger OpenAPI-documentgenerator toe aan de toepassingsservices en configureert deze voor meer informatie over de API, zoals de titel en versie. Zie Aan de slag met NSwag en ASP.NET Core voor meer informatie over het bieden van krachtigere API-details

  • Voeg de volgende gemarkeerde code toe aan de volgende regel nadat app is gedefinieerd in regel 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";
        });
    }
    

    Met de vorige code wordt de Swagger middleware ingeschakeld voor het leveren van het gegenereerde JSON-document en de Swagger-gebruikersinterface. Swagger is alleen ingeschakeld in een ontwikkelomgeving. Het inschakelen van Swagger in een productieomgeving kan mogelijk gevoelige details over de structuur en implementatie van de API beschikbaar maken.

Testgegevens plaatsen

Met de volgende code in Program.cs wordt een HTTP POST-eindpunt gemaakt /todoitems waarmee gegevens worden toegevoegd aan de in-memory database:

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

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

Voer de app uit. In de browser wordt een 404-fout weergegeven omdat er geen / eindpunt meer is.

Het POST-eindpunt wordt gebruikt om gegevens toe te voegen aan de app.

  • Wanneer de app nog steeds wordt uitgevoerd, gaat u in de browser naar https://localhost:<port>/swagger om de API-testpagina weer te geven die is gegenereerd door Swagger.

    Swagger gegenereerde API-testpagina

  • Selecteer op de testpagina van de Swagger-API Post /todoitems>Probeer het.

  • Houd er rekening mee dat het veld aanvraagbody een gegenereerde voorbeeldindeling bevat die de parameters voor de API weergeeft.

  • Voer in de aanvraagbody JSON in voor een to-do item, zonder de optionele idop te geven:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Selecteer en voeruit.

    Swagger met Post

Swagger biedt een deelvenster Antwoorden onder de knop Uitvoeren.

Swagger met Post-antwoord

Let op een paar nuttige details:

  • cURL: Swagger biedt een voorbeeld van een cURL-opdracht in unix-/Linux-syntaxis, die kan worden uitgevoerd op de opdrachtregel met elke bash-shell die gebruikmaakt van Unix/Linux-syntaxis, waaronder Git Bash vanuit Git voor Windows.
  • Aanvraag-URL: Een vereenvoudigde weergave van de HTTP-aanvraag die is gedaan door de JavaScript-code van Swagger UI voor de API-aanroep. Werkelijke aanvragen kunnen details bevatten, zoals headers en queryparameters en een aanvraagbody.
  • Serverantwoord: bevat de hoofdtekst en headers van het antwoord. In de hoofdtekst van het antwoord ziet u dat de id is ingesteld op 1.
  • Antwoordcode: Een 201 HTTP statuscode is geretourneerd, wat aangeeft dat de aanvraag is verwerkt en heeft geresulteerd in het maken van een nieuwe resource.

De GET-eindpunten onderzoeken

De voorbeeld-app implementeert verschillende GET-eindpunten door MapGetaan te roepen:

API Beschrijving Inhoud van het verzoek Hoofdtekst van antwoord
GET /todoitems Alle to-do items ophalen Geen Matrix van to-do items
GET /todoitems/complete Alle voltooide to-do items ophalen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen op id Geen Takenitem
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());

De GET-eindpunten testen

Test de app door de eindpunten vanuit een browser of Swagger aan te roepen.

  • Selecteer in Swagger GET /todoitems>Probeer het uit>Uitvoeren.

  • U kunt ook GET /todoitems vanuit een browser aanroepen door de URI-http://localhost:<port>/todoitemsin te voeren. Bijvoorbeeld http://localhost:5001/todoitems

De aanroep van GET /todoitems produceert een antwoord dat er ongeveer als volgt uitziet:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Roep GET /todoitems/{id} in Swagger aan om gegevens van een specifieke id te retourneren:

    • Selecteer GET /todoitems>Probeer het uit.
    • Stel het veld id in op 1 en selecteer uitvoeren.
  • U kunt ook GET /todoitems vanuit een browser aanroepen door de URI-https://localhost:<port>/todoitems/1in te voeren. Bijvoorbeeld https://localhost:5001/todoitems/1

  • Het antwoord is vergelijkbaar met het volgende:

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

Deze app maakt gebruik van een in-memory database. Als de app opnieuw wordt opgestart, retourneert de GET-aanvraag geen gegevens. Als er geen gegevens worden geretourneerd, POST gegevens naar de app en probeer de GET-aanvraag opnieuw.

Retourwaarden

ASP.NET Core het object automatisch serialiseert naar JSON- en schrijft de JSON naar de hoofdtekst van het antwoordbericht. De antwoordcode voor dit retourtype is 200 OK, ervan uitgaande dat er geen onverwerkte uitzonderingen zijn. Niet-verwerkte uitzonderingen worden omgezet in 5xx-fouten.

De retourtypen kunnen een breed scala aan HTTP-statuscodes vertegenwoordigen. GET /todoitems/{id} kan bijvoorbeeld twee verschillende statuswaarden retourneren:

  • Als er geen item overeenkomt met de aangevraagde id, retourneert de methode een 404-statusNotFound foutcode.
  • Anders retourneert de methode 200 met een JSON-antwoordtekst. Het retourneren van item resulteert in een HTTP 200-antwoord.

Het PUT-eindpunt onderzoeken

De voorbeeld-app implementeert één PUT-eindpunt met behulp van 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();
});

Deze methode is vergelijkbaar met de methode MapPost, met uitzondering van HTTP PUT. Een geslaagd antwoord retourneert 204 (Geen inhoud). Volgens de HTTP-specificatie vereist een PUT-aanvraag dat de client de volledige bijgewerkte entiteit verzendt, niet alleen de wijzigingen. Gebruik HTTP PATCH-om gedeeltelijke updates te ondersteunen.

Het PUT-eindpunt testen

In dit voorbeeld wordt een in-memory database gebruikt die telkens wanneer de app wordt gestart, moet worden geïnitialiseerd. Er moet een item in de database staan voordat u een PUT-aanroep uitvoert. Roep GET aan om ervoor te zorgen dat er een item in de database staat voordat u een PUT-aanroep doet.

Werk het to-do item met Id = 1 bij en stel de naam ervan in op "feed fish".

Gebruik Swagger om een PUT-aanvraag te verzenden:

  • Selecteer /todoitems/{id}>probeer het.

  • Stel het veld id in op 1.

  • Stel de aanvraagbody in op de volgende JSON:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Selecteer Voeruit.

Het DELETE-eindpunt onderzoeken en testen

De voorbeeld-app implementeert één DELETE-eindpunt met behulp van 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();
});

Gebruik Swagger om een DELETE-aanvraag te verzenden:

  • Selecteer DELETE /todoitems/{id}>probeer het.

  • Stel het veld id in op 1 en selecteer uitvoeren.

    De DELETE-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoorden. De hoofdtekst van het antwoord is leeg en het Server-antwoord statuscode is 204.

De MapGroup-API gebruiken

De code van de voorbeeld-app herhaalt het todoitems URL-voorvoegsel telkens wanneer er een eindpunt wordt ingesteld. API's hebben vaak groepen eindpunten met een gemeenschappelijk URL-voorvoegsel en de MapGroup methode is beschikbaar om dergelijke groepen te organiseren. Het vermindert terugkerende code en maakt het mogelijk om hele groepen eindpunten aan te passen met één aanroep naar methoden zoals RequireAuthorization en WithMetadata.

Vervang de inhoud van Program.cs door de volgende code:

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

De voorgaande code heeft de volgende wijzigingen:

  • Voegt var todoItems = app.MapGroup("/todoitems"); toe om de groep in te stellen met behulp van het URL-voorvoegsel /todoitems.
  • Hiermee worden alle app.Map<HttpVerb> methoden gewijzigd in todoItems.Map<HttpVerb>.
  • Hiermee verwijdert u het URL-voorvoegsel /todoitems uit de aanroepen van de Map<HttpVerb> methode.

Test de eindpunten om te controleren of ze hetzelfde werken.

De TypedResults-API gebruiken

Het retourneren van TypedResults in plaats van Results heeft verschillende voordelen, waaronder testbaarheid en het automatisch retourneren van de metagegevens van het antwoordtype voor OpenAPI om het eindpunt te beschrijven. Voor meer informatie, zie TypedResults versus Results.

De Map<HttpVerb> methoden kunnen methoden voor routehandlers aanroepen in plaats van lambdas te gebruiken. Als u een voorbeeld wilt zien, werkt u Program.cs bij met de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

app.Run();

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

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

    return TypedResults.NotFound();
}

De Map<HttpVerb>-code roept nu methoden aan in plaats van lambdas:

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

Deze methoden retourneren objecten die IResult implementeren en worden gedefinieerd door TypedResults:

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

Eenheidstests kunnen deze methoden aanroepen en testen of ze het juiste type retourneren. Als de methode bijvoorbeeld is GetAllTodos:

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

Code voor eenheidstests kan controleren of een object van het type OK<Todo[]> wordt geretourneerd door de handlermethode. Bijvoorbeeld:

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

Overmatig posten voorkomen

Op dit moment wordt in de voorbeeld-app het hele Todo-object weergegeven. Productie-apps In productietoepassingen wordt vaak een subset van het model gebruikt om de gegevens te beperken die kunnen worden ingevoerd en geretourneerd. Er zijn meerdere redenen achter deze en beveiliging is een belangrijke. De subset van een model wordt meestal aangeduid als een DTO (Data Transfer Object), invoermodel of weergavemodel. DTO- wordt in dit artikel gebruikt.

Een DTO kan worden gebruikt voor het volgende:

  • Voorkom te veel berichten plaatsen
  • Eigenschappen verbergen die klanten niet mogen zien.
  • Laat sommige eigenschappen weg om de nettolading te verkleinen.
  • Platte objectgrafieken die geneste objecten bevatten. Platgemaakte objectgrafieken kunnen handiger zijn voor clients.

Als u de DTO-benadering wilt demonstreren, werkt u de Todo klasse bij om een geheim veld op te nemen:

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

Het geheime veld moet worden verborgen voor deze app, maar een beheer-app kan ervoor kiezen om het beschikbaar te maken.

Controleer of u het geheime veld kunt posten en ophalen.

Maak een bestand met de naam TodoItemDTO.cs met de volgende code:

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

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

Vervang de inhoud van het Program.cs-bestand door de volgende code om dit DTO-model te gebruiken:

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

Controleer of u alle velden kunt posten en ophalen, behalve het geheime veld.

Problemen oplossen met het voltooide voorbeeld

Als u een probleem ondervindt dat u niet kunt oplossen, vergelijkt u de code met het voltooide project. Voltooid project weergeven of downloaden (downloaden van).

Volgende stappen

Meer informatie

Raadpleeg de beknopte handleiding voor minimale API's

Minimale API's zijn ontworpen om HTTP-API's te maken met minimale afhankelijkheden. Ze zijn ideaal voor microservices en apps die alleen de minimale bestanden, functies en afhankelijkheden in ASP.NET Core willen opnemen.

In deze zelfstudie leert u de basisbeginselen van het bouwen van een minimale API met ASP.NET Core. Een andere benadering voor het maken van API's in ASP.NET Core is het gebruik van controllers. Zie het overzicht van API'svoor hulp bij het kiezen tussen minimale API's en api's op basis van een controller. Zie voor een tutorial over het maken van een API-project, gebaseerd op -controllers die meer functies bevatten, Een web-APImaken.

Overzicht

In deze handleiding gaan we de volgende API maken:

API Beschrijving Aanvraagtekst Hoofdtekst van antwoord
GET /todoitems Alle to-do items ophalen Geen Matrix van to-do items
GET /todoitems/complete Haal voltooide to-do items op Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen met ID Geen Takenitem
POST /todoitems Een nieuw item toevoegen Takenitem Takenitem
PUT /todoitems/{id} Een bestaand item bijwerken Takenitem Geen
DELETE /todoitems/{id}     Een item verwijderen Geen Geen

Voorwaarden

Een API-project maken

  • Start Visual Studio 2022 en selecteer Een nieuw project maken.

  • In het dialoogvenster Een nieuw project maken:

    • Typ Empty in het zoekvak Zoeken naar sjablonen.
    • Selecteer de ASP.NET Core Empty-sjabloon en kies Volgende.

    Visual Studio Een nieuw project maken

  • Geef het project de naam TodoApi- en selecteer Volgende.

  • In het dialoogvenster Aanvullende informatie:

    • Selecteer .NET 6.0
    • Deselecteer Gebruik geen top-level statements
    • Selecteer Creëer

De code onderzoeken

Het bestand Program.cs bevat de volgende code:

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

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

app.Run();

De voorgaande code:

  • Hiermee maakt u een WebApplicationBuilder en een WebApplication met vooraf geconfigureerde standaardinstellingen.
  • Hiermee maakt u een HTTP GET-eindpunt / dat Hello World!retourneert:

De app uitvoeren

Druk op Ctrl+F5 om uit te voeren zonder het foutopsporingsprogramma.

Visual Studio geeft het volgende dialoogvenster weer:

Dit project is geconfigureerd voor het gebruik van SSL. Als u SSL-waarschuwingen in de browser wilt voorkomen, kunt u ervoor kiezen om het zelfondertekende certificaat te vertrouwen dat IIS Express heeft gegenereerd. Wilt u het IIS Express SSL-certificaat vertrouwen?

Selecteer Ja als u het IIS Express SSL-certificaat vertrouwt.

Het volgende dialoogvenster wordt weergegeven:

beveiligingswaarschuwingsdialoogvenster

Selecteer Ja als u akkoord gaat met het vertrouwen van het ontwikkelingscertificaat.

Zie Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certificaatfoutvoor meer informatie over het vertrouwen van de Firefox-browser.

Visual Studio start de Kestrel webserver en opent een browservenster.

Hello World! wordt weergegeven in de browser. Het bestand Program.cs bevat een minimale maar volledige app.

NuGet-pakketten toevoegen

NuGet-pakketten moeten worden toegevoegd ter ondersteuning van de database en diagnostische gegevens die in deze zelfstudie worden gebruikt.

  • Selecteer in het menu ToolsNuGet Package Manager > NuGet-pakketten beheren voor Solution.
  • Selecteer het tabblad Bladeren.
  • Voer Microsoft.EntityFrameworkCore.InMemory- in het zoekvak in en selecteer vervolgens Microsoft.EntityFrameworkCore.InMemory.
  • Selecteer in het rechterdeelvenster het selectievakje Project.
  • Selecteer in de vervolgkeuzelijst versie de meest recente versie 7, bijvoorbeeld 6.0.28, en selecteer vervolgens Installeren.
  • Volg de voorgaande instructies om het Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore-pakket toe te voegen met de nieuwste versie 7 die beschikbaar is.

De contextklassen van het model en de database

Maak in de projectmap een bestand met de naam Todo.cs met de volgende code:

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

Met de voorgaande code wordt het model voor deze app gemaakt. Een model is een klasse die gegevens vertegenwoordigt die door de app worden beheerd.

Maak een bestand met de naam TodoDb.cs met de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

De voorgaande code definieert de databasecontext, die als hoofdklasse de functionaliteit van Entity Framework coördineert voor een gegevensmodel. Deze klasse is afgeleid van de klasse Microsoft.EntityFrameworkCore.DbContext.

De API-code toevoegen

Vervang de inhoud van het bestand Program.cs door de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

Met de volgende gemarkeerde code wordt de databasecontext toegevoegd aan de afhankelijkheidsinjectie (DI) container en worden databasegerelateerde uitzonderingen weergegeven:

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

De DI-container biedt toegang tot de databasecontext en andere services.

API-testinterface maken met Swagger

Er zijn veel beschikbare hulpprogramma's voor het testen van web-API's waaruit u kunt kiezen en u kunt de inleidende API-teststappen van deze zelfstudie volgen met uw eigen favoriete hulpprogramma.

In deze zelfstudie wordt gebruikgemaakt van het .NET-pakket NSwag.AspNetCore, waarmee Swagger-hulpprogramma's worden geïntegreerd voor het genereren van een testgebruikersinterface die voldoet aan de OpenAPI-specificatie:

  • NSwag: Een .NET-bibliotheek die Swagger rechtstreeks integreert in ASP.NET Core-toepassingen, waardoor middleware en configuratie worden geboden.
  • Swagger: Een set opensource-hulpprogramma's zoals OpenAPIGenerator en SwaggerUI die API-testpagina's genereren die voldoen aan de OpenAPI-specificatie.
  • OpenAPI-specificatie: een document dat de mogelijkheden van de API beschrijft, op basis van de XML- en kenmerkaantekeningen binnen de controllers en modellen.

Zie ASP.NET Core-web-API-documentatie met Swagger/OpenAPI-voor meer informatie over het gebruik van OpenAPI en NSwag met ASP.NET.

Swagger-hulpprogramma's installeren

  • Voer de volgende opdracht uit:

    dotnet add package NSwag.AspNetCore
    

Met de vorige opdracht wordt het pakket NSwag.AspNetCore toegevoegd, dat hulpprogramma's bevat voor het genereren van Swagger-documenten en de gebruikersinterface.

Swagger-middleware configureren

  • Voeg in Program.cs bovenaan de volgende using instructies toe:

    using NSwag.AspNetCore;
    
  • Voeg de hieronder gemarkeerde code toe voordat app wordt gedefinieerd in regel 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();
    

In de vorige code:

  • builder.Services.AddEndpointsApiExplorer();: hiermee schakelt u de API Explorer in. Dit is een service die metagegevens over de HTTP-API biedt. De API Explorer wordt door Swagger gebruikt om het Swagger-document te genereren.

  • builder.Services.AddOpenApiDocument(config => {...});: voegt de Swagger OpenAPI-documentgenerator toe aan de toepassingsservices en configureert deze voor meer informatie over de API, zoals de titel en versie. Zie Aan de slag met NSwag en ASP.NET Core voor meer informatie over het bieden van krachtigere API-details

  • Voeg de volgende gemarkeerde code toe aan de volgende regel nadat app is gedefinieerd in regel 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";
        });
    }
    
    

    Met de vorige code wordt de Swagger middleware ingeschakeld voor het leveren van het gegenereerde JSON-document en de Swagger-gebruikersinterface. Swagger is alleen ingeschakeld in een ontwikkelomgeving. Het inschakelen van Swagger in een productieomgeving kan mogelijk gevoelige details over de structuur en implementatie van de API beschikbaar maken.

Testgegevens plaatsen

Met de volgende code in Program.cs wordt een HTTP POST-eindpunt gemaakt /todoitems waarmee gegevens worden toegevoegd aan de in-memory database:

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

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

Voer de app uit. In de browser wordt een 404-fout weergegeven omdat er geen / eindpunt meer is.

Het POST-eindpunt wordt gebruikt om gegevens toe te voegen aan de app.

  • Wanneer de app nog steeds wordt uitgevoerd, gaat u in de browser naar https://localhost:<port>/swagger om de API-testpagina weer te geven die is gegenereerd door Swagger.

    Swagger gegenereerde API-testpagina

  • Selecteer op de testpagina van de Swagger-API Post /todoitems>Probeer het.

  • Houd er rekening mee dat het veld aanvraagbody een gegenereerde voorbeeldindeling bevat die de parameters voor de API weergeeft.

  • Voer in de aanvraagbody JSON in voor een to-do item, zonder de optionele idop te geven:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Selecteer Voeruit.

    Swagger met Post-gegevens

Swagger biedt een paneel Antwoorden onder de knop Uitvoeren.

Swagger met het deelvenster Post-resonse

Let op een paar nuttige details:

  • cURL: Swagger biedt een voorbeeld van een cURL-opdracht in unix-/Linux-syntaxis, die kan worden uitgevoerd op de opdrachtregel met elke bash-shell die gebruikmaakt van Unix/Linux-syntaxis, waaronder Git Bash vanuit Git voor Windows.
  • Aanvraag-URL: Een vereenvoudigde weergave van de HTTP-aanvraag die is gedaan door de JavaScript-code van Swagger UI voor de API-aanroep. Werkelijke aanvragen kunnen details bevatten, zoals headers en queryparameters en een aanvraagbody.
  • Serverantwoord: bevat de hoofdtekst en headers van het antwoord. In de hoofdtekst van het antwoord ziet u dat de id is ingesteld op 1.
  • Antwoordcode: Een 201 HTTP statuscode is geretourneerd, wat aangeeft dat de aanvraag is verwerkt en heeft geresulteerd in het maken van een nieuwe resource.

De GET-eindpunten onderzoeken

De voorbeeld-app implementeert verschillende GET-eindpunten door MapGetaan te roepen:

API Beschrijving Aanvraaginhoud Hoofdtekst van antwoord
GET /todoitems Alle to-do items ophalen Geen Matrix van to-do items
GET /todoitems/complete Alle voltooide to-do items ophalen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen op basis van ID Geen Takenitem
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());

De GET-eindpunten testen

Test de app door de eindpunten vanuit een browser of Swagger aan te roepen.

  • Selecteer in Swagger GET /todoitems>Uitproberen>Uitvoeren.

  • U kunt ook GET /todoitems vanuit een browser aanroepen door de URI-http://localhost:<port>/todoitemsin te voeren. Bijvoorbeeld http://localhost:5001/todoitems

De aanroep van GET /todoitems produceert een antwoord dat er ongeveer als volgt uitziet:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Roep GET /todoitems/{id} in Swagger aan om gegevens van een specifieke id te retourneren:

    • Selecteer GET /todoitems>Probeer het uit.
    • Stel het veld id in op 1 en selecteer uitvoeren.
  • U kunt ook GET /todoitems vanuit een browser aanroepen door de URI-https://localhost:<port>/todoitems/1in te voeren. Bijvoorbeeld, bijvoorbeeld https://localhost:5001/todoitems/1

  • Het antwoord is vergelijkbaar met het volgende:

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

Deze app maakt gebruik van een in-memory database. Als de app opnieuw wordt opgestart, retourneert de GET-aanvraag geen gegevens. Als er geen gegevens worden geretourneerd, POST gegevens naar de app en probeer de GET-aanvraag opnieuw.

Retourwaarden

ASP.NET Core het object automatisch serialiseert naar JSON- en schrijft de JSON naar de hoofdtekst van het antwoordbericht. De antwoordcode voor dit retourtype is 200 OK, ervan uitgaande dat er geen onverwerkte uitzonderingen zijn. Niet-verwerkte uitzonderingen worden omgezet in 5xx-fouten.

De retourtypen kunnen een breed scala aan HTTP-statuscodes vertegenwoordigen. GET /todoitems/{id} kan bijvoorbeeld twee verschillende statuswaarden retourneren:

  • Als er geen item overeenkomt met de aangevraagde id, retourneert de methode een 404-statusNotFound foutcode.
  • Anders retourneert de methode 200 met een JSON-antwoordtekst. Het retourneren van item resulteert in een HTTP 200-antwoord.

Het PUT-eindpunt onderzoeken

De voorbeeld-app implementeert één PUT-eindpunt met behulp van 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();
});

Deze methode is vergelijkbaar met de methode MapPost, met uitzondering van HTTP PUT. Een geslaagd antwoord retourneert 204 (Geen inhoud). Volgens de HTTP-specificatie vereist een PUT-aanvraag dat de client de volledige bijgewerkte entiteit verzendt, niet alleen de wijzigingen. Gebruik HTTP PATCH-om gedeeltelijke updates te ondersteunen.

Het PUT-eindpunt testen

In dit voorbeeld wordt een in-memory database gebruikt die telkens wanneer de app wordt gestart, moet worden geïnitialiseerd. Er moet een item in de database staan voordat u een PUT-aanroep uitvoert. Roep GET aan om ervoor te zorgen dat er een item in de database staat voordat u een PUT-aanroep doet.

Werk het to-do item met Id = 1 bij en stel de naam ervan in op "feed fish".

Gebruik Swagger om een PUT-aanvraag te verzenden:

  • Selecteer /todoitems/{id}>probeer het.

  • Stel het veld id in op 1.

  • Stel de aanvraagbody in op de volgende JSON:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Selecteer Voeruit.

Het DELETE-eindpunt onderzoeken en testen

De voorbeeld-app implementeert één DELETE-eindpunt met behulp van 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();
});

Gebruik Swagger om een DELETE-aanvraag te verzenden:

  • Selecteer DELETE /todoitems/{id}>probeer het.

  • Stel het veld id in op 1 en selecteer uitvoeren.

    De DELETE-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoorden. De hoofdtekst van het antwoord is leeg en het Server-antwoord statuscode is 204.

Overboeking voorkomen

Op dit moment wordt in de voorbeeld-app het hele Todo-object weergegeven. Productie-apps In productietoepassingen wordt vaak een subset van het model gebruikt om de gegevens te beperken die kunnen worden ingevoerd en geretourneerd. Er zijn meerdere redenen achter deze en beveiliging is een belangrijke. De subset van een model wordt meestal aangeduid als een DTO (Data Transfer Object), invoermodel of weergavemodel. DTO- wordt in dit artikel gebruikt.

Een DTO kan worden gebruikt voor het volgende:

  • Voorkom overboeking.
  • Eigenschappen verbergen die klanten niet mogen zien.
  • Laat sommige eigenschappen weg om de nettolading te verkleinen.
  • Platte objectgrafieken die geneste objecten bevatten. Platgemaakte objectgrafieken kunnen handiger zijn voor clients.

Als u de DTO-benadering wilt demonstreren, werkt u de Todo klasse bij om een geheim veld op te nemen:

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

Het geheime veld moet worden verborgen voor deze app, maar een beheer-app kan ervoor kiezen om het beschikbaar te maken.

Controleer of u het geheime veld kunt posten en ophalen.

Maak een bestand met de naam TodoItemDTO.cs met de volgende code:

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

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

Vervang de inhoud van het Program.cs-bestand door de volgende code om dit DTO-model te gebruiken:

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

Controleer of u alle velden kunt posten en ophalen, behalve het geheime veld.

Minimale API testen

Zie dit GitHub-voorbeeldvoor een voorbeeld van het testen van een minimale API-app.

Publiceren naar Azure

Zie Quickstart: Een ASP.NET-web-app implementerenvoor meer informatie over het implementeren in Azure.

Aanvullende informatiebronnen

Minimale API's zijn ontworpen om HTTP-API's te maken met minimale afhankelijkheden. Ze zijn ideaal voor microservices en apps die alleen de minimale bestanden, functies en afhankelijkheden in ASP.NET Core willen opnemen.

In deze zelfstudie leert u de basisbeginselen van het bouwen van een minimale API met ASP.NET Core. Een andere benadering voor het maken van API's in ASP.NET Core is het gebruik van controllers. Zie het overzicht van API'svoor hulp bij het kiezen tussen minimale API's en api's op basis van een controller. Zie voor een zelfstudie over het maken van een API-project gebaseerd op controllers met meer functies, Een web-API maken.

Overzicht

In deze tutorial wordt de volgende API gemaakt:

API Beschrijving Aanvraagtekst Hoofdtekst van antwoord
GET /todoitems Alle to-do items ophalen Geen Matrix van to-do items
GET /todoitems/complete Voltooide to-do items verkrijgen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen met ID Geen Takenitem
POST /todoitems Een nieuw item toevoegen Actiepunt Takenitem
PUT /todoitems/{id} Een bestaand item bijwerken Actiepunt Geen
DELETE /todoitems/{id}     Een item verwijderen Geen Geen

Voorwaarden

Een API-project maken

  • Start Visual Studio 2022 en selecteer Een nieuw project maken.

  • In het dialoogvenster Een nieuw project aanmaken:

    • Typ Empty in het zoekvak Zoeken naar sjablonen.
    • Selecteer de sjabloon ASP.NET Core Empty en selecteer Volgende.

    Visual Studio Een nieuw project maken

  • Geef het project de naam TodoApi- en selecteer Volgende.

  • In het dialoogvenster Aanvullende informatie:

    • Selecteer .NET 8.0 (Long Term Support)
    • Schakel Gebruik geen instructies op het hoogste niveau uit
    • Selecteer Creëer

    Aanvullende informatie

De code onderzoeken

Het bestand Program.cs bevat de volgende code:

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

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

app.Run();

De voorgaande code:

  • Hiermee maakt u een WebApplicationBuilder en een WebApplication met vooraf geconfigureerde standaardinstellingen.
  • Hiermee maakt u een HTTP GET-eindpunt / dat Hello World!retourneert:

De app uitvoeren

Druk op Ctrl+F5 om uit te voeren zonder het foutopsporingsprogramma.

Visual Studio geeft het volgende dialoogvenster weer:

Dit project is geconfigureerd voor het gebruik van SSL. Als u SSL-waarschuwingen in de browser wilt voorkomen, kunt u ervoor kiezen om het zelfondertekende certificaat te vertrouwen dat IIS Express heeft gegenereerd. Wilt u het IIS Express SSL-certificaat vertrouwen?

Selecteer Ja als u het IIS Express SSL-certificaat vertrouwt.

Het volgende dialoogvenster wordt weergegeven:

beveiligingswaarschuwingsdialoogvenster

Selecteer Ja als u akkoord gaat met het vertrouwen van het ontwikkelingscertificaat.

Zie Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certificaatfoutvoor meer informatie over het vertrouwen van de Firefox-browser.

Visual Studio start de Kestrel webserver en opent een browservenster.

Hello World! wordt weergegeven in de browser. Het bestand Program.cs bevat een minimale maar volledige app.

Sluit het browservenster.

NuGet-pakketten toevoegen

NuGet-pakketten moeten worden toegevoegd ter ondersteuning van de database en diagnostische gegevens die in deze zelfstudie worden gebruikt.

  • Selecteer in het menu ToolsNuGet Package Manager > NuGet-pakketten beheren voor Solution.
  • Selecteer het tabblad Bladeren.
  • Voer Microsoft.EntityFrameworkCore.InMemory- in het zoekvak in en selecteer vervolgens Microsoft.EntityFrameworkCore.InMemory.
  • Vink het selectievakje Project aan in het rechterdeelvenster en selecteer dan Installeren.
  • Volg de voorgaande instructies om het Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore-pakket toe te voegen.

De contextklassen van het model en de database

  • Maak in de projectmap een bestand met de naam Todo.cs met de volgende code:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Met de voorgaande code wordt het model voor deze app gemaakt. Een model is een klasse die gegevens vertegenwoordigt die door de app worden beheerd.

  • Maak een bestand met de naam TodoDb.cs met de volgende code:
using Microsoft.EntityFrameworkCore;

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

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

De voorgaande code definieert de databasecontext, de hoofdklasse die de functionaliteit van Entity Framework voor een gegevensmodel coördineert. Deze klasse is afgeleid van de klasse Microsoft.EntityFrameworkCore.DbContext.

De API-code toevoegen

  • Vervang de inhoud van het bestand Program.cs door de volgende code:
using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

Met de volgende gemarkeerde code wordt de databasecontext toegevoegd aan de afhankelijkheidsinjectie (DI) container en worden databasegerelateerde uitzonderingen weergegeven:

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

De DI-container biedt toegang tot de databasecontext en andere services.

In deze zelfstudie worden Endpoints Explorer en .http-bestanden gebruikt om de API te testen.

Boekingsgegevens testen

Met de volgende code in Program.cs wordt een HTTP POST-eindpunt gemaakt /todoitems waarmee gegevens worden toegevoegd aan de in-memory database:

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

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

Voer de app uit. In de browser wordt een 404-fout weergegeven omdat er geen / eindpunt meer is.

Het POST-eindpunt wordt gebruikt om gegevens toe te voegen aan de app.

  • Selecteer Weergave>Andere Windows>Endpoints Explorer.

  • Klik met de rechtermuisknop op het eindpunt POST en selecteer Aanvraag genereren.

    contextmenu van Endpoints Explorer waarin het menu-item Aanvraag genereren is gemarkeerd.

    Er wordt een nieuw bestand gemaakt in de projectmap met de naam TodoApi.http, met inhoud die vergelijkbaar is met het volgende voorbeeld:

    @TodoApi_HostAddress = https://localhost:7031
    
    Post {{TodoApi_HostAddress}}/todoitems
    
    ###
    
    • Met de eerste regel maakt u een variabele die wordt gebruikt voor alle eindpunten.
    • De volgende regel definieert een POST-aanvraag.
    • De drievoudige hashtag (###) regel is een scheidingsteken voor aanvragen: wat erna komt, is bedoeld voor een andere aanvraag.
  • De POST-aanvraag heeft headers en een hoofdtekst nodig. Als u deze onderdelen van de aanvraag wilt definiëren, voegt u de volgende regels toe direct na de POST-aanvraagregel:

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

    Met de voorgaande code worden een header voor het inhoudstype en een hoofdtekst van een JSON-aanvraag toegevoegd. Het todoApi.http-bestand moet er nu uitzien zoals in het volgende voorbeeld, maar met uw poortnummer:

    @TodoApi_HostAddress = https://localhost:7057
    
    Post {{TodoApi_HostAddress}}/todoitems
    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    
    ###
    
  • Voer de app uit.

  • Selecteer de link Aanvraag verzenden die boven de POST aanvraagregel staat.

    .http-bestandvenster met run link gemarkeerd.

    De POST-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord.

    .http-bestandvenster met reactie van de POST-aanvraag.

De GET-eindpunten onderzoeken

De voorbeeld-app implementeert verschillende GET-eindpunten door MapGetaan te roepen:

API Beschrijving Aanvraagbody Hoofdtekst van antwoord
GET /todoitems Alle to-do artikelen ophalen Geen Matrix van to-do items
GET /todoitems/complete Alle voltooide to-do items ophalen Geen Matrix van to-do items
GET /todoitems/{id} Een item ophalen via ID Geen Takenitem
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());

De GET-eindpunten testen

Test de app door de GET-eindpunten aan te roepen vanuit een browser of met behulp van Endpoints Explorer. De volgende stappen zijn bedoeld voor Endpoints Explorer.

  • Klik in Endpoints Explorermet de rechtermuisknop op het eerste GET--eindpunt en selecteer Aanvraag genereren.

    De volgende inhoud wordt toegevoegd aan het TodoApi.http-bestand:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Selecteer de koppeling Aanvraag verzenden die zich boven de nieuwe GET aanvraagregel bevindt.

    De GET-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord.

  • De hoofdtekst van het antwoord is vergelijkbaar met de volgende JSON:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • Klik in Endpoints Explorermet de rechtermuisknop op het eindpunt /todoitems/{id}GET en selecteer Aanvraag genereren. De volgende inhoud wordt toegevoegd aan het TodoApi.http-bestand:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Vervang {id} door 1.

  • Selecteer de aanvraag verzenden koppeling die zich boven de nieuwe GET-aanvraagregel bevindt.

    De GET-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord.

  • De hoofdtekst van het antwoord is vergelijkbaar met de volgende JSON:

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

Deze app maakt gebruik van een in-memory database. Als de app opnieuw wordt opgestart, retourneert de GET-aanvraag geen gegevens. Als er geen gegevens worden geretourneerd, POST gegevens naar de app en probeer de GET-aanvraag opnieuw.

Retourwaarden

ASP.NET Core het object automatisch serialiseert naar JSON- en schrijft de JSON naar de hoofdtekst van het antwoordbericht. De antwoordcode voor dit retourtype is 200 OK, ervan uitgaande dat er geen onverwerkte uitzonderingen zijn. Niet-verwerkte uitzonderingen worden omgezet in 5xx-fouten.

De retourtypen kunnen een breed scala aan HTTP-statuscodes vertegenwoordigen. GET /todoitems/{id} kan bijvoorbeeld twee verschillende statuswaarden retourneren:

  • Als er geen item overeenkomt met de aangevraagde id, retourneert de methode een 404-statusNotFound foutcode.
  • Anders retourneert de methode 200 met een JSON-antwoordtekst. Het retourneren van item resulteert in een HTTP 200-antwoord.

Het PUT-eindpunt onderzoeken

De voorbeeld-app implementeert één PUT-eindpunt met behulp van 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();
});

Deze methode is vergelijkbaar met de methode MapPost, met uitzondering van HTTP PUT. Een geslaagd antwoord retourneert 204 (Geen inhoud). Volgens de HTTP-specificatie vereist een PUT-aanvraag dat de client de volledige bijgewerkte entiteit verzendt, niet alleen de wijzigingen. Gebruik HTTP PATCH-om gedeeltelijke updates te ondersteunen.

Het PUT-eindpunt testen

In dit voorbeeld wordt een in-memory database gebruikt die telkens wanneer de app wordt gestart, moet worden geïnitialiseerd. Er moet een item in de database staan voordat u een PUT-aanroep uitvoert. Roep GET aan om ervoor te zorgen dat er een item in de database staat voordat u een PUT-aanroep doet.

Werk het to-do item met Id = 1 bij en stel de naam ervan in op "feed fish".

  • Klik in Endpoints Explorermet de rechtermuisknop op het PUT-eindpunt en selecteer Aanvraag genereren.

    De volgende inhoud wordt toegevoegd aan het TodoApi.http-bestand:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Vervang {id} in de PUT-aanvraagregel door 1.

  • Voeg de volgende regels toe direct na de PUT-aanvraagregel:

    Content-Type: application/json
    
    {
      "name": "feed fish",
      "isComplete": false
    }
    

    Met de voorgaande code worden een header voor het inhoudstype en een hoofdtekst van een JSON-aanvraag toegevoegd.

  • Selecteer de koppeling Aanvraag verzenden boven de nieuwe PUT-aanvraagregel.

    De PUT-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord. De hoofdtekst van het antwoord is leeg en de statuscode is 204.

Het DELETE-eindpunt onderzoeken en testen

De voorbeeld-app implementeert één DELETE-eindpunt met behulp van 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();
});
  • Klik in Endpoints Explorermet de rechtermuisknop op het eindpunt DELETE en selecteer Aanvraag genereren.

    Er wordt een DELETE-aanvraag toegevoegd aan TodoApi.http.

  • Vervang {id} in de DELETE-aanvraagregel door 1. De DELETE-aanvraag moet eruitzien als in het volgende voorbeeld:

    DELETE {{TodoApi_HostAddress}}/todoitems/1
    
    ###
    
  • Selecteer de koppeling Aanvraag verzenden voor de DELETE-aanvraag.

    De DELETE-aanvraag wordt verzonden naar de app en het antwoord wordt weergegeven in het deelvenster Antwoord. De hoofdtekst van het antwoord is leeg en de statuscode is 204.

De MapGroup-API gebruiken

De code van de voorbeeld-app herhaalt het todoitems URL-voorvoegsel telkens wanneer er een eindpunt wordt ingesteld. API's hebben vaak groepen eindpunten met een gemeenschappelijk URL-voorvoegsel en de MapGroup methode is beschikbaar om dergelijke groepen te organiseren. Het vermindert terugkerende code en maakt het mogelijk om hele groepen eindpunten aan te passen met één aanroep naar methoden zoals RequireAuthorization en WithMetadata.

Vervang de inhoud van Program.cs door de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

De voorgaande code heeft de volgende wijzigingen:

  • Voegt var todoItems = app.MapGroup("/todoitems"); toe om de groep in te stellen met behulp van het URL-voorvoegsel /todoitems.
  • Hiermee worden alle app.Map<HttpVerb> methoden gewijzigd in todoItems.Map<HttpVerb>.
  • Hiermee verwijdert u het URL-voorvoegsel /todoitems uit de aanroepen van de Map<HttpVerb> methode.

Test de eindpunten om te controleren of ze hetzelfde werken.

De TypedResults-API gebruiken

Het retourneren van TypedResults in plaats van Results heeft verschillende voordelen, waaronder testbaarheid en het automatisch retourneren van de metagegevens van het antwoordtype voor OpenAPI om het eindpunt te beschrijven. Voor meer informatie, zie TypedResults versus Results.

De Map<HttpVerb> methoden kunnen methoden voor routehandlers aanroepen in plaats van lambdas te gebruiken. Als u een voorbeeld wilt zien, werkt u Program.cs bij met de volgende code:

using Microsoft.EntityFrameworkCore;

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

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

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

app.Run();

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

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

    return TypedResults.NotFound();
}

De Map<HttpVerb>-code roept nu methoden aan in plaats van lambdas:

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

Deze methoden retourneren objecten die IResult implementeren en worden gedefinieerd door TypedResults:

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

Eenheidstests kunnen deze methoden aanroepen en testen of ze het juiste type retourneren. Als de methode bijvoorbeeld is GetAllTodos:

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

Code voor eenheidstests kan controleren of een object van het type OK<Todo[]> wordt geretourneerd door de handlermethode. Bijvoorbeeld:

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

Overmatig posten voorkomen

Op dit moment wordt in de voorbeeld-app het hele Todo-object weergegeven. Productie-apps In productietoepassingen wordt vaak een subset van het model gebruikt om de gegevens te beperken die kunnen worden ingevoerd en geretourneerd. Er zijn meerdere redenen achter deze en beveiliging is een belangrijke. De subset van een model wordt meestal aangeduid als een DTO (Data Transfer Object), invoermodel of weergavemodel. DTO- wordt in dit artikel gebruikt.

Een DTO kan worden gebruikt voor het volgende:

  • Voorkom overboeking.
  • Eigenschappen verbergen die clients niet mogen bekijken.
  • Laat sommige eigenschappen weg om de nettolading te verkleinen.
  • Platte objectgrafieken die geneste objecten bevatten. Platgemaakte objectgrafieken kunnen handiger zijn voor clients.

Als u de DTO-benadering wilt demonstreren, werkt u de Todo klasse bij om een geheim veld op te nemen:

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

Het geheime veld moet worden verborgen voor deze app, maar een beheer-app kan ervoor kiezen om het beschikbaar te maken.

Controleer of u het geheime veld kunt versturen en ophalen.

Maak een bestand met de naam TodoItemDTO.cs met de volgende code:

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

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

Vervang de inhoud van het Program.cs-bestand door de volgende code om dit DTO-model te gebruiken:

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

Controleer of u alle velden kunt posten en ophalen, behalve het geheime veld.

Problemen oplossen met het voltooide voorbeeld

Als u een probleem ondervindt dat u niet kunt oplossen, vergelijkt u de code met het voltooide project. Voltooide project downloaden of weergeven (hoe te downloaden).

Volgende stappen

Meer informatie

Raadpleeg met minimale API's