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 8 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 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 8 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 | Corpo 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
Visual Studio 2022 Preview con il carico di lavoro ASP.NET e sviluppo Web.
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.
- Immettere
Assegnare al progetto il nome TodoApi e selezionare Avanti.
Nella finestra di dialogo Informazioni aggiuntive:
- Selezionare .NET 9.0 (anteprima)
- 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:
- Crea un WebApplicationBuilder oggetto e con WebApplication impostazioni predefinite preconfigurate.
- Crea un endpoint
/
HTTP GET che restituisceHello World!
:
Eseguire l'app
Premere CTRL+F5 per l'esecuzione senza il debugger.
Visual Studio visualizza la finestra di dialogo seguente:
Selezionare Sì se si considera attendibile il certificato SSL di IIS Express.
Verrà visualizzata la finestra di dialogo seguente:
Selezionare Sì 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 menu 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 Visualizza>altri endpoint di Windows>Explorer.
Fare clic con il pulsante destro del mouse sull'endpoint POST e scegliere 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.La richiesta POST viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .
Esaminare gli endpoint GET
L'app di esempio implementa diversi endpoint GET chiamando MapGet
:
API | Descrizione | Corpo 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 ilTodoApi.http
contenuto seguente:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Sostituisci
{id}
con1
.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 stato NotFound 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}
con1
.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 con1
. 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/todoitems
URL . - Modifica tutti i
app.Map<HttpVerb>
metodi intodoItems.Map<HttpVerb>
. - Rimuove il prefisso
/todoitems
URL dalle chiamate alMap<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
- Configurare le opzioni di serializzazione JSON.
- Gestire errori ed eccezioni: la pagina delle eccezioni per sviluppatori è abilitata per impostazione predefinita nell'ambiente di sviluppo per le app per le API minime. Per informazioni su come gestire errori ed eccezioni, vedere Gestire gli errori nelle API di base di ASP.NET.
- Per un esempio di test di un'app per le API minima, vedere questo esempio di GitHub.
- Supporto openAPI nelle API minime.
- Guida introduttiva: Pubblicare in Azure.
- Organizzazione delle API minime di base ASP.NET.
Altre informazioni
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 | Corpo 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
Visual Studio 2022 con il carico di lavoro Sviluppo ASP.NET e Web.
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.
- Immettere
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.
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:
- Crea un WebApplicationBuilder oggetto e con WebApplication impostazioni predefinite preconfigurate.
- Crea un endpoint
/
HTTP GET che restituisceHello World!
:
Eseguire l'app
Premere CTRL+F5 per l'esecuzione senza il debugger.
Visual Studio visualizza la finestra di dialogo seguente:
Selezionare Sì se si considera attendibile il certificato SSL di IIS Express.
Verrà visualizzata la finestra di dialogo seguente:
Selezionare Sì 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 menu 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 rigavar 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 CoreAggiungere il codice evidenziato seguente alla riga successiva dopo
app
la definizione nella rigavar 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.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 fornisce un riquadro Risposte sotto il pulsante Esegui .
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 su1
. - 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 | Corpo 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>Try it out>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 stato NotFound 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/todoitems
URL . - Modifica tutti i
app.Map<HttpVerb>
metodi intodoItems.Map<HttpVerb>
. - Rimuove il prefisso
/todoitems
URL dalle chiamate alMap<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
- Configurare le opzioni di serializzazione JSON.
- Gestire errori ed eccezioni: la pagina delle eccezioni per sviluppatori è abilitata per impostazione predefinita nell'ambiente di sviluppo per le app per le API minime. Per informazioni su come gestire errori ed eccezioni, vedere Gestire gli errori nelle API di base di ASP.NET.
- Per un esempio di test di un'app per le API minima, vedere questo esempio di GitHub.
- Supporto openAPI nelle API minime.
- Guida introduttiva: Pubblicare in Azure.
- Organizzazione delle API minime di base ASP.NET.
Altre informazioni
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 | Corpo 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
- Visual Studio 2022 con il carico di lavoro Sviluppo ASP.NET e Web.
- .NET 6.0 SDK
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.
- Immettere
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:
- Crea un WebApplicationBuilder oggetto e con WebApplication impostazioni predefinite preconfigurate.
- Crea un endpoint
/
HTTP GET che restituisceHello World!
:
Eseguire l'app
Premere CTRL+F5 per l'esecuzione senza il debugger.
Visual Studio visualizza la finestra di dialogo seguente:
Selezionare Sì se si considera attendibile il certificato SSL di IIS Express.
Verrà visualizzata la finestra di dialogo seguente:
Selezionare Sì 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 menu 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 rigavar 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 CoreAggiungere il codice evidenziato seguente alla riga successiva dopo
app
la definizione nella rigavar 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.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 fornisce un riquadro Risposte sotto il pulsante Esegui .
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 su1
. - 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 | Corpo 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>Try it out>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 stato NotFound 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 | Corpo 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
Visual Studio 2022 con il carico di lavoro Sviluppo ASP.NET e Web.
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.
- Immettere
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.
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:
- Crea un WebApplicationBuilder oggetto e con WebApplication impostazioni predefinite preconfigurate.
- Crea un endpoint
/
HTTP GET che restituisceHello World!
:
Eseguire l'app
Premere CTRL+F5 per l'esecuzione senza il debugger.
Visual Studio visualizza la finestra di dialogo seguente:
Selezionare Sì se si considera attendibile il certificato SSL di IIS Express.
Verrà visualizzata la finestra di dialogo seguente:
Selezionare Sì 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 menu 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 Visualizza>altri endpoint di Windows>Explorer.
Fare clic con il pulsante destro del mouse sull'endpoint POST e scegliere 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.La richiesta POST viene inviata all'app e la risposta viene visualizzata nel riquadro Risposta .
Esaminare gli endpoint GET
L'app di esempio implementa diversi endpoint GET chiamando MapGet
:
API | Descrizione | Corpo 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 ilTodoApi.http
contenuto seguente:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Sostituisci
{id}
con1
.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 stato NotFound 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}
con1
.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 con1
. 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/todoitems
URL . - Modifica tutti i
app.Map<HttpVerb>
metodi intodoItems.Map<HttpVerb>
. - Rimuove il prefisso
/todoitems
URL dalle chiamate alMap<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
- Configurare le opzioni di serializzazione JSON.
- Gestire errori ed eccezioni: la pagina delle eccezioni per sviluppatori è abilitata per impostazione predefinita nell'ambiente di sviluppo per le app per le API minime. Per informazioni su come gestire errori ed eccezioni, vedere Gestire gli errori nelle API di base di ASP.NET.
- Per un esempio di test di un'app per le API minima, vedere questo esempio di GitHub.
- Supporto openAPI nelle API minime.
- Guida introduttiva: Pubblicare in Azure.
- Organizzazione delle API minime di base ASP.NET.