Validation des données dans les API minimales .NET 10
Les API minimales ont toujours été l'alternative légère aux contrôleurs basés sur ASP.NET Core, mais pendant longtemps, elles avaient un écart notable : pas de support intégré pour valider les données entrantes. Vous aviez soit à faire des vérifications manuelles dans chaque gestionnaire, soit à vous appuyer sur des bibliothèques tierces. .NET 10 comble cet écart avec la validation par annotation de données de première classe pour les chaînes de requête, les en-têtes et les corps des requêtes, le tout sans packages supplémentaires.
Cette plongée approfondie dans les API Minimales .NET 10, basée sur la démonstration de Tim Corey, explique comment enregistrer le service de validation, annoter les classes modèles, et éviter les pièges courants liés aux modificateurs d'accès qui peuvent faire échouer votre configuration.
La Configuration : Une API Minimale Simple
[0:44 - 1:35] La démonstration commence avec un projet API Minimal ASP.NET Core dépouillé fonctionnant sur .NET 10. La surface est intentionnellement petite : deux points de terminaison POST, chacun acceptant un modèle différent.
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));
Le modèle Person contient des champs d'identité de base. LoginModel gère les identifiants : une adresse e-mail, un mot de passe, et un champ de confirmation de mot de passe. Les deux sont envoyés en tant que corps JSON. À ce stade, il n'y a aucun contrôle des entrées ; l'API accepte tout ce qu'elle reçoit, y compris les chaînes vides et les adresses email mal formées.
Scalar (l'interface utilisateur OpenAPI moderne qui accompagne .NET 10) est utilisé pour envoyer des requêtes de test directement depuis le navigateur, ce qui permet de voir exactement ce que l'API renvoie avant et après la mise en place de la validation.
Enregistrement du Service de Validation
[2:36 - 3:06] Avant que tout attribut de validation ne prenne effet, vous devez opter pour cela au niveau du service. Une seule ligne dans votre bloc d'enregistrement de service permet l'application sur tous les points de terminaison d'API Minimale :
builder.Services.AddValidation();
builder.Services.AddValidation();
Cette seule ligne est l'étape de configuration entière. Il n'y a pas de middleware à ajouter, pas d'étape de pipeline à connecter. Le framework prend automatiquement le relais une fois le service enregistré. Si vous omettez cet appel, vos attributs d'annotation de données seront présents sur le modèle mais jamais évalués, et les requêtes passeront indépendamment de leur contenu.
Ceci vaut la peine d'être gardé à l'esprit comme première étape de débogage : si la validation ne fait rien en silence, AddValidation() est généralement la pièce manquante.
Ajouter la Validation à un Modèle de Classe
[3:00 - 3:55] Avec le service enregistré, ajouter la validation à un modèle basé sur une classe consiste à décorer les propriétés avec des attributs d'annotation de données :
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Après avoir ajouté la directive d'utilisation System.ComponentModel.DataAnnotations en haut du fichier, marquer FirstName et LastName comme [Required] garantit qu'ils sont validés. Envoyer une requête POST avec un corps vide via Scalar retourne maintenant un 400 Bad Request avec une réponse d'erreur structurée :
{
"errors": {
"FirstName": ["The FirstName field is required."],
"LastName": ["The LastName field is required."]
}
}
Pas de gestion d'erreur personnalisée, pas d'attributs de filtre. Le framework produit cette réponse automatiquement, et la vérification s'exécute avant que le corps de votre gestionnaire ne s'exécute, donc vous n'avez jamais besoin de vous protéger contre les valeurs nulles à l'intérieur de la logique du point de terminaison.
Appliquer la Validation à un Record
[4:29 - 5:30] Les mêmes attributs fonctionnent sur les enregistrements C#, mais la syntaxe diffère légèrement car les propriétés des enregistrements sont généralement définies dans le constructeur principal plutôt que comme des déclarations de membres séparées.
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);
Les attributs sur les paramètres du constructeur sont appliqués aux propriétés générées, donc [Required] et [EmailAddress] se comportent exactement comme ils le feraient sur une classe. Envoyer une requête avec un e-mail mal formé, par exemple "notanemail", retourne maintenant un 400 qui identifie le champ Email comme invalide.
L'attribut [Compare] peut aussi être utilisé pour imposer que ConfirmPassword corresponde à Password. Sur un enregistrement, les cibles d'attribut ont besoin d'une indication explicite car le compilateur doit savoir que vous voulez dire le membre généré, pas le paramètre du constructeur lui-même :
[property: Compare(nameof(Password))]
string ConfirmPassword
[property: Compare(nameof(Password))]
string ConfirmPassword
La cible [property:] indique au compilateur d'attacher l'attribut au membre généré plutôt qu'au paramètre. Sans cela, [Compare] compile mais ne s'exécute jamais pendant la vérification. C'est la partie la plus délicate à travailler avec les enregistrements vs classes dans ce contexte : les propriétés de classe acceptent naturellement les attributs, tandis que les paramètres des enregistrements ont besoin de la cible explicite pour tout ce qui fonctionne au niveau des membres.
L'Exigence du Modificateur d'Accès Public
[7:51 - 9:00] Un piège courant est facile à manquer et ne produit aucun message d'erreur lorsque vous le rencontrez. Le système de validation utilise la réflexion pour inspecter vos types de modèle à l'exécution ; pour que la réflexion trouve les membres d'un type, le type lui-même doit être marqué public.
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }
La même règle s'applique aux enregistrements. Si votre modèle est déclaré sans modificateur d'accès, C# par défaut est internal, et le service l'ignore complètement. Votre point de terminaison reçoit toujours la requête, le gestionnaire s'exécute toujours et aucune erreur n'est renvoyée ; les vérifications ne font simplement rien silencieusement.
Il s'agit d'un comportement de ASP.NET Core, non spécifique aux APIs minimales, mais cela se manifeste plus souvent ici parce que ces projets tendent à être compacts et les développeurs définissent parfois des modèles en ligne ou dans le même fichier que Program.cs sans penser à la visibilité.
Une manière rapide d'auditer votre projet : tout type de modèle transmis à un gestionnaire de point d'accès devrait être explicitement marqué public. Sinon, aucun nombre d'attributs ne fera démarrer le framework.
Ce Que Vous Pouvez Valider
Les attributs d'annotation de données intégrés couvrent les scénarios les plus courants sans aucun travail supplémentaire :
[Required]rejette les valeurs nulles ou vides[EmailAddress]valide le format d'une chaîne d'e-mail[Compare]vérifie que deux propriétés correspondent, utile pour la confirmation de mot de passe[Range]impose des limites numériques ou de date[StringLength]limite la longueur de la chaîne avec un minimum optionnel[RegularExpression]valide selon un modèle personnalisé
Tous ces travaux traversent les paramètres de chaîne de requête, les en-têtes de requête, et les corps JSON. La même classe modèle ou enregistrement peut être lié à partir de différentes sources sans modifier aucun de ses attributs.
Conclusion
[7:46 - fin] Avec AddValidation() enregistré et vos modèles décorés, les APIs minimales appliquent automatiquement des contraintes d'entrée à travers les classes et les enregistrements. Pour que cela fonctionne sous .NET 10, il suffit d'appeler builder.Services.AddValidation() une fois, de décorer vos propriétés de modèle avec des attributs d'annotations de données standard, et de s'assurer que chaque type de modèle est marqué public.
La cible [property:] sur les enregistrements et l'exigence du modificateur public sont les seuls véritables pièges. Ils sont faciles à ignorer et produisent des échecs silencieux plutôt que des erreurs de compilation, alors gardez-les sur votre liste de contrôle chaque fois que les vérifications d'entrée semblent ne rien faire.
Regardez la vidéo complète sur la chaîne de Tim Corey sur YouTube pour une démonstration complète du code source.
