Événement
31 mars, 23 h - 2 avr., 23 h
L’événement de la communauté Microsoft Fabric, Power BI, SQL et AI ultime. 31 mars au 2 avril 2025.
S’inscrire maintenantCe navigateur n’est plus pris en charge.
Effectuez une mise à niveau vers Microsoft Edge pour tirer parti des dernières fonctionnalités, des mises à jour de sécurité et du support technique.
Note
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.
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 |
Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
Empty
dans la zone de recherche Rechercher des modèles.Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
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 :
/
qui retourne Hello World!
: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.
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
Microsoft.EntityFrameworkCore.InMemory
.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
.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.
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.
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.
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
###
###
) 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.
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 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 fichier TodoApi.http
:
GET {{TodoApi_HostAddress}}/todoitems/{id}
###
Remplacez {id}
par 1
.
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.
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 :
item
entraîne une réponse HTTP 200.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.
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}
par 1
.
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.
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 par 1
. 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.
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 :
var todoItems = app.MapGroup("/todoitems");
pour configurer le groupe à l’aide du préfixe d’URL /todoitems
.app.Map<HttpVerb>
par todoItems.Map<HttpVerb>
./todoitems
des appels de méthode Map<HttpVerb>
.Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
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);
}
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 :
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.
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).
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.
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 |
Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
Empty
dans la zone de recherche Rechercher des modèles.Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
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 :
/
qui retourne Hello World!
: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.
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
Microsoft.EntityFrameworkCore.InMemory
.7.0.17
, puis sélectionnez Installer.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
avec la version 7 la plus récente disponible.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.
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.
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 :
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.
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.
Ajoutez le code mis en surbrillance suivant avant la définition de app
dans la ligne var app = builder.Build();
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
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 ligne var app = builder.Build();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
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.
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 :
id
a été défini sur 1
.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.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 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 :
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.
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 :
item
entraîne une réponse HTTP 200.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.
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.
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.
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 :
var todoItems = app.MapGroup("/todoitems");
pour configurer le groupe à l’aide du préfixe d’URL /todoitems
.app.Map<HttpVerb>
par todoItems.Map<HttpVerb>
./todoitems
des appels de méthode Map<HttpVerb>
.Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
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);
}
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 :
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.
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).
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.
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 |
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
Empty
dans la zone de recherche Rechercher des modèles.Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
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 :
/
qui retourne Hello World!
: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.
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
Microsoft.EntityFrameworkCore.InMemory
.6.0.28
, puis sélectionnez Installer.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
avec la version 7 la plus récente disponible.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.
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.
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 :
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.
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.
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 ligne var app = builder.Build();
using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
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 ligne var app = builder.Build();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
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.
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 :
id
a été défini sur 1
.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.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 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 :
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.
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 :
item
entraîne une réponse HTTP 200.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.
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.
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.
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 :
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.
Pour obtenir un échantillon de test d’une application API minimale, consultez cet échantillon GitHub.
Pour plus d’informations sur le déploiement sur Azure, consultez Démarrage rapide : Déployer une application web ASP.NET.
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.
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 |
Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.
Démarrez Visual Studio 2022 et sélectionnez Créer un projet.
Dans la boîte de dialogue Créer un projet :
Empty
dans la zone de recherche Rechercher des modèles.Nommez le projet TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
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 :
/
qui retourne Hello World!
: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.
Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et les diagnostics utilisés dans ce tutoriel.
Microsoft.EntityFrameworkCore.InMemory
.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
.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.
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.
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.
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
###
###
) 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.
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 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 fichier TodoApi.http
:
GET {{TodoApi_HostAddress}}/todoitems/{id}
###
Remplacez {id}
par 1
.
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.
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 :
item
entraîne une réponse HTTP 200.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.
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}
par 1
.
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.
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 par 1
. 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.
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 :
var todoItems = app.MapGroup("/todoitems");
pour configurer le groupe à l’aide du préfixe d’URL /todoitems
.app.Map<HttpVerb>
par todoItems.Map<HttpVerb>
./todoitems
des appels de méthode Map<HttpVerb>
.Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
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);
}
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 :
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.
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).
Consultez Informations de référence rapides sur les API minimales.
Commentaires sur ASP.NET Core
ASP.NET Core est un projet open source. Sélectionnez un lien pour fournir des commentaires :
Événement
31 mars, 23 h - 2 avr., 23 h
L’événement de la communauté Microsoft Fabric, Power BI, SQL et AI ultime. 31 mars au 2 avril 2025.
S’inscrire maintenantFormation
Module
Créer une API web avec des contrôleurs ASP.NET Core - Training
Créez un service RESTful avec des contrôleurs ASP.NET Core, qui prend en charge les opérations CRUD (Create, Read, Update, Delete).