Une API est un contrat public. Sa qualité se juge sur la prévisibilité : codes HTTP justes, erreurs normalisées, versionnement, idempotence et comportement sous charge.
Chapitres du guide
En clair
Une API REST, c'est un guichet : le client envoie une demande HTTP (« donne-moi la commande 42 »), le serveur répond avec un code (200 = OK, 404 = introuvable…) et des données au format JSON. Avant d'arriver au code qui la traite, la demande passe une série de contrôles en file : sécurité, droits, quotas.
Le pipeline de middleware Junior
Chaque requête traverse les middlewares dans l'ordre d'enregistrement, atteint l'endpoint, puis la réponse remonte dans l'ordre inverse. L'ordre est sémantique : l'authentification doit précéder l'autorisation, la gestion d'erreurs doit envelopper tout le reste.
Program.cs — ordre recommandé
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorizationBuilder().AddPolicy("orders:write", p => p.RequireClaim("scope","orders.write"));
builder.Services.AddRateLimiter(o => o.AddFixedWindowLimiter("api", w =>{ w.PermitLimit =100; w.Window = TimeSpan.FromMinutes(1);}));
builder.Services.AddOpenApi();// OpenAPI intégré depuis .NET 9
builder.Services.AddHealthChecks().AddDbContextCheck<ShopDbContext>();var app = builder.Build();
app.UseExceptionHandler();// 1. enveloppe toutif(!app.Environment.IsDevelopment()) app.UseHsts();
app.UseHttpsRedirection();
app.UseCors("front");// avant l'auth pour les preflight
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
app.MapOpenApi();
app.MapHealthChecks("/health/ready");
app.MapOrders();// groupes d'endpoints par fonctionnalité
app.Run();
Controllers, Minimal APIs ou FastEndpoints Junior
Approche
Forces
Limites
Pour
Controllers MVC
Connue de tous, filtres, conventions
Contrôleurs qui grossissent, réflexion
Existant, grosses équipes hétérogènes
Minimal APIs
Légères, rapides, compatibles AOT, TypedResults
À organiser soi-même (MapGroup, extensions)
Nouveaux services, vertical slices
FastEndpoints
Pattern REPR (un endpoint = une classe), validation intégrée
Dépendance tierce
Vertical slices structurées
Le contrat HTTP Junior
Méthode
Sémantique
Idempotente
Succès typique
GET
Lire
Oui
200, 304
POST
Créer, déclencher une action
Non → clé d'idempotence
201 + Location, 202
PUT
Remplacer entièrement
Oui
200, 204
PATCH
Modifier partiellement
Pas garanti
200, 204
DELETE
Supprimer
Oui
204
Code
Quand
400
Requête mal formée, validation échouée (avec errors par champ)
401 / 403
Non authentifié / authentifié sans droit
404
Ressource introuvable (ou masquée pour ne pas révéler son existence)
409
Conflit d'état (commande déjà validée, doublon)
412
If-Match ne correspond plus : modification concurrente
422
Syntaxe correcte mais règle métier violée (choix d'équipe vs 400, à documenter)
429
Quota dépassé, avec Retry-After
500 / 503
Erreur interne (jamais de stack trace exposée) / indisponible temporairement
Erreurs normalisées : ProblemDetails (RFC 9457)
{"type":"https://api.shop.fr/errors/order-already-submitted","title":"Commande déjà validée","status":409,"detail":"La commande 7f3c… ne peut plus être modifiée.","instance":"/orders/7f3c…/lines","traceId":"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}
Versionnement : dans l'URL (/v1/) pour la lisibilité, ou par en-tête ; package Asp.Versioning.Http. Ne jamais casser un contrat publié.
Pagination : par curseur (?after=…&limit=50) pour les gros volumes, l'offset devient lent et instable quand les données bougent.
Concurrence optimiste : renvoyer un ETag, exiger If-Match sur PUT/PATCH, répondre 412 en cas de conflit.
Idempotence des POST : en-tête Idempotency-Key stocké avec la réponse ; un rejeu renvoie la même réponse sans retraiter.
DTO dédiés : ne jamais exposer les entités EF (sur-exposition, boucles de sérialisation, couplage au schéma).
Injection de dépendances : les durées de vie Confirmé
Démo · Singleton, Scoped, Transient
Chaque requête résout le service deux fois (contrôleur + handler). Le numéro identifie l'instance ; la couleur, la requête.
SingletonScopedTransient
Durée
Instance
Exemples
Singleton
Une pour toute l'appli — doit être thread-safe
Configuration, cache, HttpClient via factory, clients Pub/Sub
Scoped
Une par requête HTTP (ou par scope créé)
DbContext, unité de travail, utilisateur courant
Transient
Nouvelle à chaque résolution
Services légers sans état
Piège
Dépendance captive : un singleton qui reçoit un service scoped (un DbContext) le garde vivant pour toujours, partagé entre requêtes. Activez ValidateScopes et ValidateOnBuild (actifs par défaut en Development). Dans un BackgroundService, créez un scope avec IServiceScopeFactory.
new HttpClient() par appel : épuisement des sockets. Un HttpClient statique éternel : DNS jamais rafraîchi. Utilisez IHttpClientFactory (clients typés).
Options non validées : une clé de config manquante découverte au premier appel en production. .ValidateDataAnnotations().ValidateOnStart().
Codes HTTP exacts, erreurs en ProblemDetails, POST rendus idempotents.
Scoped pour le DbContext, jamais injecté dans un singleton.
Un projet .NET à cadrer ou à reprendre ?
API REST documentées et testées, applications ASP.NET MVC (y compris VB.NET), migration de services anciens vers une architecture claire : parlons de votre code.