Tutoriel : Créer une API minimale avec ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 9 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 9 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 9 de cet article.
Par Rick Anderson et Ryan Nowak
Les API minimales sont conçues pour créer des API HTTP ayant des dépendances minimales. Elles sont idéales pour les microservices et les applications pour lesquels vous voulez inclure seulement le minimum de fichiers, de fonctionnalités et de dépendances dans ASP.NET Core.
Ce tutoriel décrit les principes fondamentaux liés à la génération d’une API minimale avec ASP.NET Core. Une autre approche de la création d’API dans ASP.NET Core consiste à utiliser des contrôleurs. Pour obtenir de l’aide avec le choix entre les API minimales et les API basées sur un contrôleur, consultez Vue d’ensemble des API. Pour obtenir un tutoriel sur la création d’un projet d’API basée sur des contrôleurs qui contiennent d’autres fonctionnalités, consultez Créer une API web.
Vue d’ensemble
Ce didacticiel crée l’API suivante :
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenir les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
POST /todoitems |
Ajouter un nouvel élément | Tâche | Tâche |
PUT /todoitems/{id} |
Mettre à jour un élément existant | Tâche | Aucune |
DELETE /todoitems/{id} |
Supprimer un élément | Aucune | Aucune |
Prerequisites
Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
Créez un projet d’API
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
- Entrez
Empty
dans la zone de recherche Rechercher des modèles. - Sélectionnez le modèle ASP.NET Core vide, puis Suivant.
- Entrez
Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
- Sélectionnez .NET 9.0
- Décochez la case N’utilisez pas d’instructions de niveau supérieur
- Sélectionnez Créer
Examiner le code
Le fichier Program.cs
contient le code suivant :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Le code précédent :
- Crée un WebApplicationBuilder et un WebApplication avec des valeurs préconfigurées par défaut.
- Crée un point de terminaison HTTP GET
/
qui retourneHello World!
:
Exécuter l’application
Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.
Visual Studio affiche la boîte de dialogue suivante :
Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.
La boîte de dialogue suivante s’affiche :
Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.
Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio lance le Kestrel serveur web et ouvre une fenêtre de navigateur.
Hello World!
s’affiche dans le navigateur. Le fichier Program.cs
contient une application minimale mais complète.
Fermez la fenêtre du navigateur.
Ajouter des packages NuGet
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
- Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Gérer les packages NuGet pour la solution.
- Sélectionnez l’onglet Parcourir.
- Sélectionnez Inclure la préversion.
- Entrez Microsoft.EntityFrameworkCore.InMemory dans la zone de recherche, puis sélectionnez
Microsoft.EntityFrameworkCore.InMemory
. - Cochez la case Projet dans le volet droit, puis sélectionnez Installer.
- Suivez les instructions précédentes pour ajouter le package
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
.
Classes de contexte de modèle et de base de données
- Dans le dossier du projet, créez un fichier nommé
Todo.cs
avec le code suivant :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Le code précédent crée le modèle pour cette application. Un modèle est un ensemble de classes qui représentent les données gérées par l’application.
- Créez un fichier nommé
TodoDb.cs
avec le code suivant :
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Le précédent code définit le contexte de base de données, qui est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données. Cette classe dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
Ajouter le code de l’API
- Remplacez le contenu du fichier
Program.cs
par le code suivant :
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();
Le code suivant mis en surbrillance ajoute le contexte de base de données au conteneur d’injection de dépendances et permet d’afficher les exceptions liées à la base de données :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Le conteneur d’injection de dépendances fournit l’accès au contexte de base de données et à d’autres services.
Ce didacticiel utilise l’Explorateur des points de terminaison et les fichiers .http pour tester l’API.
Testez les données de publication
Le code suivant dans Program.cs
crée un point de terminaison HTTP POST /todoitems
qui ajoute des données à la base de données en mémoire :
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Exécutez l'application. Le navigateur affiche une erreur 404, car il n’y a plus de point de terminaison /
.
Le point de terminaison POST sera utilisé pour ajouter des données à l’application.
Sélectionnez Afficher, > et Explorateur de points de terminaison.
Cliquez avec le bouton droit sur le point de terminaison POST, puis sélectionnez Générer une requête.
Un nouveau fichier est créé dans le dossier de projet nommé
TodoApi.http
, avec un contenu similaire à l’exemple suivant :@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- La première ligne crée une variable qui est utilisée pour tous les points de terminaison.
- La ligne suivante définit une requête POST.
- La triple hashtag (
###
) ligne est un délimiteur de requête : ce qui arrive après qu’il est pour une autre requête.
La requête POST a besoin d’en-têtes et d’un corps. Pour définir ces parties de la requête, ajoutez les lignes suivantes immédiatement après la ligne de requête POST :
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Le code précédent ajoute un en-tête Content-Type et un corps de la demande JSON. Le fichier TodoApi.http doit maintenant ressembler à l’exemple suivant, mais avec votre numéro de port :
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Exécutez l'application.
Sélectionnez le lien Envoyer une requête au-dessus de la ligne de requête
POST
.La requête POST est envoyée à l’application et la réponse s’affiche dans le volet Réponse.
Examinez les points de terminaison GET
L’échantillon d’application implémente plusieurs points de terminaison GET en appelant MapGet
:
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenez tous les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
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());
Testez les points de terminaison GET
Testez l’application en appelant les GET
points d’extrémité à partir d’un navigateur ou en utilisant l’Explorateur des points de terminaison. Les étapes suivantes concernent l’Explorateur des points de terminaison.
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le premier GET un point de terminaison et sélectionnez Générer une requête.
Le contenu suivant est ajouté au fichier
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de requête
GET
.La requête GET est envoyée à l’application et la réponse s’affiche dans le volet Réponse.
Le corps de la réponse est similaire à JSON suivant :
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le point de terminaison
/todoitems/{id}
GET, puis sélectionnez Générer une requête. Le contenu suivant est ajouté au fichierTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Remplacez
{id}
par1
.Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de requête GET.
La requête GET est envoyée à l’application et la réponse s’affiche dans le volet Réponse.
Le corps de la réponse est similaire à JSON suivant :
{ "id": 1, "name": "walk dog", "isComplete": true }
Cette application utilise une base de données en mémoire. Si l’application est redémarrée, la requête GET ne retourne aucune donnée. Si aucune donnée n’est retournée, publiez (POST) les données dans l’application et réessayez la requête GET.
Valeurs de retour
ASP.NET Core sérialise automatiquement l’objet en JSON et écrit le JSON dans le corps du message de réponse. Le code de réponse pour ce type de retour est 200 OK, en supposant qu’il n’existe pas d’exception non gérée. Les exceptions non gérées sont converties en erreurs 5xx.
Les types de retour peuvent représenter une large plage de codes d’état HTTP. Par exemple, GET /todoitems/{id}
peut retourner deux valeurs d’état différentes :
- Si aucun élément ne correspond à l’ID demandé, la méthode retourne un code d’erreur Code de statut 404NotFound.
- Sinon, la méthode retourne 200 avec un corps de réponse JSON. Le retour de
item
entraîne une réponse HTTP 200.
Examinez le point de terminaison PUT
L’échantillon d’application implémente un point de terminaison PUT unique à l’aide de 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();
});
Cette méthode est similaire à la méthode MapPost
, sauf qu’elle utilise HTTP PUT. Une réponse réussie retourne 204 (Aucun contenu). D’après la spécification HTTP, une requête PUT nécessite que le client envoie toute l’entité mise à jour, et pas seulement les changements. Pour prendre en charge les mises à jour partielles, utilisez HTTP PATCH.
Testez le point de terminaison PUT
Cet exemple utilise une base de données en mémoire qui doit être initialisée à chaque démarrage de l’application. La base de données doit contenir un élément avant que vous ne passiez un appel PUT. Appelez GET pour vérifier qu’un élément existe dans la base de données avant d’effectuer un appel PUT.
Mettez à jour l’élément de tâche avec Id = 1
et définissez son nom sur "feed fish"
.
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le point de terminaison PUT et sélectionnez Générer une requête.
Le contenu suivant est ajouté au fichier
TodoApi.http
:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
Dans la ligne de requête PUT, remplacez
{id}
par1
.Ajoutez les lignes suivantes immédiatement après la ligne de requête PUT :
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Le code précédent ajoute un en-tête Content-Type et un corps de la demande JSON.
Sélectionnez le lien Send request (Envoyer une requête) qui se trouve au-dessus de la ligne de la nouvelle requête PUT.
La requête PUT est envoyée à l’application et la réponse s’affiche dans le volet Réponse. Le corps de la réponse est vide et le code d’état est 204.
Examiner et tester le point de terminaison DELETE
L’échantillon d’application implémente un seul point de terminaison DELETE à l’aide de 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();
});
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le point de terminaison DELETE et sélectionnez Générer une requête.
Une requête DELETE est ajoutée à
TodoApi.http
.Remplacez
{id}
dans la ligne de requête DELETE par1
. La requête DELETE doit ressembler à l’exemple suivant :DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Sélectionnez le lien Envoyer une requête pour la demande DELETE.
La requête DELETE est envoyée à l’application et la réponse s’affiche dans le volet Réponse. Le corps de la réponse est vide et le code d’état est 204.
Utiliser l’API MapGroup
L’échantillon de code d’application répète le préfixe d’URL todoitems
chaque fois qu’il configure un point de terminaison. Les API ont souvent des groupes de points de terminaison avec un préfixe d’URL commun, et la méthode MapGroup est disponible pour vous aider à organiser ces groupes. Cela réduit le code répétitif et permet de personnaliser des groupes entiers de points de terminaison avec un seul appel à des méthodes comme RequireAuthorization et WithMetadata.
Remplacez le contenu de Program.cs
par le code suivant :
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();
Le code précédent dispose des modifications suivantes :
- Ajoute
var todoItems = app.MapGroup("/todoitems");
pour configurer le groupe à l’aide du préfixe d’URL/todoitems
. - Remplace toutes les méthodes
app.Map<HttpVerb>
partodoItems.Map<HttpVerb>
. - Supprime le préfixe d’URL
/todoitems
des appels de méthodeMap<HttpVerb>
.
Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
Utilisez l’API TypedResults
Retourner TypedResults plutôt que Results présente plusieurs avantages, notamment la testabilité et le retour automatique des métadonnées de type de réponse pour OpenAPI afin de décrire le point de terminaison. Pour plus d'informations, consultez TypedResults vs Results.
Les méthodes Map<HttpVerb>
peuvent appeler des méthodes de gestionnaire de routage au lieu d’utiliser des expressions lambda. Pour voir un exemple, mettez à jour Program.cs avec le code suivant :
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();
}
Le code Map<HttpVerb>
appelle maintenant des méthodes au lieu des 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);
Ces méthodes retournent des objets qui implémentent IResult et sont définis par 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();
}
Les tests unitaires peuvent appeler ces méthodes et vérifier qu’elles retournent le type correct. Par exemple, si la méthode est GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Le code de test unitaire peut vérifier qu’un objet de type Ok<Todo[]> est retourné à partir de la méthode de gestionnaire. Par exemple :
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);
}
Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet Todo
. Dans les applications de production, un sous-ensemble du modèle est souvent utilisé pour restreindre les données qui peuvent être entrées et retournées. Il y a plusieurs raisons à cela, et la sécurité en est une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.
Un DTO peut être utilisé pour :
- Empêcher la sur-publication.
- Masquer les propriétés que les clients ne sont pas censés voir.
- Omettez certaines propriétés afin de réduire la taille de la charge utile.
- Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques d’objets aplatis peuvent être plus pratiques pour les clients.
Pour illustrer l’approche DTO, mettez à jour la classe Todo
pour inclure un champ secret :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Le champ secret doit être masqué dans cette application, mais une application administrative peut choisir de l’exposer.
Vérifiez que vous pouvez publier et obtenir le champ secret.
Créez un fichier nommé TodoItemDTO.cs
avec le code suivant :
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);
}
Remplacez le contenu du fichier Program.cs
par le code suivant pour utiliser ce modèle 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();
}
Vérifiez que vous pouvez publier et obtenir tous les champs à l’exception du champ secret.
Résolution des problèmes avec l’exemple terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).
Étapes suivantes
- Configurer les options de sérialisation JSON
- Gérer les erreurs et les exceptions : la page d’exceptions du développeur est activée par défaut dans l’environnement de développement pour les applications API minimales. Pour plus d’informations sur la gestion des erreurs et des exceptions, consultez Gérer les erreurs dans les API ASP.NET Core.
- Pour obtenir un échantillon de test d’une application API minimale, consultez cet échantillon GitHub.
- Prise en charge d’OpenAPI dans les API minimales.
- Démarrage rapide : Publier sur Azure.
- Organisation des API minimales d’ASP.NET Core.
En savoir plus
Consultez Informations de référence rapides sur les API minimales.
Les API minimales sont conçues pour créer des API HTTP ayant des dépendances minimales. Elles sont idéales pour les microservices et les applications qui ne nécessitent qu’un minimum de fichiers, de fonctionnalités et de dépendances dans ASP.NET Core.
Ce tutoriel décrit les principes fondamentaux liés à la génération d’une API minimale avec ASP.NET Core. Une autre approche de la création d’API dans ASP.NET Core consiste à utiliser des contrôleurs. Pour obtenir de l’aide sur le choix entre les API minimales et les API basées sur un contrôleur, consultez Vue d’ensemble des API. Pour obtenir un tutoriel sur la création d’un projet d’API basée sur des contrôleurs qui contiennent d’autres fonctionnalités, consultez Créer une API web.
Vue d’ensemble
Ce didacticiel crée l’API suivante :
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenir les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
POST /todoitems |
Ajouter un nouvel élément | Tâche | Tâche |
PUT /todoitems/{id} |
Mettre à jour un élément existant | Tâche | Aucune |
DELETE /todoitems/{id} |
Supprimer un élément | Aucune | Aucune |
Prerequisites
Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
Créez un projet d’API
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
- Entrez
Empty
dans la zone de recherche Rechercher des modèles. - Sélectionnez le modèle ASP.NET Core vide, puis Suivant.
- Entrez
Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
- Sélectionner .NET 7.0
- Décochez la case N’utilisez pas d’instructions de niveau supérieur
- Sélectionnez Créer
Examiner le code
Le fichier Program.cs
contient le code suivant :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Le code précédent :
- Crée un WebApplicationBuilder et un WebApplication avec des valeurs préconfigurées par défaut.
- Crée un point de terminaison HTTP GET
/
qui retourneHello World!
:
Exécuter l’application
Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.
Visual Studio affiche la boîte de dialogue suivante :
Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.
La boîte de dialogue suivante s’affiche :
Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.
Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio lance le Kestrel serveur web et ouvre une fenêtre de navigateur.
Hello World!
s’affiche dans le navigateur. Le fichier Program.cs
contient une application minimale mais complète.
Ajouter des packages NuGet
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
- Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Gérer les packages NuGet pour la solution.
- Sélectionnez l’onglet Parcourir.
- Entrez Microsoft.EntityFrameworkCore.InMemory dans la zone de recherche, puis sélectionnez
Microsoft.EntityFrameworkCore.InMemory
. - Cochez la case Projet dans le volet droit.
- Dans la liste déroulante Version, sélectionnez la version 7 la plus récente disponible, par exemple
7.0.17
, puis sélectionnez Installer. - Suivez les instructions précédentes pour ajouter le package
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
avec la version 7 la plus récente disponible.
Classes de contexte de modèle et de base de données
Dans le dossier du projet, créez un fichier nommé Todo.cs
avec le code suivant :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Le code précédent crée le modèle pour cette application. Un modèle est un ensemble de classes qui représentent les données gérées par l’application.
Créez un fichier nommé TodoDb.cs
avec le code suivant :
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Le précédent code définit le contexte de base de données, qui est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données. Cette classe dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
Ajouter le code de l’API
Remplacez le contenu du fichier Program.cs
par le code suivant :
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();
Le code suivant mis en surbrillance ajoute le contexte de base de données au conteneur d’injection de dépendances et permet d’afficher les exceptions liées à la base de données :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Le conteneur d’injection de dépendances fournit l’accès au contexte de base de données et à d’autres services.
Créer une interface utilisateur de test d’API avec Swagger
Vous pouvez choisir parmi de nombreux outils de test d’API web, et vous pouvez suivre les étapes de cette introduction aux tests d’API de ce tutoriel avec l’outil que vous préférez.
Ce tutoriel utilise le package .NET NSwag.AspNetCore, qui intègre des outils Swagger pour générer une interface utilisateur de test conforme à la spécification OpenAPI :
- NSwag : une bibliothèque .NET qui intègre Swagger directement dans des applications ASP.NET Core, en fournissant un intergiciel et une configuration.
- Swagger : un ensemble d’outils open source, comme OpenAPIGenerator et SwaggerUI, générant des pages de test d’API qui suivent la spécification OpenAPI.
- Spécification OpenAPI : un document qui décrit les fonctionnalités de l’API, en se basant sur les annotations XML et d’attribut présentes dans les contrôleurs et les modèles.
Pour plus d’informations sur l’utilisation d’OpenAPI et de NSwag avec ASP.NET, consultez Documentation des API web ASP.NET Core avec Swagger / OpenAPI.
Installer les outils Swagger
Exécutez la commande suivante :
dotnet add package NSwag.AspNetCore
La commande précédente ajoute le package NSwag.AspNetCore, qui contient des outils pour générer des documents et une interface utilisateur Swagger.
Configurer l’intergiciel Swagger
Ajoutez le code mis en surbrillance suivant avant la définition de
app
dans la lignevar 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();
Dans le code précédent :
builder.Services.AddEndpointsApiExplorer();
: active l’Explorateur d’API, qui est un service fournissant des métadonnées sur l’API HTTP. L’Explorateur d’API est utilisé par Swagger pour générer le document Swagger.builder.Services.AddOpenApiDocument(config => {...});
: ajoute le générateur de documents Swagger OpenAPI aux services d’application et le configure pour fournir plus d’informations sur l’API, comme son titre et sa version. Pour plus d’informations sur la fourniture de détails d’API plus robustes, consultez Bien démarrer avec NSwag et ASP.NET Core.Ajoutez le code en surbrillance suivant à la ligne qui se trouve après la définition de
app
dans la lignevar 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"; }); }
Le code précédent permet à l’intergiciel Swagger de servir le document JSON et l’interface utilisateur Swagger générés. Swagger est activé seulement dans un environnement de développement. L’activation de Swagger dans un environnement de production peut exposer des détails potentiellement sensibles sur la structure et l’implémentation de l’API.
Testez les données de publication
Le code suivant dans Program.cs
crée un point de terminaison HTTP POST /todoitems
qui ajoute des données à la base de données en mémoire :
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Exécutez l'application. Le navigateur affiche une erreur 404, car il n’y a plus de point de terminaison /
.
Le point de terminaison POST sera utilisé pour ajouter des données à l’application.
Avec l’application toujours en cours d’exécution, dans le navigateur, accédez à
https://localhost:<port>/swagger
pour afficher la page de test de l’API générée par Swagger.Dans la page de test de l’API Swagger, sélectionnez POST /todoitems>Try it out (Essayer).
Notez que le champ Request body (Corps de la requête) contient un exemple de format généré reflétant les paramètres de l’API.
Dans le corps de la requête, entrez JSON pour un élément de tâche, sans spécifier l’élément facultatif
id
:{ "name":"walk dog", "isComplete":true }
Sélectionnez Exécuter.
Swagger fournit un volet Responses (Réponses) sous le bouton Execute (Exécuter).
Notez quelques-unes des informations utiles :
- cURL : Swagger fournit un exemple de commande cURL selon la syntaxe Unix/Linux, qui peut être exécutée sur la ligne de commande avec n’importe quel shell bash utilisant la syntaxe Unix/Linux, y compris Git Bash de Git pour Windows.
- Request URL (URL de la requête) : une représentation simplifiée de la requête HTTP effectuée par le code JavaScript de l’interface utilisateur Swagger pour l’appel d’API. Les requêtes réelles peuvent inclure des détails comme des en-têtes, des paramètres de requête et un corps de requête.
- Server response (Réponse du serveur) : inclut le corps et les en-têtes de la réponse. Le corps de la réponse montre que l’élément
id
a été défini sur1
. - Code de réponse : un code d’état
HTTP
201 a été retourné, indiquant que la demande a été correctement traitée et a entraîné la création d’une nouvelle ressource.
Examinez les points de terminaison GET
L’échantillon d’application implémente plusieurs points de terminaison GET en appelant MapGet
:
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenez tous les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
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());
Testez les points de terminaison GET
Testez l’application en appelant les points de terminaison depuis un navigateur ou depuis Swagger.
Dans Swagger, sélectionnez GET /todoitems>Try it out (Essayer)>Execute (Exécuter).
Vous pouvez également appeler GET /todoitems depuis un navigateur en entrant l’URI
http://localhost:<port>/todoitems
. Par exemple,http://localhost:5001/todoitems
L’appel à GET /todoitems
génère une réponse similaire à ce qui suit :
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Appelez GET /todoitems/{id} dans Swagger pour retourner des données d’un ID spécifique :
- Sélectionnez GET /todoitems>Try it out (Essayer).
- Définissez le champ id sur
1
, puis sélectionnez Execute (Exécuter).
Vous pouvez également appeler GET /todoitems depuis un navigateur en entrant l’URI
https://localhost:<port>/todoitems/1
. Par exemple,https://localhost:5001/todoitems/1
La réponse est similaire à celle-ci :
{ "id": 1, "name": "walk dog", "isComplete": true }
Cette application utilise une base de données en mémoire. Si l’application est redémarrée, la requête GET ne retourne aucune donnée. Si aucune donnée n’est retournée, publiez (POST) les données dans l’application et réessayez la requête GET.
Valeurs de retour
ASP.NET Core sérialise automatiquement l’objet en JSON et écrit le JSON dans le corps du message de réponse. Le code de réponse pour ce type de retour est 200 OK, en supposant qu’il n’existe pas d’exception non gérée. Les exceptions non gérées sont converties en erreurs 5xx.
Les types de retour peuvent représenter une large plage de codes d’état HTTP. Par exemple, GET /todoitems/{id}
peut retourner deux valeurs d’état différentes :
- Si aucun élément ne correspond à l’ID demandé, la méthode retourne un code d’erreur Code de statut 404NotFound.
- Sinon, la méthode retourne 200 avec un corps de réponse JSON. Le retour de
item
entraîne une réponse HTTP 200.
Examinez le point de terminaison PUT
L’échantillon d’application implémente un point de terminaison PUT unique à l’aide de 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();
});
Cette méthode est similaire à la méthode MapPost
, sauf qu’elle utilise HTTP PUT. Une réponse réussie retourne 204 (Aucun contenu). D’après la spécification HTTP, une requête PUT nécessite que le client envoie toute l’entité mise à jour, et pas seulement les changements. Pour prendre en charge les mises à jour partielles, utilisez HTTP PATCH.
Testez le point de terminaison PUT
Cet exemple utilise une base de données en mémoire qui doit être initialisée à chaque démarrage de l’application. La base de données doit contenir un élément avant que vous ne passiez un appel PUT. Appelez GET pour vérifier qu’un élément existe dans la base de données avant d’effectuer un appel PUT.
Mettez à jour l’élément de tâche avec Id = 1
et définissez son nom sur "feed fish"
.
Utilisez Swagger pour envoyer une requête PUT :
Sélectionnez Put /todoitems/{id}>Try it out (Essayer).
Définissez le champ id sur
1
.Définissez le corps de la requête sur le JSON suivant :
{ "name": "feed fish", "isComplete": false }
Sélectionnez Exécuter.
Examiner et tester le point de terminaison DELETE
L’échantillon d’application implémente un seul point de terminaison DELETE à l’aide de 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();
});
Utilisez Swagger pour envoyer une requête DELETE :
Sélectionnez DELETE /todoitems/{id}>Try it out (Essayer).
Définissez le champ ID sur
1
, puis sélectionnez Execute (Exécuter).La requête DELETE est envoyée à l’application et la réponse est affichée dans le volet Responses (Réponses). Le corps de la réponse est vide et le code d’état de Server response (Réponse du serveur) est 204.
Utiliser l’API MapGroup
L’échantillon de code d’application répète le préfixe d’URL todoitems
chaque fois qu’il configure un point de terminaison. Les API ont souvent des groupes de points de terminaison avec un préfixe d’URL commun, et la méthode MapGroup est disponible pour vous aider à organiser ces groupes. Cela réduit le code répétitif et permet de personnaliser des groupes entiers de points de terminaison avec un seul appel à des méthodes comme RequireAuthorization et WithMetadata.
Remplacez le contenu de Program.cs
par le code suivant :
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();
Le code précédent dispose des modifications suivantes :
- Ajoute
var todoItems = app.MapGroup("/todoitems");
pour configurer le groupe à l’aide du préfixe d’URL/todoitems
. - Remplace toutes les méthodes
app.Map<HttpVerb>
partodoItems.Map<HttpVerb>
. - Supprime le préfixe d’URL
/todoitems
des appels de méthodeMap<HttpVerb>
.
Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
Utilisez l’API TypedResults
Retourner TypedResults plutôt que Results présente plusieurs avantages, notamment la testabilité et le retour automatique des métadonnées de type de réponse pour OpenAPI afin de décrire le point de terminaison. Pour plus d'informations, consultez TypedResults vs Results.
Les méthodes Map<HttpVerb>
peuvent appeler des méthodes de gestionnaire de routage au lieu d’utiliser des expressions lambda. Pour voir un exemple, mettez à jour Program.cs avec le code suivant :
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();
}
Le code Map<HttpVerb>
appelle maintenant des méthodes au lieu des 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);
Ces méthodes retournent des objets qui implémentent IResult et sont définis par 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();
}
Les tests unitaires peuvent appeler ces méthodes et vérifier qu’elles retournent le type correct. Par exemple, si la méthode est GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Le code de test unitaire peut vérifier qu’un objet de type Ok<Todo[]> est retourné à partir de la méthode de gestionnaire. Par exemple :
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);
}
Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet Todo
. Dans les applications de production, un sous-ensemble du modèle est souvent utilisé pour restreindre les données qui peuvent être entrées et retournées. Il y a plusieurs raisons à cela, et la sécurité en est une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.
Un DTO peut être utilisé pour :
- Empêcher la sur-publication.
- Masquer les propriétés que les clients ne sont pas censés voir.
- Omettez certaines propriétés afin de réduire la taille de la charge utile.
- Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques d’objets aplatis peuvent être plus pratiques pour les clients.
Pour illustrer l’approche DTO, mettez à jour la classe Todo
pour inclure un champ secret :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Le champ secret doit être masqué dans cette application, mais une application administrative peut choisir de l’exposer.
Vérifiez que vous pouvez publier et obtenir le champ secret.
Créez un fichier nommé TodoItemDTO.cs
avec le code suivant :
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);
}
Remplacez le contenu du fichier Program.cs
par le code suivant pour utiliser ce modèle 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>();
}
Vérifiez que vous pouvez publier et obtenir tous les champs à l’exception du champ secret.
Résolution des problèmes avec l’exemple terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).
Étapes suivantes
- Configurer les options de sérialisation JSON
- Gérer les erreurs et les exceptions : la page d’exceptions du développeur est activée par défaut dans l’environnement de développement pour les applications API minimales. Pour plus d’informations sur la gestion des erreurs et des exceptions, consultez Gérer les erreurs dans les API ASP.NET Core.
- Pour obtenir un échantillon de test d’une application API minimale, consultez cet échantillon GitHub.
- Prise en charge d’OpenAPI dans les API minimales.
- Démarrage rapide : Publier sur Azure.
- Organisation des API minimales d’ASP.NET Core.
En savoir plus
Consultez Informations de référence rapides sur les API minimales.
Les API minimales sont conçues pour créer des API HTTP ayant des dépendances minimales. Elles sont idéales pour les microservices et les applications qui ne nécessitent qu’un minimum de fichiers, de fonctionnalités et de dépendances dans ASP.NET Core.
Ce tutoriel décrit les principes fondamentaux liés à la génération d’une API minimale avec ASP.NET Core. Une autre approche de la création d’API dans ASP.NET Core consiste à utiliser des contrôleurs. Pour obtenir de l’aide sur le choix entre les API minimales et les API basées sur un contrôleur, consultez Vue d’ensemble des API. Pour obtenir un tutoriel sur la création d’un projet d’API basée sur des contrôleurs qui contiennent d’autres fonctionnalités, consultez Créer une API web.
Vue d’ensemble
Ce didacticiel crée l’API suivante :
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenir les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
POST /todoitems |
Ajouter un nouvel élément | Tâche | Tâche |
PUT /todoitems/{id} |
Mettre à jour un élément existant | Tâche | Aucune |
DELETE /todoitems/{id} |
Supprimer un élément | Aucune | Aucune |
Prerequisites
- Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
- SDK .NET 6.0
Créez un projet d’API
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
- Entrez
Empty
dans la zone de recherche Rechercher des modèles. - Sélectionnez le modèle ASP.NET Core vide, puis Suivant.
- Entrez
Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
- Sélectionnez .NET 6.0
- Décochez la case N’utilisez pas d’instructions de niveau supérieur
- Sélectionnez Créer
Examiner le code
Le fichier Program.cs
contient le code suivant :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Le code précédent :
- Crée un WebApplicationBuilder et un WebApplication avec des valeurs préconfigurées par défaut.
- Crée un point de terminaison HTTP GET
/
qui retourneHello World!
:
Exécuter l’application
Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.
Visual Studio affiche la boîte de dialogue suivante :
Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.
La boîte de dialogue suivante s’affiche :
Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.
Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio lance le Kestrel serveur web et ouvre une fenêtre de navigateur.
Hello World!
s’affiche dans le navigateur. Le fichier Program.cs
contient une application minimale mais complète.
Ajouter des packages NuGet
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
- Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Gérer les packages NuGet pour la solution.
- Sélectionnez l’onglet Parcourir.
- Entrez Microsoft.EntityFrameworkCore.InMemory dans la zone de recherche, puis sélectionnez
Microsoft.EntityFrameworkCore.InMemory
. - Cochez la case Projet dans le volet droit.
- Dans la liste déroulante Version, sélectionnez la version 7 la plus récente disponible, par exemple
6.0.28
, puis sélectionnez Installer. - Suivez les instructions précédentes pour ajouter le package
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
avec la version 7 la plus récente disponible.
Classes de contexte de modèle et de base de données
Dans le dossier du projet, créez un fichier nommé Todo.cs
avec le code suivant :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Le code précédent crée le modèle pour cette application. Un modèle est un ensemble de classes qui représentent les données gérées par l’application.
Créez un fichier nommé TodoDb.cs
avec le code suivant :
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Le précédent code définit le contexte de base de données, qui est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données. Cette classe dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
Ajouter le code de l’API
Remplacez le contenu du fichier Program.cs
par le code suivant :
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();
Le code suivant mis en surbrillance ajoute le contexte de base de données au conteneur d’injection de dépendances et permet d’afficher les exceptions liées à la base de données :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Le conteneur d’injection de dépendances fournit l’accès au contexte de base de données et à d’autres services.
Créer une interface utilisateur de test d’API avec Swagger
Vous pouvez choisir parmi de nombreux outils de test d’API web, et vous pouvez suivre les étapes de cette introduction aux tests d’API de ce tutoriel avec l’outil que vous préférez.
Ce tutoriel utilise le package .NET NSwag.AspNetCore, qui intègre des outils Swagger pour générer une interface utilisateur de test conforme à la spécification OpenAPI :
- NSwag : une bibliothèque .NET qui intègre Swagger directement dans des applications ASP.NET Core, en fournissant un intergiciel et une configuration.
- Swagger : un ensemble d’outils open source, comme OpenAPIGenerator et SwaggerUI, générant des pages de test d’API qui suivent la spécification OpenAPI.
- Spécification OpenAPI : un document qui décrit les fonctionnalités de l’API, en se basant sur les annotations XML et d’attribut présentes dans les contrôleurs et les modèles.
Pour plus d’informations sur l’utilisation d’OpenAPI et de NSwag avec ASP.NET, consultez Documentation des API web ASP.NET Core avec Swagger / OpenAPI.
Installer les outils Swagger
Exécutez la commande suivante :
dotnet add package NSwag.AspNetCore
La commande précédente ajoute le package NSwag.AspNetCore, qui contient des outils pour générer des documents et une interface utilisateur Swagger.
Configurer l’intergiciel Swagger
En haut du fichier Program.cs, ajoutez les instructions
using
suivantes :using NSwag.AspNetCore;
Ajoutez le code mis en surbrillance suivant avant la définition de
app
dans la lignevar 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();
Dans le code précédent :
builder.Services.AddEndpointsApiExplorer();
: active l’Explorateur d’API, qui est un service fournissant des métadonnées sur l’API HTTP. L’Explorateur d’API est utilisé par Swagger pour générer le document Swagger.builder.Services.AddOpenApiDocument(config => {...});
: ajoute le générateur de documents Swagger OpenAPI aux services d’application et le configure pour fournir plus d’informations sur l’API, comme son titre et sa version. Pour plus d’informations sur la fourniture de détails d’API plus robustes, consultez Bien démarrer avec NSwag et ASP.NET Core.Ajoutez le code en surbrillance suivant à la ligne qui se trouve après la définition de
app
dans la lignevar 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"; }); }
Le code précédent permet à l’intergiciel Swagger de servir le document JSON et l’interface utilisateur Swagger générés. Swagger est activé seulement dans un environnement de développement. L’activation de Swagger dans un environnement de production peut exposer des détails potentiellement sensibles sur la structure et l’implémentation de l’API.
Testez les données de publication
Le code suivant dans Program.cs
crée un point de terminaison HTTP POST /todoitems
qui ajoute des données à la base de données en mémoire :
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Exécutez l'application. Le navigateur affiche une erreur 404, car il n’y a plus de point de terminaison /
.
Le point de terminaison POST sera utilisé pour ajouter des données à l’application.
Avec l’application toujours en cours d’exécution, dans le navigateur, accédez à
https://localhost:<port>/swagger
pour afficher la page de test de l’API générée par Swagger.Dans la page de test de l’API Swagger, sélectionnez POST /todoitems>Try it out (Essayer).
Notez que le champ Request body (Corps de la requête) contient un exemple de format généré reflétant les paramètres de l’API.
Dans le corps de la requête, entrez JSON pour un élément de tâche, sans spécifier l’élément facultatif
id
:{ "name":"walk dog", "isComplete":true }
Sélectionnez Exécuter.
Swagger fournit un volet Responses (Réponses) sous le bouton Execute (Exécuter).
Notez quelques-unes des informations utiles :
- cURL : Swagger fournit un exemple de commande cURL selon la syntaxe Unix/Linux, qui peut être exécutée sur la ligne de commande avec n’importe quel shell bash utilisant la syntaxe Unix/Linux, y compris Git Bash de Git pour Windows.
- Request URL (URL de la requête) : une représentation simplifiée de la requête HTTP effectuée par le code JavaScript de l’interface utilisateur Swagger pour l’appel d’API. Les requêtes réelles peuvent inclure des détails comme des en-têtes, des paramètres de requête et un corps de requête.
- Server response (Réponse du serveur) : inclut le corps et les en-têtes de la réponse. Le corps de la réponse montre que l’élément
id
a été défini sur1
. - Code de réponse : un code d’état
HTTP
201 a été retourné, indiquant que la demande a été correctement traitée et a entraîné la création d’une nouvelle ressource.
Examinez les points de terminaison GET
L’échantillon d’application implémente plusieurs points de terminaison GET en appelant MapGet
:
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenez tous les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
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());
Testez les points de terminaison GET
Testez l’application en appelant les points de terminaison depuis un navigateur ou depuis Swagger.
Dans Swagger, sélectionnez GET /todoitems>Try it out (Essayer)>Execute (Exécuter).
Vous pouvez également appeler GET /todoitems depuis un navigateur en entrant l’URI
http://localhost:<port>/todoitems
. Par exemple,http://localhost:5001/todoitems
L’appel à GET /todoitems
génère une réponse similaire à ce qui suit :
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Appelez GET /todoitems/{id} dans Swagger pour retourner des données d’un ID spécifique :
- Sélectionnez GET /todoitems>Try it out (Essayer).
- Définissez le champ id sur
1
, puis sélectionnez Execute (Exécuter).
Vous pouvez également appeler GET /todoitems depuis un navigateur en entrant l’URI
https://localhost:<port>/todoitems/1
. Par exemple,https://localhost:5001/todoitems/1
La réponse est similaire à celle-ci :
{ "id": 1, "name": "walk dog", "isComplete": true }
Cette application utilise une base de données en mémoire. Si l’application est redémarrée, la requête GET ne retourne aucune donnée. Si aucune donnée n’est retournée, publiez (POST) les données dans l’application et réessayez la requête GET.
Valeurs de retour
ASP.NET Core sérialise automatiquement l’objet en JSON et écrit le JSON dans le corps du message de réponse. Le code de réponse pour ce type de retour est 200 OK, en supposant qu’il n’existe pas d’exception non gérée. Les exceptions non gérées sont converties en erreurs 5xx.
Les types de retour peuvent représenter une large plage de codes d’état HTTP. Par exemple, GET /todoitems/{id}
peut retourner deux valeurs d’état différentes :
- Si aucun élément ne correspond à l’ID demandé, la méthode retourne un code d’erreur Code de statut 404NotFound.
- Sinon, la méthode retourne 200 avec un corps de réponse JSON. Le retour de
item
entraîne une réponse HTTP 200.
Examinez le point de terminaison PUT
L’échantillon d’application implémente un point de terminaison PUT unique à l’aide de 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();
});
Cette méthode est similaire à la méthode MapPost
, sauf qu’elle utilise HTTP PUT. Une réponse réussie retourne 204 (Aucun contenu). D’après la spécification HTTP, une requête PUT nécessite que le client envoie toute l’entité mise à jour, et pas seulement les changements. Pour prendre en charge les mises à jour partielles, utilisez HTTP PATCH.
Testez le point de terminaison PUT
Cet exemple utilise une base de données en mémoire qui doit être initialisée à chaque démarrage de l’application. La base de données doit contenir un élément avant que vous ne passiez un appel PUT. Appelez GET pour vérifier qu’un élément existe dans la base de données avant d’effectuer un appel PUT.
Mettez à jour l’élément de tâche avec Id = 1
et définissez son nom sur "feed fish"
.
Utilisez Swagger pour envoyer une requête PUT :
Sélectionnez Put /todoitems/{id}>Try it out (Essayer).
Définissez le champ id sur
1
.Définissez le corps de la requête sur le JSON suivant :
{ "name": "feed fish", "isComplete": false }
Sélectionnez Exécuter.
Examiner et tester le point de terminaison DELETE
L’échantillon d’application implémente un seul point de terminaison DELETE à l’aide de 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();
});
Utilisez Swagger pour envoyer une requête DELETE :
Sélectionnez DELETE /todoitems/{id}>Try it out (Essayer).
Définissez le champ ID sur
1
, puis sélectionnez Execute (Exécuter).La requête DELETE est envoyée à l’application et la réponse est affichée dans le volet Responses (Réponses). Le corps de la réponse est vide et le code d’état de Server response (Réponse du serveur) est 204.
Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet Todo
. Dans les applications de production, un sous-ensemble du modèle est souvent utilisé pour restreindre les données qui peuvent être entrées et retournées. Il y a plusieurs raisons à cela, et la sécurité en est une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.
Un DTO peut être utilisé pour :
- Empêcher la sur-publication.
- Masquer les propriétés que les clients ne sont pas censés voir.
- Omettez certaines propriétés afin de réduire la taille de la charge utile.
- Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques d’objets aplatis peuvent être plus pratiques pour les clients.
Pour illustrer l’approche DTO, mettez à jour la classe Todo
pour inclure un champ secret :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Le champ secret doit être masqué dans cette application, mais une application administrative peut choisir de l’exposer.
Vérifiez que vous pouvez publier et obtenir le champ secret.
Créez un fichier nommé TodoItemDTO.cs
avec le code suivant :
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);
}
Remplacez le contenu du fichier Program.cs
par le code suivant pour utiliser ce modèle 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>();
}
Vérifiez que vous pouvez publier et obtenir tous les champs à l’exception du champ secret.
Tester l’API minimale
Pour obtenir un échantillon de test d’une application API minimale, consultez cet échantillon GitHub.
Publier sur Azure
Pour plus d’informations sur le déploiement sur Azure, consultez Démarrage rapide : Déployer une application web ASP.NET.
Ressources supplémentaires
Les API minimales sont conçues pour créer des API HTTP ayant des dépendances minimales. Elles sont idéales pour les microservices et les applications pour lesquels vous voulez inclure seulement le minimum de fichiers, de fonctionnalités et de dépendances dans ASP.NET Core.
Ce tutoriel décrit les principes fondamentaux liés à la génération d’une API minimale avec ASP.NET Core. Une autre approche de la création d’API dans ASP.NET Core consiste à utiliser des contrôleurs. Pour obtenir de l’aide avec le choix entre les API minimales et les API basées sur un contrôleur, consultez Vue d’ensemble des API. Pour obtenir un tutoriel sur la création d’un projet d’API basée sur des contrôleurs qui contiennent d’autres fonctionnalités, consultez Créer une API web.
Vue d’ensemble
Ce didacticiel crée l’API suivante :
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenir les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
POST /todoitems |
Ajouter un nouvel élément | Tâche | Tâche |
PUT /todoitems/{id} |
Mettre à jour un élément existant | Tâche | Aucune |
DELETE /todoitems/{id} |
Supprimer un élément | Aucune | Aucune |
Prerequisites
Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
Créez un projet d’API
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
- Entrez
Empty
dans la zone de recherche Rechercher des modèles. - Sélectionnez le modèle ASP.NET Core vide, puis Suivant.
- Entrez
Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
- Sélectionnez .NET 8.0 (support à long terme)
- Décochez la case N’utilisez pas d’instructions de niveau supérieur
- Sélectionnez Créer
Examiner le code
Le fichier Program.cs
contient le code suivant :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Le code précédent :
- Crée un WebApplicationBuilder et un WebApplication avec des valeurs préconfigurées par défaut.
- Crée un point de terminaison HTTP GET
/
qui retourneHello World!
:
Exécuter l’application
Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.
Visual Studio affiche la boîte de dialogue suivante :
Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.
La boîte de dialogue suivante s’affiche :
Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.
Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio lance le Kestrel serveur web et ouvre une fenêtre de navigateur.
Hello World!
s’affiche dans le navigateur. Le fichier Program.cs
contient une application minimale mais complète.
Fermez la fenêtre du navigateur.
Ajouter des packages NuGet
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
- Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Gérer les packages NuGet pour la solution.
- Sélectionnez l’onglet Parcourir.
- Entrez Microsoft.EntityFrameworkCore.InMemory dans la zone de recherche, puis sélectionnez
Microsoft.EntityFrameworkCore.InMemory
. - Cochez la case Projet dans le volet droit, puis sélectionnez Installer.
- Suivez les instructions précédentes pour ajouter le package
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
.
Classes de contexte de modèle et de base de données
- Dans le dossier du projet, créez un fichier nommé
Todo.cs
avec le code suivant :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Le code précédent crée le modèle pour cette application. Un modèle est un ensemble de classes qui représentent les données gérées par l’application.
- Créez un fichier nommé
TodoDb.cs
avec le code suivant :
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Le précédent code définit le contexte de base de données, qui est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données. Cette classe dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
Ajouter le code de l’API
- Remplacez le contenu du fichier
Program.cs
par le code suivant :
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();
Le code suivant mis en surbrillance ajoute le contexte de base de données au conteneur d’injection de dépendances et permet d’afficher les exceptions liées à la base de données :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Le conteneur d’injection de dépendances fournit l’accès au contexte de base de données et à d’autres services.
Ce didacticiel utilise l’Explorateur des points de terminaison et les fichiers .http pour tester l’API.
Testez les données de publication
Le code suivant dans Program.cs
crée un point de terminaison HTTP POST /todoitems
qui ajoute des données à la base de données en mémoire :
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Exécutez l'application. Le navigateur affiche une erreur 404, car il n’y a plus de point de terminaison /
.
Le point de terminaison POST sera utilisé pour ajouter des données à l’application.
Sélectionnez Afficher, > et Explorateur de points de terminaison.
Cliquez avec le bouton droit sur le point de terminaison POST, puis sélectionnez Générer une requête.
Un nouveau fichier est créé dans le dossier de projet nommé
TodoApi.http
, avec un contenu similaire à l’exemple suivant :@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- La première ligne crée une variable qui est utilisée pour tous les points de terminaison.
- La ligne suivante définit une requête POST.
- La triple hashtag (
###
) ligne est un délimiteur de requête : ce qui arrive après qu’il est pour une autre requête.
La requête POST a besoin d’en-têtes et d’un corps. Pour définir ces parties de la requête, ajoutez les lignes suivantes immédiatement après la ligne de requête POST :
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Le code précédent ajoute un en-tête Content-Type et un corps de la demande JSON. Le fichier TodoApi.http doit maintenant ressembler à l’exemple suivant, mais avec votre numéro de port :
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Exécutez l'application.
Sélectionnez le lien Envoyer une requête au-dessus de la ligne de requête
POST
.La requête POST est envoyée à l’application et la réponse s’affiche dans le volet Réponse.
Examinez les points de terminaison GET
L’échantillon d’application implémente plusieurs points de terminaison GET en appelant MapGet
:
API | Description | Corps de la requête | Corps de réponse |
---|---|---|---|
GET /todoitems |
Obtenir toutes les tâches | Aucune | Tableau de tâches |
GET /todoitems/complete |
Obtenez tous les éléments de tâche terminés | Aucune | Tableau de tâches |
GET /todoitems/{id} |
Obtenir un élément par ID | Aucune | Tâche |
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());
Testez les points de terminaison GET
Testez l’application en appelant les GET
points d’extrémité à partir d’un navigateur ou en utilisant l’Explorateur des points de terminaison. Les étapes suivantes concernent l’Explorateur des points de terminaison.
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le premier GET un point de terminaison et sélectionnez Générer une requête.
Le contenu suivant est ajouté au fichier
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de requête
GET
.La requête GET est envoyée à l’application et la réponse s’affiche dans le volet Réponse.
Le corps de la réponse est similaire à JSON suivant :
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le point de terminaison
/todoitems/{id}
GET, puis sélectionnez Générer une requête. Le contenu suivant est ajouté au fichierTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Remplacez
{id}
par1
.Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de requête GET.
La requête GET est envoyée à l’application et la réponse s’affiche dans le volet Réponse.
Le corps de la réponse est similaire à JSON suivant :
{ "id": 1, "name": "walk dog", "isComplete": true }
Cette application utilise une base de données en mémoire. Si l’application est redémarrée, la requête GET ne retourne aucune donnée. Si aucune donnée n’est retournée, publiez (POST) les données dans l’application et réessayez la requête GET.
Valeurs de retour
ASP.NET Core sérialise automatiquement l’objet en JSON et écrit le JSON dans le corps du message de réponse. Le code de réponse pour ce type de retour est 200 OK, en supposant qu’il n’existe pas d’exception non gérée. Les exceptions non gérées sont converties en erreurs 5xx.
Les types de retour peuvent représenter une large plage de codes d’état HTTP. Par exemple, GET /todoitems/{id}
peut retourner deux valeurs d’état différentes :
- Si aucun élément ne correspond à l’ID demandé, la méthode retourne un code d’erreur Code de statut 404NotFound.
- Sinon, la méthode retourne 200 avec un corps de réponse JSON. Le retour de
item
entraîne une réponse HTTP 200.
Examinez le point de terminaison PUT
L’échantillon d’application implémente un point de terminaison PUT unique à l’aide de 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();
});
Cette méthode est similaire à la méthode MapPost
, sauf qu’elle utilise HTTP PUT. Une réponse réussie retourne 204 (Aucun contenu). D’après la spécification HTTP, une requête PUT nécessite que le client envoie toute l’entité mise à jour, et pas seulement les changements. Pour prendre en charge les mises à jour partielles, utilisez HTTP PATCH.
Testez le point de terminaison PUT
Cet exemple utilise une base de données en mémoire qui doit être initialisée à chaque démarrage de l’application. La base de données doit contenir un élément avant que vous ne passiez un appel PUT. Appelez GET pour vérifier qu’un élément existe dans la base de données avant d’effectuer un appel PUT.
Mettez à jour l’élément de tâche avec Id = 1
et définissez son nom sur "feed fish"
.
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le point de terminaison PUT et sélectionnez Générer une requête.
Le contenu suivant est ajouté au fichier
TodoApi.http
:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
Dans la ligne de requête PUT, remplacez
{id}
par1
.Ajoutez les lignes suivantes immédiatement après la ligne de requête PUT :
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Le code précédent ajoute un en-tête Content-Type et un corps de la demande JSON.
Sélectionnez le lien Send request (Envoyer une requête) qui se trouve au-dessus de la ligne de la nouvelle requête PUT.
La requête PUT est envoyée à l’application et la réponse s’affiche dans le volet Réponse. Le corps de la réponse est vide et le code d’état est 204.
Examiner et tester le point de terminaison DELETE
L’échantillon d’application implémente un seul point de terminaison DELETE à l’aide de 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();
});
Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de la souris sur le point de terminaison DELETE et sélectionnez Générer une requête.
Une requête DELETE est ajoutée à
TodoApi.http
.Remplacez
{id}
dans la ligne de requête DELETE par1
. La requête DELETE doit ressembler à l’exemple suivant :DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Sélectionnez le lien Envoyer une requête pour la demande DELETE.
La requête DELETE est envoyée à l’application et la réponse s’affiche dans le volet Réponse. Le corps de la réponse est vide et le code d’état est 204.
Utiliser l’API MapGroup
L’échantillon de code d’application répète le préfixe d’URL todoitems
chaque fois qu’il configure un point de terminaison. Les API ont souvent des groupes de points de terminaison avec un préfixe d’URL commun, et la méthode MapGroup est disponible pour vous aider à organiser ces groupes. Cela réduit le code répétitif et permet de personnaliser des groupes entiers de points de terminaison avec un seul appel à des méthodes comme RequireAuthorization et WithMetadata.
Remplacez le contenu de Program.cs
par le code suivant :
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();
Le code précédent dispose des modifications suivantes :
- Ajoute
var todoItems = app.MapGroup("/todoitems");
pour configurer le groupe à l’aide du préfixe d’URL/todoitems
. - Remplace toutes les méthodes
app.Map<HttpVerb>
partodoItems.Map<HttpVerb>
. - Supprime le préfixe d’URL
/todoitems
des appels de méthodeMap<HttpVerb>
.
Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
Utilisez l’API TypedResults
Retourner TypedResults plutôt que Results présente plusieurs avantages, notamment la testabilité et le retour automatique des métadonnées de type de réponse pour OpenAPI afin de décrire le point de terminaison. Pour plus d'informations, consultez TypedResults vs Results.
Les méthodes Map<HttpVerb>
peuvent appeler des méthodes de gestionnaire de routage au lieu d’utiliser des expressions lambda. Pour voir un exemple, mettez à jour Program.cs avec le code suivant :
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();
}
Le code Map<HttpVerb>
appelle maintenant des méthodes au lieu des 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);
Ces méthodes retournent des objets qui implémentent IResult et sont définis par 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();
}
Les tests unitaires peuvent appeler ces méthodes et vérifier qu’elles retournent le type correct. Par exemple, si la méthode est GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Le code de test unitaire peut vérifier qu’un objet de type Ok<Todo[]> est retourné à partir de la méthode de gestionnaire. Par exemple :
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);
}
Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet Todo
. Dans les applications de production, un sous-ensemble du modèle est souvent utilisé pour restreindre les données qui peuvent être entrées et retournées. Il y a plusieurs raisons à cela, et la sécurité en est une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.
Un DTO peut être utilisé pour :
- Empêcher la sur-publication.
- Masquer les propriétés que les clients ne sont pas censés voir.
- Omettez certaines propriétés afin de réduire la taille de la charge utile.
- Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques d’objets aplatis peuvent être plus pratiques pour les clients.
Pour illustrer l’approche DTO, mettez à jour la classe Todo
pour inclure un champ secret :
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Le champ secret doit être masqué dans cette application, mais une application administrative peut choisir de l’exposer.
Vérifiez que vous pouvez publier et obtenir le champ secret.
Créez un fichier nommé TodoItemDTO.cs
avec le code suivant :
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);
}
Remplacez le contenu du fichier Program.cs
par le code suivant pour utiliser ce modèle 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();
}
Vérifiez que vous pouvez publier et obtenir tous les champs à l’exception du champ secret.
Résolution des problèmes avec l’exemple terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).
Étapes suivantes
- Configurer les options de sérialisation JSON
- Gérer les erreurs et les exceptions : la page d’exceptions du développeur est activée par défaut dans l’environnement de développement pour les applications API minimales. Pour plus d’informations sur la gestion des erreurs et des exceptions, consultez Gérer les erreurs dans les API ASP.NET Core.
- Pour obtenir un échantillon de test d’une application API minimale, consultez cet échantillon GitHub.
- Prise en charge d’OpenAPI dans les API minimales.
- Démarrage rapide : Publier sur Azure.
- Organisation des API minimales d’ASP.NET Core.
En savoir plus
Consultez Informations de référence rapides sur les API minimales.