Condividi tramite


Esercitazione: Creare un'API minima con ASP.NET Core

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Di Rick Anderson e Tom Dykstra

Le API minime sono progettata per creare API HTTP con dipendenze minime. Sono ideali per microservizi e app che vogliono includere solo i file, le funzionalità e le dipendenze minimi in ASP.NET Core.

Questa esercitazione illustra le nozioni di base per la creazione di un'API minima con ASP.NET Core. Un altro approccio alla creazione di API in ASP.NET Core consiste nell'usare i controller. Per informazioni sulla scelta tra API minime e API basate su controller, vedere Panoramica delle API. Per un'esercitazione sulla creazione di un progetto API basato su controller che contengono altre funzionalità, vedere Creare un'API Web.

Panoramica

Questa esercitazione consente di creare l'API seguente:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
POST /todoitems Aggiunge un nuovo elemento Elemento attività Elemento attività
PUT /todoitems/{id} Aggiorna un elemento esistente Elemento attività None
DELETE /todoitems/{id}     Elimina un elemento None None

Prerequisiti

Creare un progetto API

  • Avviare Visual Studio 2022 e selezionare Crea un nuovo progetto.

  • Nella finestra di dialogo Crea un nuovo progetto:

    • Immettere Empty nella casella di ricerca Cerca modelli .
    • Selezionare il modello core vuoto ASP.NET e selezionare Avanti.

    Creare un nuovo progetto in Visual Studio

  • Assegnare al progetto il nome TodoApi e selezionare Avanti.

  • Nella finestra di dialogo Informazioni aggiuntive:

    • Selezionare .NET 9.0
    • Deselezionare Non usare istruzioni di primo livello
    • Selezionare Crea.

    Informazioni aggiuntive

Esaminare il codice

Il Program.cs file contiene il codice seguente:

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

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

app.Run();

Il codice precedente:

Eseguire l'app

Premere CTRL+F5 per l'esecuzione senza il debugger.

Visual Studio visualizza la finestra di dialogo seguente:

Questo progetto è configurato per l'uso di SSL. Per evitare avvisi SSL nel browser, è possibile scegliere di considerare attendibile il certificato autofirmato generato da IIS Express. Considerare attendibile il certificato SSL di IIS Express?

Selezionare se si considera attendibile il certificato SSL di IIS Express.

Verrà visualizzata la finestra di dialogo seguente:

Finestra di dialogo Avviso di sicurezza

Selezionare se si accetta di considerare attendibile il certificato di sviluppo.

Per informazioni sull'attendibilità del browser Firefox, vedere Firefox SEC_ERROR_INADEQUATE_KEY_USAGE errore del certificato.

Visual Studio avvia il Kestrel server Web e apre una finestra del browser.

Hello World! viene visualizzato nel browser. Il Program.cs file contiene un'app minima ma completa.

Chiudere la finestra del browser.

Aggiungere i pacchetti NuGet di

I pacchetti NuGet devono essere aggiunti per supportare il database e la diagnostica usati in questa esercitazione.

  • Scegliere NuGet Gestione pacchetti Gestisci pacchetti NuGet per la soluzione dal >Strumenti.
  • Selezionare la scheda Sfoglia.
  • Selezionare Includi versione.
  • Immettere Microsoft.EntityFrameworkCore.InMemory nella casella di ricerca e quindi selezionare Microsoft.EntityFrameworkCore.InMemory.
  • Selezionare la casella di controllo Progetto nel riquadro destro e quindi selezionare Installa.
  • Seguire le istruzioni precedenti per aggiungere il Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacchetto.

Classi di contesto del modello e del database

  • Nella cartella del progetto creare un file denominato Todo.cs con il codice seguente:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Il codice precedente crea il modello per questa app. Un modello è una classe che rappresenta i dati gestiti dall'app.

  • Creare un file denominato TodoDb.cs con il codice seguente:
using Microsoft.EntityFrameworkCore;

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

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

Il codice precedente definisce il contesto del database, ovvero la classe principale che coordina la funzionalità di Entity Framework per un modello di dati. Tale classe deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.

Aggiungere il codice API

  • Sostituire il contenuto del file Program.cs con il codice seguente:
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();

Il codice evidenziato seguente aggiunge il contesto del database al contenitore di inserimento delle dipendenze e consente di visualizzare le eccezioni correlate al database:

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

Il contenitore DI fornisce l'accesso al contesto del database e ad altri servizi.

Questa esercitazione usa Endpoints Explorer e i file con estensione http per testare l'API.

Testare i dati di registrazione

Il codice seguente in Program.cs crea un endpoint /todoitems HTTP POST che aggiunge dati al database in memoria:

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

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

Eseguire l'app. Il browser visualizza un errore 404 perché non è più presente un / endpoint.

L'endpoint POST verrà usato per aggiungere dati all'app.

  • Selezionare >>Explorer.

  • Fare clic con il pulsante destro del mouse sull'endpoint POST e scegliere Genera richiesta.

    Menu di scelta rapida di Esplora endpoint che evidenzia la voce di menu Genera richiesta.

    Viene creato un nuovo file nella cartella di progetto denominata TodoApi.http, con contenuto simile all'esempio seguente:

    @TodoApi_HostAddress = https://localhost:7031
    
    Post {{TodoApi_HostAddress}}/todoitems
    
    ###
    
    • La prima riga crea una variabile usata per tutti gli endpoint.
    • La riga successiva definisce una richiesta POST.
    • La riga triple hashtag (###) è un delimitatore di richiesta: ciò che viene dopo è per una richiesta diversa.
  • La richiesta POST richiede intestazioni e corpo. Per definire le parti della richiesta, aggiungere le righe seguenti immediatamente dopo la riga di richiesta POST:

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

    Il codice precedente aggiunge un'intestazione Content-Type e un corpo della richiesta JSON. Il file TodoApi.http dovrebbe ora essere simile all'esempio seguente, ma con il numero di porta:

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

  • Selezionare il collegamento Invia richiesta sopra la riga della POST richiesta.

    Finestra del file .http con collegamento di esecuzione evidenziato.

    La richiesta POST viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .

    Finestra del file .http con risposta dalla richiesta POST.

Esaminare gli endpoint GET

L'app di esempio implementa diversi endpoint GET chiamando MapGet:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere tutti gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
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());

Testare gli endpoint GET

Testare l'app chiamando gli GET endpoint da un browser o usando Esplora endpoint. I passaggi seguenti sono relativi a Esplora endpoint.

  • In Esplora endpoint fare clic con il pulsante destro del mouse sul primo endpoint GET e scegliere Genera richiesta.

    Al file viene aggiunto il TodoApi.http contenuto seguente:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Selezionare il collegamento Invia richiesta sopra la nuova GET riga di richiesta.

    La richiesta GET viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .

  • Il corpo della risposta è simile al codice JSON seguente:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • In Esplora endpoint fare clic con il pulsante destro del mouse sull'endpoint /todoitems/{id}GET e scegliere Genera richiesta. Al file viene aggiunto il TodoApi.http contenuto seguente:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Sostituisci {id} con 1.

  • Selezionare il collegamento Invia richiesta sopra la nuova riga di richiesta GET.

    La richiesta GET viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .

  • Il corpo della risposta è simile al codice JSON seguente:

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

Questa app usa un database in memoria. Se l'app viene riavviata, la richiesta GET non restituisce dati. Se non vengono restituiti dati, i dati POST nell'app e riprovare la richiesta GET.

Valori restituiti

ASP.NET Core serializza automaticamente l'oggetto su JSON e scrive il codice JSON nel corpo del messaggio di risposta. Il codice di risposta per questo tipo restituito è 200 OK, presupponendo che non siano presenti eccezioni non gestite. Le eccezioni non gestite vengono convertite in errori 5xx.

I tipi restituiti possono rappresentare un'ampia gamma di codici di stato HTTP. Ad esempio, GET /todoitems/{id} può restituire due valori di stato diversi:

  • Se nessun elemento corrisponde all'ID richiesto, il metodo restituisce un codice di errore di statoNotFound 404.
  • In caso contrario, il metodo restituisce il codice 200 con un corpo della risposta JSON. La restituzione di item risulta in una risposta HTTP 200.

Esaminare l'endpoint PUT

L'app di esempio implementa un singolo endpoint PUT usando 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();
});

Questo metodo è simile al metodo , ad eccezione del MapPost fatto che usa HTTP PUT. Una risposta con esito positivo restituisce 204 (nessun contenuto). In base alla specifica HTTP, una richiesta PUT richiede che il client invii l'intera entità aggiornata e non solo le modifiche. Per supportare gli aggiornamenti parziali, usare HTTP PATCH.

Testare l'endpoint PUT

Questo esempio usa un database in memoria che deve essere inizializzato ogni volta che l'app viene avviata. Deve esistere un elemento nel database prima di eseguire una chiamata PUT. Chiamare GET per assicurarsi che nel database sia presente un elemento prima di effettuare una chiamata PUT.

Aggiornare l'elemento attività con Id = 1 e impostarne il nome su "feed fish".

  • In Esplora endpoint fare clic con il pulsante destro del mouse sull'endpoint PUT e selezionare Genera richiesta.

    Al file viene aggiunto il TodoApi.http contenuto seguente:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Nella riga della richiesta PUT sostituire {id} con 1.

  • Aggiungere le righe seguenti subito dopo la riga della richiesta PUT:

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

    Il codice precedente aggiunge un'intestazione Content-Type e un corpo della richiesta JSON.

  • Selezionare il collegamento Invia richiesta sopra la nuova riga di richiesta PUT.

    La richiesta PUT viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta . Il corpo della risposta è vuoto e il codice di stato è 204.

Esaminare e testare l'endpoint DELETE

L'app di esempio implementa un singolo endpoint DELETE usando 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();
});
  • In Esplora endpoint fare clic con il pulsante destro del mouse sull'endpoint DELETE e scegliere Genera richiesta.

    Una richiesta DELETE viene aggiunta a TodoApi.http.

  • Sostituire {id} nella riga di richiesta DELETE con 1. La richiesta DELETE dovrebbe essere simile all'esempio seguente:

    DELETE {{TodoApi_HostAddress}}/todoitems/1
    
    ###
    
  • Selezionare il collegamento Invia richiesta per la richiesta DELETE.

    La richiesta DELETE viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta . Il corpo della risposta è vuoto e il codice di stato è 204.

Usare l'API MapGroup

Il codice dell'app di esempio ripete il todoitems prefisso URL ogni volta che configura un endpoint. Le API hanno spesso gruppi di endpoint con un prefisso URL comune e il MapGroup metodo è disponibile per organizzare tali gruppi. Riduce il codice ripetitivo e consente di personalizzare interi gruppi di endpoint con una singola chiamata a metodi come RequireAuthorization e WithMetadata.

Sostituire il contenuto di Program.cs con il codice seguente:

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

Il codice precedente presenta le modifiche seguenti:

  • Aggiunge var todoItems = app.MapGroup("/todoitems"); per configurare il gruppo usando il prefisso /todoitemsURL .
  • Modifica tutti i app.Map<HttpVerb> metodi in todoItems.Map<HttpVerb>.
  • Rimuove il prefisso /todoitems URL dalle chiamate al Map<HttpVerb> metodo.

Testare gli endpoint per verificare che funzionino allo stesso modo.

Usare l'API TypedResults

La restituzione TypedResults anziché Results presenta diversi vantaggi, tra cui la testabilità e la restituzione automatica dei metadati del tipo di risposta per OpenAPI per descrivere l'endpoint. Per altre informazioni, vedere TypedResults vs Results.

I Map<HttpVerb> metodi possono chiamare metodi del gestore di route anziché usare espressioni lambda. Per visualizzare un esempio, aggiornare Program.cs con il codice seguente:

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

Il Map<HttpVerb> codice chiama ora i metodi anziché le espressioni lambda:

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

Questi metodi restituiscono oggetti che implementano IResult e sono definiti da 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();
}

Gli unit test possono chiamare questi metodi e verificare che restituiscono il tipo corretto. Ad esempio, se il metodo è GetAllTodos:

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

Il codice di unit test può verificare che un oggetto di tipo Ok<Todo[]> venga restituito dal metodo del gestore. Ad esempio:

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

Impedire l'over-post

Attualmente l'app di esempio espone l'intero Todo oggetto. App di produzione Nelle applicazioni di produzione, viene spesso usato un subset del modello per limitare i dati che possono essere inseriti e restituiti. Esistono diversi motivi alla base di questa situazione e la sicurezza è una delle principali. Il subset di un modello viene in genere definito DTO (Data Transfer Object), modello di input o modello di visualizzazione. DTO viene usato in questo articolo.

Un DTO può essere usato per:

  • Impedire l'over-post.
  • Nascondere le proprietà che i client non devono visualizzare.
  • Omettere alcune proprietà per ridurre le dimensioni del payload.
  • Appiattire gli oggetti grafici che contengono oggetti annidati. Gli oggetti grafici appiattiti possono essere più pratici per i client.

Per illustrare l'approccio DTO, aggiornare la Todo classe in modo da includere un campo segreto:

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

Il campo segreto deve essere nascosto da questa app, ma un'app amministrativa potrebbe scegliere di esporla.

Verificare che sia possibile pubblicare e ottenere il campo segreto.

Creare un file denominato TodoItemDTO.cs con il codice seguente:

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

Sostituire il contenuto del Program.cs file con il codice seguente per usare questo modello DTO:

using Microsoft.EntityFrameworkCore;

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

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

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

app.Run();

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

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

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

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

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

    todoItemDTO = new TodoItemDTO(todoItem);

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

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

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

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

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

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

    return TypedResults.NotFound();
}

Verificare che sia possibile pubblicare e ottenere tutti i campi ad eccezione del campo segreto.

Risoluzione dei problemi con l'esempio completato

Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Visualizzare o scaricare il progetto completato (come scaricare).

Passaggi successivi

Altre informazioni

Vedere Informazioni di riferimento rapido sulle API minime

Le API minime sono progettata per creare API HTTP con dipendenze minime. Sono ideali per microservizi e app che vogliono includere solo i file, le funzionalità e le dipendenze minimi in ASP.NET Core.

Questa esercitazione illustra le nozioni di base per la creazione di un'API minima con ASP.NET Core. Un altro approccio alla creazione di API in ASP.NET Core consiste nell'usare i controller. Per informazioni sulla scelta tra API minime e API basate su controller, vedere Panoramica delle API. Per un'esercitazione sulla creazione di un progetto API basato su controller che contengono altre funzionalità, vedere Creare un'API Web.

Panoramica

Questa esercitazione consente di creare l'API seguente:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
POST /todoitems Aggiunge un nuovo elemento Elemento attività Elemento attività
PUT /todoitems/{id} Aggiorna un elemento esistente Elemento attività None
DELETE /todoitems/{id}     Elimina un elemento None None

Prerequisiti

Creare un progetto API

  • Avviare Visual Studio 2022 e selezionare Crea un nuovo progetto.

  • Nella finestra di dialogo Crea un nuovo progetto:

    • Immettere Empty nella casella di ricerca Cerca modelli .
    • Selezionare il modello core vuoto ASP.NET e selezionare Avanti.

    Creare un nuovo progetto in Visual Studio

  • Assegnare al progetto il nome TodoApi e selezionare Avanti.

  • Nella finestra di dialogo Informazioni aggiuntive:

    • Selezionare .NET 7.0
    • Deselezionare Non usare istruzioni di primo livello
    • Selezionare Crea.

    Informazioni aggiuntive

Esaminare il codice

Il Program.cs file contiene il codice seguente:

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

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

app.Run();

Il codice precedente:

Eseguire l'app

Premere CTRL+F5 per l'esecuzione senza il debugger.

Visual Studio visualizza la finestra di dialogo seguente:

Questo progetto è configurato per l'uso di SSL. Per evitare avvisi SSL nel browser, è possibile scegliere di considerare attendibile il certificato autofirmato generato da IIS Express. Considerare attendibile il certificato SSL di IIS Express?

Selezionare se si considera attendibile il certificato SSL di IIS Express.

Verrà visualizzata la finestra di dialogo seguente:

Finestra di dialogo Avviso di sicurezza

Selezionare se si accetta di considerare attendibile il certificato di sviluppo.

Per informazioni sull'attendibilità del browser Firefox, vedere Firefox SEC_ERROR_INADEQUATE_KEY_USAGE errore del certificato.

Visual Studio avvia il Kestrel server Web e apre una finestra del browser.

Hello World! viene visualizzato nel browser. Il Program.cs file contiene un'app minima ma completa.

Aggiungere i pacchetti NuGet di

I pacchetti NuGet devono essere aggiunti per supportare il database e la diagnostica usati in questa esercitazione.

  • Scegliere NuGet Gestione pacchetti Gestisci pacchetti NuGet per la soluzione dal >Strumenti.
  • Selezionare la scheda Sfoglia.
  • Immettere Microsoft.EntityFrameworkCore.InMemory nella casella di ricerca e quindi selezionare Microsoft.EntityFrameworkCore.InMemory.
  • Selezionare la casella di controllo Progetto nel riquadro destro.
  • Nell'elenco a discesa Versione selezionare l'ultima versione 7 disponibile, ad esempio 7.0.17, e quindi selezionare Installa.
  • Seguire le istruzioni precedenti per aggiungere il Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacchetto con la versione 7 più recente disponibile.

Classi di contesto del modello e del database

Nella cartella del progetto creare un file denominato Todo.cs con il codice seguente:

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

Il codice precedente crea il modello per questa app. Un modello è una classe che rappresenta i dati gestiti dall'app.

Creare un file denominato TodoDb.cs con il codice seguente:

using Microsoft.EntityFrameworkCore;

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

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

Il codice precedente definisce il contesto del database, ovvero la classe principale che coordina la funzionalità di Entity Framework per un modello di dati. Tale classe deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.

Aggiungere il codice API

Sostituire il contenuto del file Program.cs con il codice seguente:

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

Il codice evidenziato seguente aggiunge il contesto del database al contenitore di inserimento delle dipendenze e consente di visualizzare le eccezioni correlate al database:

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

Il contenitore DI fornisce l'accesso al contesto del database e ad altri servizi.

Creare un'interfaccia utente di test dell'API con Swagger

Sono disponibili molti strumenti di test dell'API Web tra cui scegliere ed è possibile seguire questa procedura di test introduttiva dell'API con il proprio strumento preferito.

Questa esercitazione usa il pacchetto .NET NSwag.AspNetCore, che integra gli strumenti Swagger per generare un'interfaccia utente di test aderendo alla specifica OpenAPI:

  • NSwag: libreria .NET che integra Swagger direttamente nelle applicazioni ASP.NET Core, fornendo middleware e configurazione.
  • Swagger: set di strumenti open source come OpenAPIGenerator e SwaggerUI che generano pagine di test api che seguono la specifica OpenAPI.
  • Specifica OpenAPI: documento che descrive le funzionalità dell'API, in base alle annotazioni XML e attributi all'interno dei controller e dei modelli.

Per altre informazioni sull'uso di OpenAPI e NSwag con ASP.NET, vedere la documentazione dell'API Web di ASP.NET Core con Swagger/OpenAPI.

Installare gli strumenti di Swagger

  • Esegui questo comando:

    dotnet add package NSwag.AspNetCore
    

Il comando precedente aggiunge il pacchetto NSwag.AspNetCore , che contiene strumenti per generare documenti e interfaccia utente di Swagger.

Configurare il middleware Swagger

  • Aggiungere il codice evidenziato seguente prima app di essere definito nella riga 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();
    

Nel codice precedente:

  • builder.Services.AddEndpointsApiExplorer();: abilita Esplora API, ovvero un servizio che fornisce metadati sull'API HTTP. Esplora API viene usato da Swagger per generare il documento Swagger.

  • builder.Services.AddOpenApiDocument(config => {...});: aggiunge il generatore di documenti OpenAPI di Swagger ai servizi dell'applicazione e lo configura per fornire altre informazioni sull'API, ad esempio il titolo e la versione. Per informazioni dettagliate sull'API più affidabile, vedere Introduzione a NSwag e ASP.NET Core

  • Aggiungere il codice evidenziato seguente alla riga successiva dopo app la definizione nella riga 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";
        });
    }
    

    Il codice precedente abilita il middleware Swagger per gestire il documento JSON generato e l'interfaccia utente di Swagger. Swagger è abilitato solo in un ambiente di sviluppo. L'abilitazione di Swagger in un ambiente di produzione potrebbe esporre dettagli potenzialmente sensibili sulla struttura e l'implementazione dell'API.

Testare i dati di registrazione

Il codice seguente in Program.cs crea un endpoint /todoitems HTTP POST che aggiunge dati al database in memoria:

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

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

Eseguire l'app. Il browser visualizza un errore 404 perché non è più presente un / endpoint.

L'endpoint POST verrà usato per aggiungere dati all'app.

  • Con l'app ancora in esecuzione, nel browser passare a https://localhost:<port>/swagger per visualizzare la pagina di test dell'API generata da Swagger.

    Pagina di test dell'API generata da Swagger

  • Nella pagina di test dell'API Swagger selezionare Post /todoitems Try it (Pubblica /todoitems>Prova).

  • Si noti che il campo Corpo della richiesta contiene un formato di esempio generato che riflette i parametri per l'API.

  • Nel corpo della richiesta immettere JSON per un elemento attività, senza specificare il parametro facoltativo id:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Seleziona Execute.

    Swagger con Post

Swagger fornisce un riquadro Risposte sotto il pulsante Esegui .

Swagger con Post response

Si notino alcuni dei dettagli utili:

  • cURL: Swagger fornisce un comando cURL di esempio nella sintassi Unix/Linux, che può essere eseguito nella riga di comando con qualsiasi shell bash che usa la sintassi Unix/Linux, incluso Git Bash da Git per Windows.
  • URL richiesta: rappresentazione semplificata della richiesta HTTP effettuata dal codice JavaScript dell'interfaccia utente di Swagger per la chiamata API. Le richieste effettive possono includere dettagli come intestazioni e parametri di query e un corpo della richiesta.
  • Risposta del server: include il corpo e le intestazioni della risposta. Il corpo della risposta mostra che è id stato impostato su 1.
  • Codice di risposta: è stato restituito un codice di stato 201 HTTP che indica che la richiesta è stata elaborata correttamente e ha generato la creazione di una nuova risorsa.

Esaminare gli endpoint GET

L'app di esempio implementa diversi endpoint GET chiamando MapGet:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere tutti gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
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());

Testare gli endpoint GET

Testare l'app chiamando gli endpoint da un browser o da Swagger.

  • In Swagger selezionare GET /todoitems>>Execute (Esegui).

  • In alternativa, chiamare GET /todoitems da un browser immettendo l'URI http://localhost:<port>/todoitems. Ad esempio, http://localhost:5001/todoitems

La chiamata a GET /todoitems produce una risposta simile alla seguente:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Chiamare GET /todoitems/{id} in Swagger per restituire dati da un ID specifico:

    • Selezionare GET /todoitems>Try it (Prova).
    • Impostare il campo ID su 1 e selezionare Esegui.
  • In alternativa, chiamare GET /todoitems da un browser immettendo l'URI https://localhost:<port>/todoitems/1. Ad esempio, https://localhost:5001/todoitems/1

  • La risposta è simile alla seguente:

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

Questa app usa un database in memoria. Se l'app viene riavviata, la richiesta GET non restituisce dati. Se non vengono restituiti dati, i dati POST nell'app e riprovare la richiesta GET.

Valori restituiti

ASP.NET Core serializza automaticamente l'oggetto su JSON e scrive il codice JSON nel corpo del messaggio di risposta. Il codice di risposta per questo tipo restituito è 200 OK, presupponendo che non siano presenti eccezioni non gestite. Le eccezioni non gestite vengono convertite in errori 5xx.

I tipi restituiti possono rappresentare un'ampia gamma di codici di stato HTTP. Ad esempio, GET /todoitems/{id} può restituire due valori di stato diversi:

  • Se nessun elemento corrisponde all'ID richiesto, il metodo restituisce un codice di errore di statoNotFound 404.
  • In caso contrario, il metodo restituisce il codice 200 con un corpo della risposta JSON. La restituzione di item risulta in una risposta HTTP 200.

Esaminare l'endpoint PUT

L'app di esempio implementa un singolo endpoint PUT usando 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();
});

Questo metodo è simile al metodo , ad eccezione del MapPost fatto che usa HTTP PUT. Una risposta con esito positivo restituisce 204 (nessun contenuto). In base alla specifica HTTP, una richiesta PUT richiede che il client invii l'intera entità aggiornata e non solo le modifiche. Per supportare gli aggiornamenti parziali, usare HTTP PATCH.

Testare l'endpoint PUT

Questo esempio usa un database in memoria che deve essere inizializzato ogni volta che l'app viene avviata. Deve esistere un elemento nel database prima di eseguire una chiamata PUT. Chiamare GET per assicurarsi che nel database sia presente un elemento prima di effettuare una chiamata PUT.

Aggiornare l'elemento attività con Id = 1 e impostarne il nome su "feed fish".

Usare Swagger per inviare una richiesta PUT:

  • Selezionare Inserisci /todoitems/{id}>Prova.

  • Impostare il campo ID su 1.

  • Impostare il corpo della richiesta sul codice JSON seguente:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Seleziona Execute.

Esaminare e testare l'endpoint DELETE

L'app di esempio implementa un singolo endpoint DELETE usando 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();
});

Usare Swagger per inviare una richiesta DELETE:

  • Selezionare DELETE /todoitems/{id}Try it out(Elimina /todoitems/{id}>Prova.

  • Impostare il campo ID su 1 e selezionare Esegui.

    La richiesta DELETE viene inviata all'app e la risposta viene visualizzata nel riquadro Risposte . Il corpo della risposta è vuoto e il codice di stato della risposta del server è 204.

Usare l'API MapGroup

Il codice dell'app di esempio ripete il todoitems prefisso URL ogni volta che configura un endpoint. Le API hanno spesso gruppi di endpoint con un prefisso URL comune e il MapGroup metodo è disponibile per organizzare tali gruppi. Riduce il codice ripetitivo e consente di personalizzare interi gruppi di endpoint con una singola chiamata a metodi come RequireAuthorization e WithMetadata.

Sostituire il contenuto di Program.cs con il codice seguente:

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

Il codice precedente presenta le modifiche seguenti:

  • Aggiunge var todoItems = app.MapGroup("/todoitems"); per configurare il gruppo usando il prefisso /todoitemsURL .
  • Modifica tutti i app.Map<HttpVerb> metodi in todoItems.Map<HttpVerb>.
  • Rimuove il prefisso /todoitems URL dalle chiamate al Map<HttpVerb> metodo.

Testare gli endpoint per verificare che funzionino allo stesso modo.

Usare l'API TypedResults

La restituzione TypedResults anziché Results presenta diversi vantaggi, tra cui la testabilità e la restituzione automatica dei metadati del tipo di risposta per OpenAPI per descrivere l'endpoint. Per altre informazioni, vedere TypedResults vs Results.

I Map<HttpVerb> metodi possono chiamare metodi del gestore di route anziché usare espressioni lambda. Per visualizzare un esempio, aggiornare Program.cs con il codice seguente:

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

Il Map<HttpVerb> codice chiama ora i metodi anziché le espressioni lambda:

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

Questi metodi restituiscono oggetti che implementano IResult e sono definiti da 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();
}

Gli unit test possono chiamare questi metodi e verificare che restituiscono il tipo corretto. Ad esempio, se il metodo è GetAllTodos:

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

Il codice di unit test può verificare che un oggetto di tipo Ok<Todo[]> venga restituito dal metodo del gestore. Ad esempio:

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

Impedire l'over-post

Attualmente l'app di esempio espone l'intero Todo oggetto. App di produzione Nelle applicazioni di produzione, viene spesso usato un subset del modello per limitare i dati che possono essere inseriti e restituiti. Esistono diversi motivi alla base di questa situazione e la sicurezza è una delle principali. Il subset di un modello viene in genere definito DTO (Data Transfer Object), modello di input o modello di visualizzazione. DTO viene usato in questo articolo.

Un DTO può essere usato per:

  • Impedire l'over-post.
  • Nascondere le proprietà che i client non devono visualizzare.
  • Omettere alcune proprietà per ridurre le dimensioni del payload.
  • Appiattire gli oggetti grafici che contengono oggetti annidati. Gli oggetti grafici appiattiti possono essere più pratici per i client.

Per illustrare l'approccio DTO, aggiornare la Todo classe in modo da includere un campo segreto:

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

Il campo segreto deve essere nascosto da questa app, ma un'app amministrativa potrebbe scegliere di esporla.

Verificare che sia possibile pubblicare e ottenere il campo segreto.

Creare un file denominato TodoItemDTO.cs con il codice seguente:

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

Sostituire il contenuto del Program.cs file con il codice seguente per usare questo modello DTO:

using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

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

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

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


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

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

Verificare che sia possibile pubblicare e ottenere tutti i campi ad eccezione del campo segreto.

Risoluzione dei problemi con l'esempio completato

Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Visualizzare o scaricare il progetto completato (come scaricare).

Passaggi successivi

Altre informazioni

Vedere Informazioni di riferimento rapido sulle API minime

Le API minime sono progettata per creare API HTTP con dipendenze minime. Sono ideali per microservizi e app che vogliono includere solo i file, le funzionalità e le dipendenze minimi in ASP.NET Core.

Questa esercitazione illustra le nozioni di base per la creazione di un'API minima con ASP.NET Core. Un altro approccio alla creazione di API in ASP.NET Core consiste nell'usare i controller. Per informazioni sulla scelta tra API minime e API basate su controller, vedere Panoramica delle API. Per un'esercitazione sulla creazione di un progetto API basato su controller che contengono altre funzionalità, vedere Creare un'API Web.

Panoramica

Questa esercitazione consente di creare l'API seguente:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
POST /todoitems Aggiunge un nuovo elemento Elemento attività Elemento attività
PUT /todoitems/{id} Aggiorna un elemento esistente Elemento attività None
DELETE /todoitems/{id}     Elimina un elemento None None

Prerequisiti

Creare un progetto API

  • Avviare Visual Studio 2022 e selezionare Crea un nuovo progetto.

  • Nella finestra di dialogo Crea un nuovo progetto:

    • Immettere Empty nella casella di ricerca Cerca modelli .
    • Selezionare il modello core vuoto ASP.NET e selezionare Avanti.

    Creare un nuovo progetto in Visual Studio

  • Assegnare al progetto il nome TodoApi e selezionare Avanti.

  • Nella finestra di dialogo Informazioni aggiuntive:

    • Selezionare .NET 6.0
    • Deselezionare Non usare istruzioni di primo livello
    • Selezionare Crea.

Esaminare il codice

Il Program.cs file contiene il codice seguente:

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

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

app.Run();

Il codice precedente:

Eseguire l'app

Premere CTRL+F5 per l'esecuzione senza il debugger.

Visual Studio visualizza la finestra di dialogo seguente:

Questo progetto è configurato per l'uso di SSL. Per evitare avvisi SSL nel browser, è possibile scegliere di considerare attendibile il certificato autofirmato generato da IIS Express. Considerare attendibile il certificato SSL di IIS Express?

Selezionare se si considera attendibile il certificato SSL di IIS Express.

Verrà visualizzata la finestra di dialogo seguente:

Finestra di dialogo Avviso di sicurezza

Selezionare se si accetta di considerare attendibile il certificato di sviluppo.

Per informazioni sull'attendibilità del browser Firefox, vedere Firefox SEC_ERROR_INADEQUATE_KEY_USAGE errore del certificato.

Visual Studio avvia il Kestrel server Web e apre una finestra del browser.

Hello World! viene visualizzato nel browser. Il Program.cs file contiene un'app minima ma completa.

Aggiungere i pacchetti NuGet di

I pacchetti NuGet devono essere aggiunti per supportare il database e la diagnostica usati in questa esercitazione.

  • Scegliere NuGet Gestione pacchetti Gestisci pacchetti NuGet per la soluzione dal >Strumenti.
  • Selezionare la scheda Sfoglia.
  • Immettere Microsoft.EntityFrameworkCore.InMemory nella casella di ricerca e quindi selezionare Microsoft.EntityFrameworkCore.InMemory.
  • Selezionare la casella di controllo Progetto nel riquadro destro.
  • Nell'elenco a discesa Versione selezionare l'ultima versione 7 disponibile, ad esempio 6.0.28, e quindi selezionare Installa.
  • Seguire le istruzioni precedenti per aggiungere il Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacchetto con la versione 7 più recente disponibile.

Classi di contesto del modello e del database

Nella cartella del progetto creare un file denominato Todo.cs con il codice seguente:

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

Il codice precedente crea il modello per questa app. Un modello è una classe che rappresenta i dati gestiti dall'app.

Creare un file denominato TodoDb.cs con il codice seguente:

using Microsoft.EntityFrameworkCore;

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

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

Il codice precedente definisce il contesto del database, ovvero la classe principale che coordina la funzionalità di Entity Framework per un modello di dati. Tale classe deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.

Aggiungere il codice API

Sostituire il contenuto del file Program.cs con il codice seguente:

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

Il codice evidenziato seguente aggiunge il contesto del database al contenitore di inserimento delle dipendenze e consente di visualizzare le eccezioni correlate al database:

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

Il contenitore DI fornisce l'accesso al contesto del database e ad altri servizi.

Creare un'interfaccia utente di test dell'API con Swagger

Sono disponibili molti strumenti di test dell'API Web tra cui scegliere ed è possibile seguire questa procedura di test introduttiva dell'API con il proprio strumento preferito.

Questa esercitazione usa il pacchetto .NET NSwag.AspNetCore, che integra gli strumenti Swagger per generare un'interfaccia utente di test aderendo alla specifica OpenAPI:

  • NSwag: libreria .NET che integra Swagger direttamente nelle applicazioni ASP.NET Core, fornendo middleware e configurazione.
  • Swagger: set di strumenti open source come OpenAPIGenerator e SwaggerUI che generano pagine di test api che seguono la specifica OpenAPI.
  • Specifica OpenAPI: documento che descrive le funzionalità dell'API, in base alle annotazioni XML e attributi all'interno dei controller e dei modelli.

Per altre informazioni sull'uso di OpenAPI e NSwag con ASP.NET, vedere la documentazione dell'API Web di ASP.NET Core con Swagger/OpenAPI.

Installare gli strumenti di Swagger

  • Esegui questo comando:

    dotnet add package NSwag.AspNetCore
    

Il comando precedente aggiunge il pacchetto NSwag.AspNetCore , che contiene strumenti per generare documenti e interfaccia utente di Swagger.

Configurare il middleware Swagger

  • In Program.cs aggiungere le istruzioni seguenti using nella parte superiore:

    using NSwag.AspNetCore;
    
  • Aggiungere il codice evidenziato seguente prima app di essere definito nella riga 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();
    

Nel codice precedente:

  • builder.Services.AddEndpointsApiExplorer();: abilita Esplora API, ovvero un servizio che fornisce metadati sull'API HTTP. Esplora API viene usato da Swagger per generare il documento Swagger.

  • builder.Services.AddOpenApiDocument(config => {...});: aggiunge il generatore di documenti OpenAPI di Swagger ai servizi dell'applicazione e lo configura per fornire altre informazioni sull'API, ad esempio il titolo e la versione. Per informazioni dettagliate sull'API più affidabile, vedere Introduzione a NSwag e ASP.NET Core

  • Aggiungere il codice evidenziato seguente alla riga successiva dopo app la definizione nella riga 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";
        });
    }
    
    

    Il codice precedente abilita il middleware Swagger per gestire il documento JSON generato e l'interfaccia utente di Swagger. Swagger è abilitato solo in un ambiente di sviluppo. L'abilitazione di Swagger in un ambiente di produzione potrebbe esporre dettagli potenzialmente sensibili sulla struttura e l'implementazione dell'API.

Testare i dati di registrazione

Il codice seguente in Program.cs crea un endpoint /todoitems HTTP POST che aggiunge dati al database in memoria:

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

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

Eseguire l'app. Il browser visualizza un errore 404 perché non è più presente un / endpoint.

L'endpoint POST verrà usato per aggiungere dati all'app.

  • Con l'app ancora in esecuzione, nel browser passare a https://localhost:<port>/swagger per visualizzare la pagina di test dell'API generata da Swagger.

    Pagina di test dell'API generata da Swagger

  • Nella pagina di test dell'API Swagger selezionare Post /todoitems Try it (Pubblica /todoitems>Prova).

  • Si noti che il campo Corpo della richiesta contiene un formato di esempio generato che riflette i parametri per l'API.

  • Nel corpo della richiesta immettere JSON per un elemento attività, senza specificare il parametro facoltativo id:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Seleziona Execute.

    Swagger con dati Post

Swagger fornisce un riquadro Risposte sotto il pulsante Esegui .

Riquadro Swagger con post resonse

Si notino alcuni dei dettagli utili:

  • cURL: Swagger fornisce un comando cURL di esempio nella sintassi Unix/Linux, che può essere eseguito nella riga di comando con qualsiasi shell bash che usa la sintassi Unix/Linux, incluso Git Bash da Git per Windows.
  • URL richiesta: rappresentazione semplificata della richiesta HTTP effettuata dal codice JavaScript dell'interfaccia utente di Swagger per la chiamata API. Le richieste effettive possono includere dettagli come intestazioni e parametri di query e un corpo della richiesta.
  • Risposta del server: include il corpo e le intestazioni della risposta. Il corpo della risposta mostra che è id stato impostato su 1.
  • Codice di risposta: è stato restituito un codice di stato 201 HTTP che indica che la richiesta è stata elaborata correttamente e ha generato la creazione di una nuova risorsa.

Esaminare gli endpoint GET

L'app di esempio implementa diversi endpoint GET chiamando MapGet:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere tutti gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
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());

Testare gli endpoint GET

Testare l'app chiamando gli endpoint da un browser o da Swagger.

  • In Swagger selezionare GET /todoitems>>Execute (Esegui).

  • In alternativa, chiamare GET /todoitems da un browser immettendo l'URI http://localhost:<port>/todoitems. Ad esempio, http://localhost:5001/todoitems

La chiamata a GET /todoitems produce una risposta simile alla seguente:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Chiamare GET /todoitems/{id} in Swagger per restituire dati da un ID specifico:

    • Selezionare GET /todoitems>Try it (Prova).
    • Impostare il campo ID su 1 e selezionare Esegui.
  • In alternativa, chiamare GET /todoitems da un browser immettendo l'URI https://localhost:<port>/todoitems/1. Ad esempio, ad esempio, https://localhost:5001/todoitems/1

  • La risposta è simile alla seguente:

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

Questa app usa un database in memoria. Se l'app viene riavviata, la richiesta GET non restituisce dati. Se non vengono restituiti dati, i dati POST nell'app e riprovare la richiesta GET.

Valori restituiti

ASP.NET Core serializza automaticamente l'oggetto su JSON e scrive il codice JSON nel corpo del messaggio di risposta. Il codice di risposta per questo tipo restituito è 200 OK, presupponendo che non siano presenti eccezioni non gestite. Le eccezioni non gestite vengono convertite in errori 5xx.

I tipi restituiti possono rappresentare un'ampia gamma di codici di stato HTTP. Ad esempio, GET /todoitems/{id} può restituire due valori di stato diversi:

  • Se nessun elemento corrisponde all'ID richiesto, il metodo restituisce un codice di errore di statoNotFound 404.
  • In caso contrario, il metodo restituisce il codice 200 con un corpo della risposta JSON. La restituzione di item risulta in una risposta HTTP 200.

Esaminare l'endpoint PUT

L'app di esempio implementa un singolo endpoint PUT usando 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();
});

Questo metodo è simile al metodo , ad eccezione del MapPost fatto che usa HTTP PUT. Una risposta con esito positivo restituisce 204 (nessun contenuto). In base alla specifica HTTP, una richiesta PUT richiede che il client invii l'intera entità aggiornata e non solo le modifiche. Per supportare gli aggiornamenti parziali, usare HTTP PATCH.

Testare l'endpoint PUT

Questo esempio usa un database in memoria che deve essere inizializzato ogni volta che l'app viene avviata. Deve esistere un elemento nel database prima di eseguire una chiamata PUT. Chiamare GET per assicurarsi che nel database sia presente un elemento prima di effettuare una chiamata PUT.

Aggiornare l'elemento attività con Id = 1 e impostarne il nome su "feed fish".

Usare Swagger per inviare una richiesta PUT:

  • Selezionare Inserisci /todoitems/{id}>Prova.

  • Impostare il campo ID su 1.

  • Impostare il corpo della richiesta sul codice JSON seguente:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Seleziona Execute.

Esaminare e testare l'endpoint DELETE

L'app di esempio implementa un singolo endpoint DELETE usando 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();
});

Usare Swagger per inviare una richiesta DELETE:

  • Selezionare DELETE /todoitems/{id}Try it out(Elimina /todoitems/{id}>Prova.

  • Impostare il campo ID su 1 e selezionare Esegui.

    La richiesta DELETE viene inviata all'app e la risposta viene visualizzata nel riquadro Risposte . Il corpo della risposta è vuoto e il codice di stato della risposta del server è 204.

Impedire l'over-post

Attualmente l'app di esempio espone l'intero Todo oggetto. App di produzione Nelle applicazioni di produzione, viene spesso usato un subset del modello per limitare i dati che possono essere inseriti e restituiti. Esistono diversi motivi alla base di questa situazione e la sicurezza è una delle principali. Il subset di un modello viene in genere definito DTO (Data Transfer Object), modello di input o modello di visualizzazione. DTO viene usato in questo articolo.

Un DTO può essere usato per:

  • Impedire l'over-post.
  • Nascondere le proprietà che i client non devono visualizzare.
  • Omettere alcune proprietà per ridurre le dimensioni del payload.
  • Appiattire gli oggetti grafici che contengono oggetti annidati. Gli oggetti grafici appiattiti possono essere più pratici per i client.

Per illustrare l'approccio DTO, aggiornare la Todo classe in modo da includere un campo segreto:

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

Il campo segreto deve essere nascosto da questa app, ma un'app amministrativa potrebbe scegliere di esporla.

Verificare che sia possibile pubblicare e ottenere il campo segreto.

Creare un file denominato TodoItemDTO.cs con il codice seguente:

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

Sostituire il contenuto del Program.cs file con il codice seguente per usare questo modello DTO:

using Microsoft.EntityFrameworkCore;

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

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

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

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

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

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

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

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

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

    await db.SaveChangesAsync();

    return Results.NoContent();
});

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

    return Results.NotFound();
});

app.Run();

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

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

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


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

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

Verificare che sia possibile pubblicare e ottenere tutti i campi ad eccezione del campo segreto.

Testare l'API minima

Per un esempio di test di un'app per le API minima, vedere questo esempio di GitHub.

Pubblicare in Azure

Per informazioni sulla distribuzione in Azure, vedere Avvio rapido: Distribuire un'app Web ASP.NET.

Risorse aggiuntive

Le API minime sono progettata per creare API HTTP con dipendenze minime. Sono ideali per microservizi e app che vogliono includere solo i file, le funzionalità e le dipendenze minimi in ASP.NET Core.

Questa esercitazione illustra le nozioni di base per la creazione di un'API minima con ASP.NET Core. Un altro approccio alla creazione di API in ASP.NET Core consiste nell'usare i controller. Per informazioni sulla scelta tra API minime e API basate su controller, vedere Panoramica delle API. Per un'esercitazione sulla creazione di un progetto API basato su controller che contengono altre funzionalità, vedere Creare un'API Web.

Panoramica

Questa esercitazione consente di creare l'API seguente:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
POST /todoitems Aggiunge un nuovo elemento Elemento attività Elemento attività
PUT /todoitems/{id} Aggiorna un elemento esistente Elemento attività None
DELETE /todoitems/{id}     Elimina un elemento None None

Prerequisiti

Creare un progetto API

  • Avviare Visual Studio 2022 e selezionare Crea un nuovo progetto.

  • Nella finestra di dialogo Crea un nuovo progetto:

    • Immettere Empty nella casella di ricerca Cerca modelli .
    • Selezionare il modello core vuoto ASP.NET e selezionare Avanti.

    Creare un nuovo progetto in Visual Studio

  • Assegnare al progetto il nome TodoApi e selezionare Avanti.

  • Nella finestra di dialogo Informazioni aggiuntive:

    • Selezionare .NET 8.0 (supporto a lungo termine)
    • Deselezionare Non usare istruzioni di primo livello
    • Selezionare Crea.

    Informazioni aggiuntive

Esaminare il codice

Il Program.cs file contiene il codice seguente:

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

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

app.Run();

Il codice precedente:

Eseguire l'app

Premere CTRL+F5 per l'esecuzione senza il debugger.

Visual Studio visualizza la finestra di dialogo seguente:

Questo progetto è configurato per l'uso di SSL. Per evitare avvisi SSL nel browser, è possibile scegliere di considerare attendibile il certificato autofirmato generato da IIS Express. Considerare attendibile il certificato SSL di IIS Express?

Selezionare se si considera attendibile il certificato SSL di IIS Express.

Verrà visualizzata la finestra di dialogo seguente:

Finestra di dialogo Avviso di sicurezza

Selezionare se si accetta di considerare attendibile il certificato di sviluppo.

Per informazioni sull'attendibilità del browser Firefox, vedere Firefox SEC_ERROR_INADEQUATE_KEY_USAGE errore del certificato.

Visual Studio avvia il Kestrel server Web e apre una finestra del browser.

Hello World! viene visualizzato nel browser. Il Program.cs file contiene un'app minima ma completa.

Chiudere la finestra del browser.

Aggiungere i pacchetti NuGet di

I pacchetti NuGet devono essere aggiunti per supportare il database e la diagnostica usati in questa esercitazione.

  • Scegliere NuGet Gestione pacchetti Gestisci pacchetti NuGet per la soluzione dal >Strumenti.
  • Selezionare la scheda Sfoglia.
  • Immettere Microsoft.EntityFrameworkCore.InMemory nella casella di ricerca e quindi selezionare Microsoft.EntityFrameworkCore.InMemory.
  • Selezionare la casella di controllo Progetto nel riquadro destro e quindi selezionare Installa.
  • Seguire le istruzioni precedenti per aggiungere il Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacchetto.

Classi di contesto del modello e del database

  • Nella cartella del progetto creare un file denominato Todo.cs con il codice seguente:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Il codice precedente crea il modello per questa app. Un modello è una classe che rappresenta i dati gestiti dall'app.

  • Creare un file denominato TodoDb.cs con il codice seguente:
using Microsoft.EntityFrameworkCore;

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

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

Il codice precedente definisce il contesto del database, ovvero la classe principale che coordina la funzionalità di Entity Framework per un modello di dati. Tale classe deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.

Aggiungere il codice API

  • Sostituire il contenuto del file Program.cs con il codice seguente:
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();

Il codice evidenziato seguente aggiunge il contesto del database al contenitore di inserimento delle dipendenze e consente di visualizzare le eccezioni correlate al database:

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

Il contenitore DI fornisce l'accesso al contesto del database e ad altri servizi.

Questa esercitazione usa Endpoints Explorer e i file con estensione http per testare l'API.

Testare i dati di registrazione

Il codice seguente in Program.cs crea un endpoint /todoitems HTTP POST che aggiunge dati al database in memoria:

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

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

Eseguire l'app. Il browser visualizza un errore 404 perché non è più presente un / endpoint.

L'endpoint POST verrà usato per aggiungere dati all'app.

  • Selezionare >>Explorer.

  • Fare clic con il pulsante destro del mouse sull'endpoint POST e scegliere Genera richiesta.

    Menu di scelta rapida di Esplora endpoint che evidenzia la voce di menu Genera richiesta.

    Viene creato un nuovo file nella cartella di progetto denominata TodoApi.http, con contenuto simile all'esempio seguente:

    @TodoApi_HostAddress = https://localhost:7031
    
    Post {{TodoApi_HostAddress}}/todoitems
    
    ###
    
    • La prima riga crea una variabile usata per tutti gli endpoint.
    • La riga successiva definisce una richiesta POST.
    • La riga triple hashtag (###) è un delimitatore di richiesta: ciò che viene dopo è per una richiesta diversa.
  • La richiesta POST richiede intestazioni e corpo. Per definire le parti della richiesta, aggiungere le righe seguenti immediatamente dopo la riga di richiesta POST:

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

    Il codice precedente aggiunge un'intestazione Content-Type e un corpo della richiesta JSON. Il file TodoApi.http dovrebbe ora essere simile all'esempio seguente, ma con il numero di porta:

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

  • Selezionare il collegamento Invia richiesta sopra la riga della POST richiesta.

    Finestra del file .http con collegamento di esecuzione evidenziato.

    La richiesta POST viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .

    Finestra del file .http con risposta dalla richiesta POST.

Esaminare gli endpoint GET

L'app di esempio implementa diversi endpoint GET chiamando MapGet:

API Descrizione Testo della richiesta Corpo della risposta
GET /todoitems Ottiene tutti gli elementi attività None Matrice di elementi attività
GET /todoitems/complete Ottenere tutti gli elementi attività completati None Matrice di elementi attività
GET /todoitems/{id} Ottiene un elemento in base all'ID None Elemento attività
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());

Testare gli endpoint GET

Testare l'app chiamando gli GET endpoint da un browser o usando Esplora endpoint. I passaggi seguenti sono relativi a Esplora endpoint.

  • In Esplora endpoint fare clic con il pulsante destro del mouse sul primo endpoint GET e scegliere Genera richiesta.

    Al file viene aggiunto il TodoApi.http contenuto seguente:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Selezionare il collegamento Invia richiesta sopra la nuova GET riga di richiesta.

    La richiesta GET viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .

  • Il corpo della risposta è simile al codice JSON seguente:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • In Esplora endpoint fare clic con il pulsante destro del mouse sull'endpoint /todoitems/{id}GET e scegliere Genera richiesta. Al file viene aggiunto il TodoApi.http contenuto seguente:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Sostituisci {id} con 1.

  • Selezionare il collegamento Invia richiesta sopra la nuova riga di richiesta GET.

    La richiesta GET viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .

  • Il corpo della risposta è simile al codice JSON seguente:

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

Questa app usa un database in memoria. Se l'app viene riavviata, la richiesta GET non restituisce dati. Se non vengono restituiti dati, i dati POST nell'app e riprovare la richiesta GET.

Valori restituiti

ASP.NET Core serializza automaticamente l'oggetto su JSON e scrive il codice JSON nel corpo del messaggio di risposta. Il codice di risposta per questo tipo restituito è 200 OK, presupponendo che non siano presenti eccezioni non gestite. Le eccezioni non gestite vengono convertite in errori 5xx.

I tipi restituiti possono rappresentare un'ampia gamma di codici di stato HTTP. Ad esempio, GET /todoitems/{id} può restituire due valori di stato diversi:

  • Se nessun elemento corrisponde all'ID richiesto, il metodo restituisce un codice di errore di statoNotFound 404.
  • In caso contrario, il metodo restituisce il codice 200 con un corpo della risposta JSON. La restituzione di item risulta in una risposta HTTP 200.

Esaminare l'endpoint PUT

L'app di esempio implementa un singolo endpoint PUT usando 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();
});

Questo metodo è simile al metodo , ad eccezione del MapPost fatto che usa HTTP PUT. Una risposta con esito positivo restituisce 204 (nessun contenuto). In base alla specifica HTTP, una richiesta PUT richiede che il client invii l'intera entità aggiornata e non solo le modifiche. Per supportare gli aggiornamenti parziali, usare HTTP PATCH.

Testare l'endpoint PUT

Questo esempio usa un database in memoria che deve essere inizializzato ogni volta che l'app viene avviata. Deve esistere un elemento nel database prima di eseguire una chiamata PUT. Chiamare GET per assicurarsi che nel database sia presente un elemento prima di effettuare una chiamata PUT.

Aggiornare l'elemento attività con Id = 1 e impostarne il nome su "feed fish".

  • In Esplora endpoint fare clic con il pulsante destro del mouse sull'endpoint PUT e selezionare Genera richiesta.

    Al file viene aggiunto il TodoApi.http contenuto seguente:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Nella riga della richiesta PUT sostituire {id} con 1.

  • Aggiungere le righe seguenti subito dopo la riga della richiesta PUT:

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

    Il codice precedente aggiunge un'intestazione Content-Type e un corpo della richiesta JSON.

  • Selezionare il collegamento Invia richiesta sopra la nuova riga di richiesta PUT.

    La richiesta PUT viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta . Il corpo della risposta è vuoto e il codice di stato è 204.

Esaminare e testare l'endpoint DELETE

L'app di esempio implementa un singolo endpoint DELETE usando 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();
});
  • In Esplora endpoint fare clic con il pulsante destro del mouse sull'endpoint DELETE e scegliere Genera richiesta.

    Una richiesta DELETE viene aggiunta a TodoApi.http.

  • Sostituire {id} nella riga di richiesta DELETE con 1. La richiesta DELETE dovrebbe essere simile all'esempio seguente:

    DELETE {{TodoApi_HostAddress}}/todoitems/1
    
    ###
    
  • Selezionare il collegamento Invia richiesta per la richiesta DELETE.

    La richiesta DELETE viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta . Il corpo della risposta è vuoto e il codice di stato è 204.

Usare l'API MapGroup

Il codice dell'app di esempio ripete il todoitems prefisso URL ogni volta che configura un endpoint. Le API hanno spesso gruppi di endpoint con un prefisso URL comune e il MapGroup metodo è disponibile per organizzare tali gruppi. Riduce il codice ripetitivo e consente di personalizzare interi gruppi di endpoint con una singola chiamata a metodi come RequireAuthorization e WithMetadata.

Sostituire il contenuto di Program.cs con il codice seguente:

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

Il codice precedente presenta le modifiche seguenti:

  • Aggiunge var todoItems = app.MapGroup("/todoitems"); per configurare il gruppo usando il prefisso /todoitemsURL .
  • Modifica tutti i app.Map<HttpVerb> metodi in todoItems.Map<HttpVerb>.
  • Rimuove il prefisso /todoitems URL dalle chiamate al Map<HttpVerb> metodo.

Testare gli endpoint per verificare che funzionino allo stesso modo.

Usare l'API TypedResults

La restituzione TypedResults anziché Results presenta diversi vantaggi, tra cui la testabilità e la restituzione automatica dei metadati del tipo di risposta per OpenAPI per descrivere l'endpoint. Per altre informazioni, vedere TypedResults vs Results.

I Map<HttpVerb> metodi possono chiamare metodi del gestore di route anziché usare espressioni lambda. Per visualizzare un esempio, aggiornare Program.cs con il codice seguente:

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

Il Map<HttpVerb> codice chiama ora i metodi anziché le espressioni lambda:

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

Questi metodi restituiscono oggetti che implementano IResult e sono definiti da 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();
}

Gli unit test possono chiamare questi metodi e verificare che restituiscono il tipo corretto. Ad esempio, se il metodo è GetAllTodos:

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

Il codice di unit test può verificare che un oggetto di tipo Ok<Todo[]> venga restituito dal metodo del gestore. Ad esempio:

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

Impedire l'over-post

Attualmente l'app di esempio espone l'intero Todo oggetto. App di produzione Nelle applicazioni di produzione, viene spesso usato un subset del modello per limitare i dati che possono essere inseriti e restituiti. Esistono diversi motivi alla base di questa situazione e la sicurezza è una delle principali. Il subset di un modello viene in genere definito DTO (Data Transfer Object), modello di input o modello di visualizzazione. DTO viene usato in questo articolo.

Un DTO può essere usato per:

  • Impedire l'over-post.
  • Nascondere le proprietà che i client non devono visualizzare.
  • Omettere alcune proprietà per ridurre le dimensioni del payload.
  • Appiattire gli oggetti grafici che contengono oggetti annidati. Gli oggetti grafici appiattiti possono essere più pratici per i client.

Per illustrare l'approccio DTO, aggiornare la Todo classe in modo da includere un campo segreto:

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

Il campo segreto deve essere nascosto da questa app, ma un'app amministrativa potrebbe scegliere di esporla.

Verificare che sia possibile pubblicare e ottenere il campo segreto.

Creare un file denominato TodoItemDTO.cs con il codice seguente:

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

Sostituire il contenuto del Program.cs file con il codice seguente per usare questo modello DTO:

using Microsoft.EntityFrameworkCore;

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

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

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

app.Run();

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

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

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

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

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

    todoItemDTO = new TodoItemDTO(todoItem);

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

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

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

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

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

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

    return TypedResults.NotFound();
}

Verificare che sia possibile pubblicare e ottenere tutti i campi ad eccezione del campo segreto.

Risoluzione dei problemi con l'esempio completato

Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Visualizzare o scaricare il progetto completato (come scaricare).

Passaggi successivi

Altre informazioni

Vedere Informazioni di riferimento rapido sulle API minime