Ajout d'un point de terminaison Get By ID dans .NET Aspire sur Linux
[[academy-video-youtube({"vid": "hMhA5sLHUpY", "start_time": "0", "title": "Ajout d'un point de terminaison Get par ID dans .NET Aspire sur Linux", "creator": "Tim Corey", "length": "15m 20s"})]]
Retourner chaque enregistrement d'une table de base de données est utile pour énumérer des pages, mais la plupart des consommateurs d'API doivent également récupérer un enregistrement unique par son identifiant. Ce deuxième point d'extrémité introduit des décisions que le chemin 'tout récupérer' n'exigeait pas : quel type de retour a du sens pour un objet unique par rapport à une collection, que se passe-t-il lorsque l'ID ne correspond à aucun enregistrement, et comment communiquer cet échec à l'appelant avec le bon code de statut HTTP.
Dans sa vidéo "Ajout d'un endpoint Get By ID dans .NET Aspire sur Linux", Tim Corey continue l'API Tiny Ticket en ajoutant un endpoint GET /api/tickets/{id}. Ce qui commence par un copier-coller de la route existante se transforme en une démonstration en direct de la gestion des cas de bord : retourner un seul objet au lieu d'un tableau, vérifier les résultats nuls et utiliser TypedResults pour retourner soit un 200 OK avec le ticket soit un 404 Not Found lorsque l'ID n'existe pas. Si vous suivez la série C# sur Linux ou construisez des APIs minimales nécessitant des réponses correctes du code de statut, cet épisode couvre tout le processus de réflexion.
Copier le chemin Get All comme point de départ
[0:35 - 1:45] Tim ouvre le service API Program.cs et duplique le endpoint GET /api/tickets existant. La nouvelle route nécessite un paramètre de chemin pour l'ID du ticket, donc le modèle d'URL change pour inclure {id:int} et le gestionnaire reçoit un paramètre int id. La référence de procédure stockée passe de spTickets_GetAll à spTickets_Get, qui s'attend à un paramètre ID.
app.MapGet("/api/tickets/{id:int}", async (int id, IDbConnection db) =>
{
var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
// Initial version: returns a list, which we'll fix next
return tickets;
});
app.MapGet("/api/tickets/{id:int}", async (int id, IDbConnection db) =>
{
var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
// Initial version: returns a list, which we'll fix next
return tickets;
});
Une commodité de nommage à noter : le paramètre de procédure stockée est en minuscules id, ce qui correspond exactement au nom du paramètre C#. Cela signifie que Dapper peut mapper directement l'objet anonyme new { id } sans spécifier de nom de propriété. Si le paramètre SQL utilisait une casse différente, l'objet anonyme aurait besoin d'un assignement de propriété explicite comme new { Id = id }.
Retourner un seul objet au lieu d'une liste
[2:39 - 4:16] Le premier test via Swagger révèle un problème : passer ID 2 retourne un 200 avec le billet correct, mais le corps de la réponse est enveloppé dans un tableau JSON. Lorsqu'un appelant demande une ressource unique par ID, il s'attend à un objet unique, pas à une collection contenant un élément.
L'ajout de .FirstOrDefault() au résultat de la requête corrige le problème d'encapsulation. FirstOrDefault retourne le premier élément si la liste contient des éléments, ou null si la liste est vide. Cela résout le problème de tableau, mais cela introduit une nouvelle question : que doit retourner l'API lorsque l'ID ne correspond à aucun enregistrement ?
var output = tickets.FirstOrDefault();
var output = tickets.FirstOrDefault();
Le changement d'une ligne produit la bonne forme de réponse pour les enregistrements existants. Cependant, tester avec l'ID 4, qui n'existe pas dans la base de données, révèle un écart plus profond. La réponse revient comme null avec un code de statut 200. C'est techniquement un HTTP valide, mais cela induit en erreur l'appelant : un 200 signifie que la requête a réussi et que la ressource a été trouvée, alors qu'en réalité, rien ne correspondait.
Gérer Non Trouvé avec TypedResults
[4:41 - 11:44] Cette section est là où Tim travaille à travers les décisions de conception en temps réel, ce qui rend cela précieux à regarder plutôt que de simplement lire le code final. Son processus de réflexion passe par plusieurs itérations :
D'abord, il envisage d'utiliser .First() au lieu de .FirstOrDefault(), qui déclenche une exception lorsque la liste est vide. Cela produit une erreur 500, ce qui est pire qu'un null 200 parce que 500 implique un bug du serveur plutôt qu'une ressource manquante.
Ensuite, il revient en arrière et construit une vérification de null. Il stocke le résultat dans une variable, vérifie s'il est null, et retourne différentes réponses pour chaque cas. Le défi est qu'un gestionnaire d'API minimale doit déclarer explicitement son type de retour lorsqu'il peut retourner plus d'une forme de réponse.
La solution est TypedResults, qui vous permet de spécifier les types de réponse possibles dans la signature de la méthode :
app.MapGet("/api/tickets/{id:int}", async Task<Results<Ok<TicketModel>, NotFound>> (int id, IDbConnection db) =>
{
var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
var output = tickets?.FirstOrDefault();
if (output is null)
{
return TypedResults.NotFound();
}
return TypedResults.Ok(output);
});
app.MapGet("/api/tickets/{id:int}", async Task<Results<Ok<TicketModel>, NotFound>> (int id, IDbConnection db) =>
{
var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
var output = tickets?.FirstOrDefault();
if (output is null)
{
return TypedResults.NotFound();
}
return TypedResults.Ok(output);
});
Ce type de retour, Task<Results<Ok<TicketModel>, NotFound>>, indique au framework (et à Swagger) que ce endpoint produit soit un 200 avec un corps TicketModel, soit un 404 sans corps. La correspondance de parenthèses colorées dans VS Code aide à naviguer dans les crochets d'angle imbriqués, qui s'empilent rapidement avec des types de résultats génériques.
Un bug subtil fait surface lors des tests : la première version appelle TypedResults.NotFound() mais ne return pas. Le endpoint compile car NotFound() est une expression valide, mais sans le mot-clé return, l'exécution tombe dans le chemin Ok. Tim détecte cela lorsque Swagger montre encore un 200 pour un ID manquant, ajoute le return, et le 404 apparaît correctement à la prochaine exécution.
Tester les deux chemins dans Swagger
[11:44 - 14:09] With the final code in place, Tim runs through both scenarios in the Swagger UI. Passer ID 3 retourne un 200 avec l'objet billet. Passer ID 4 retourne un 404 avec un corps de réponse vide.
Il souligne également un détail dans l'interface Swagger qui peut confondre les nouveaux utilisateurs : la section "Réponses" sous le bouton d'exécution montre les codes de réponse possibles (200 et 404), pas le résultat réel. La réponse du serveur réelle apparaît dans un panneau séparé au-dessus de cette section. Mélanger les deux panneaux est une source courante de confusion "pourquoi j'obtiens un 200 ?".
L'approche TypedResults améliore également automatiquement la documentation Swagger. Parce que le type de retour déclare à la fois Ok<TicketModel> et NotFound, Swagger affiche les deux comme des résultats potentiels avec leurs schémas respectifs. Les appelants lisant la documentation API savent qu'ils doivent gérer un cas 404 sans que le développeur écrive des annotations OpenAPI séparées.
En conclusion : Cas limites avant les fonctionnalités
[14:09 - 15:09] Ce qui a commencé comme un simple copier-coller du point d'extrémité obtenir-tout s'est transformé en un exercice plus approfondi de conception d'API. La version finale gère le chemin heureux (enregistrement trouvé), l'échec prévu (enregistrement non trouvé) et une vérification de null défensive pour des scénarios inattendus (requête retournant null). L'approche de Tim consistant à travailler à travers ces cas en direct, au lieu de présenter un code poli, démontre le type de réflexion itérative que les points de terminaison de production nécessitent.
Conclusion
[15:09 - 15:20] Ajouter un endpoint get-by-ID à une API minimale implique trois décisions au-delà de la définition de la route : utiliser FirstOrDefault pour dérouler la collection, vérifier la nullité pour distinguer "non trouvé" de "trouvé" et déclarer TypedResults dans le type de retour afin que le framework retourne le code de statut HTTP correct. Le modèle Results<Ok<t>, NotFound> est réutilisable dans tout endpoint qui doit communiquer succès ou absence.
Navigation de la série : Cet article fait partie de la série C# sur Linux qui construit l'application Tiny Ticket. Précédent : Ajouter Swagger UI. Prochain : Ajouter un point d'extrémité d'insertion POST.
Conseil Exemple : Lorsque votre gestionnaire API minimal retourne plusieurs codes de statut possibles, déclarez-les toujours dans le Results<> générique. Cela génère automatiquement une documentation Swagger précise et force le compilateur à vérifier que chaque chemin de code retourne un type de résultat valide.
Regardez la vidéo complète sur sa chaîne YouTube et obtenez plus d'informations sur la construction de points d'extrémité API robustes dans la série C# sur Linux.
