Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Remarque
EF4.1 et versions ultérieures uniquement - Les fonctionnalités, API, etc. abordées dans cette page ont été introduites dans Entity Framework 4.1. Si vous utilisez une version antérieure, certaines ou toutes les informations ne s’appliquent pas
Le contenu de cette page est adapté à partir d’un article écrit à l’origine par Julie Lerman (https://thedatafarm.com).
Entity Framework fournit une grande variété de fonctionnalités de validation qui peuvent se nourrir d’une interface utilisateur pour la validation côté client ou être utilisée pour la validation côté serveur. Lors de l’utilisation du code en premier, vous pouvez spécifier des validations à l’aide d’annotations ou de configurations d’API Fluent. Des validations supplémentaires, et plus complexes, peuvent être spécifiées dans le code et fonctionnent si votre modèle provient d’abord du code, du modèle en premier ou de la base de données.
Le modèle
Je vais illustrer les validations avec une paire simple de classes : Blog et Billet.
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Annotations de données
Code First utilise des annotations de l’assembly System.ComponentModel.DataAnnotations comme un moyen de configurer les premières classes de code. Parmi ces annotations figurent celles qui fournissent des règles telles que le Required, MaxLength et MinLength. Un certain nombre d’applications clientes .NET reconnaissent également ces annotations, par exemple ASP.NET MVC. Vous pouvez obtenir la validation côté client et côté serveur avec ces annotations. Par exemple, vous pouvez forcer la propriété Blog Title à être une propriété requise.
[Required]
public string Title { get; set; }
Sans modification supplémentaire du code ou du balisage dans l’application, une application MVC existante effectue une validation côté client, même en créant dynamiquement un message à l’aide des noms de propriété et d’annotation.
Dans la méthode post-back de cette vue Create, Entity Framework est utilisé pour enregistrer le nouveau blog dans la base de données, mais la validation côté client de MVC est déclenchée avant que l’application atteigne ce code.
Toutefois, la validation côté client n'est pas infaillible. Les utilisateurs peuvent avoir un impact sur les fonctionnalités de leur navigateur ou pire encore, un pirate peut utiliser des astuces pour éviter les validations de l’interface utilisateur. Mais Entity Framework reconnaît également l’annotation Required et la valide.
Un moyen simple de le tester consiste à désactiver la fonctionnalité de validation côté client de MVC. Vous pouvez le faire dans le fichier web.config de l’application MVC. La section appSettings a une clé pour ClientValidationEnabled. La définition de cette clé sur false empêche l’interface utilisateur d’effectuer des validations.
<appSettings>
<add key="ClientValidationEnabled"value="false"/>
...
</appSettings>
Même si la validation côté client est désactivée, vous obtiendrez la même réponse dans votre application. Le message d’erreur « Le champ Titre est requis » s’affiche comme précédemment. Désormais, cela sera dû à la validation côté serveur. Entity Framework effectue la validation sur l’annotation Required (avant même qu’elle ne prenne la peine de générer une commande INSERT à envoyer à la base de données) puis retourne l'erreur à MVC, qui affiche le message.
Fluent API
Vous pouvez utiliser l'API fluente de Code First au lieu des annotations pour obtenir la même validation côté client et côté serveur. Au lieu d’utiliser Required, je vais vous montrer cela à l’aide d’une validation MaxLength.
Les configurations de l'API Fluent sont appliquées lorsque le code commence par construire le modèle à partir des classes. Vous pouvez injecter les configurations en remplaçant la méthode OnModelCreating de la classe DbContext. Voici une configuration spécifiant que la propriété BloggerName ne peut pas dépasser 10 caractères.
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(p => p.BloggerName).HasMaxLength(10);
}
}
Les erreurs de validation levées en fonction des configurations de l’API Fluent n’atteignent pas automatiquement l’interface utilisateur, mais vous pouvez la capturer dans le code, puis y répondre en conséquence.
Voici un code d’erreur de gestion des exceptions dans la classe BlogController de l’application qui capture cette erreur de validation lorsque Entity Framework tente d’enregistrer un blog avec un BloggerName qui dépasse le maximum de 10 caractères.
[HttpPost]
public ActionResult Edit(int id, Blog blog)
{
try
{
db.Entry(blog).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbEntityValidationException ex)
{
var error = ex.EntityValidationErrors.First().ValidationErrors.First();
this.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
return View();
}
}
La validation ne revient pas automatiquement dans la vue. C'est pourquoi on utilise le code supplémentaire avec ModelState.AddModelError. Cela garantit que les détails de l’erreur parviennent à l’affichage, qui utilisera ensuite le Htmlhelper ValidationMessageFor pour afficher l’erreur.
@Html.ValidationMessageFor(model => model.BloggerName)
IValidatableObject
IValidatableObject est une interface qui réside dans System.ComponentModel.DataAnnotations. Bien qu’elle ne fait pas partie de l’API Entity Framework, vous pouvez toujours l’exploiter pour la validation côté serveur dans vos classes Entity Framework.
IValidatableObject fournit une Validate méthode qu'Entity Framework appelle lors de l'exécution de SaveChanges ou que vous pouvez appeler vous-même à tout moment pour valider les classes.
Configurations telles que Required et MaxLength effectuent la validation sur un seul champ. Dans la Validate méthode, vous pouvez avoir une logique encore plus complexe, par exemple, comparer deux champs.
Dans l’exemple suivant, la Blog classe a été étendue pour implémenter IValidatableObject, puis fournir une règle selon laquelle Title et BloggerName ne puissent pas correspondre.
public class Blog : IValidatableObject
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string BloggerName { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == BloggerName)
{
yield return new ValidationResult(
"Blog Title cannot match Blogger Name",
new[] { nameof(Title), nameof(BloggerName) });
}
}
}
Le ValidationResult constructeur accepte un string qui représente le message d’erreur et un tableau de strings qui représentent les noms de membres associés à la validation. Étant donné que cette validation vérifie à la fois les Title et les BloggerName, les deux noms de propriétés sont retournés.
Contrairement à la validation fournie par l’API Fluent, ce résultat de validation est reconnu par la vue et le gestionnaire d’exceptions que j’ai utilisé précédemment pour ajouter l’erreur ModelState est inutile. Étant donné que j’ai défini les deux noms de propriétés dans le ValidationResultfichier , le MVC HtmlHelpers affiche le message d’erreur pour ces deux propriétés.
DbContext.ValidateEntity
DbContext a une méthode substituable appelée ValidateEntity. Lorsque vous appelez SaveChanges, Entity Framework appelle cette méthode pour chaque entité dans son cache dont l’état n’est pas Unchanged. Vous pouvez placer la logique de validation directement ici ou même utiliser cette méthode pour appeler, par exemple, la Blog.Validate méthode ajoutée dans la section précédente.
Voici un exemple de ValidateEntity remplacement qui valide de nouveaux Post pour s'assurer que le titre du billet n’a pas déjà été utilisé. Il vérifie d’abord si l’entité est une publication et s’assure que son état est 'Added'. Si c’est le cas, il cherche dans la base de données pour voir s’il existe déjà un billet portant le même titre. S’il existe déjà un billet existant, une nouvelle DbEntityValidationResult publication est créée.
DbEntityValidationResult abrite une DbEntityEntry et une ICollection<DbValidationErrors> pour une entité unique. Au début de cette méthode, un DbEntityValidationResult est instancié, puis toutes les erreurs identifiées sont ajoutées à sa ValidationErrors collection.
protected override DbEntityValidationResult ValidateEntity (
System.Data.Entity.Infrastructure.DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
if (entityEntry.Entity is Post post && entityEntry.State == EntityState.Added)
{
// Check for uniqueness of post title
if (Posts.Where(p => p.Title == post.Title).Any())
{
result.ValidationErrors.Add(
new System.Data.Entity.Validation.DbValidationError(
nameof(Title),
"Post title must be unique."));
}
}
if (result.ValidationErrors.Count > 0)
{
return result;
}
else
{
return base.ValidateEntity(entityEntry, items);
}
}
Déclenchement explicite de la validation
Un appel à SaveChanges déclenche toutes les validations abordées dans cet article. Mais vous n’avez pas besoin de compter sur SaveChanges. Vous préférez peut-être valider ailleurs dans votre application.
DbContext.GetValidationErrors déclenche toutes les validations, celles définies par les annotations ou l’API Fluent, la validation créée dans IValidatableObject (par exemple), Blog.Validateet les validations effectuées dans la DbContext.ValidateEntity méthode.
Le code suivant appellera GetValidationErrors sur l’instance actuelle d’un DbContext.
ValidationErrors sont regroupés par type d’entité en DbEntityValidationResult. Le code itère d’abord à travers les DbEntityValidationResults retournés par la méthode, puis à travers chacun des DbValidationError à l'intérieur.
foreach (var validationResult in db.GetValidationErrors())
{
foreach (var error in validationResult.ValidationErrors)
{
Debug.WriteLine(
"Entity Property: {0}, Error {1}",
error.PropertyName,
error.ErrorMessage);
}
}
Autres considérations lors de l’utilisation de la validation
Voici quelques autres points à prendre en compte lors de l’utilisation de la validation Entity Framework :
- Le chargement différé est désactivé pendant la validation
- EF valide les annotations de données sur les propriétés non mappées (propriétés qui ne sont pas mappées à une colonne de la base de données)
- La validation est effectuée une fois les modifications détectées pendant
SaveChanges. Si vous apportez des modifications lors de la validation, il vous incombe de notifier le suivi des modifications -
DbUnexpectedValidationExceptionest levée si des erreurs se produisent pendant la validation - Les facettes que Entity Framework inclut dans le modèle (longueur maximale, obligatoire, etc.) entraînent la validation, même s’il n’existe aucune annotation de données dans vos classes et/ou si vous avez utilisé ef Designer pour créer votre modèle
- Règles de précédence :
- Les appels d’API Fluent remplacent les annotations de données correspondantes
- Ordre d’exécution :
- La validation de propriété se produit avant la validation de type
- La validation de type se produit uniquement si la validation de propriété réussit
- Si une propriété est complexe, sa validation inclut également :
- Validation au niveau de la propriété sur les propriétés de type complexes
- Validation au niveau du type pour le type complexe, incluant la validation sur le type complexe
IValidatableObject
Résumé
L’API de validation dans Entity Framework joue très bien avec la validation côté client dans MVC, mais vous n’avez pas à vous appuyer sur la validation côté client. Entity Framework prendra en charge la validation côté serveur pour les DataAnnotations ou les configurations que vous avez appliquées avec l'API Fluent en mode Code First.
Vous avez également vu un certain nombre de points d’extensibilité pour personnaliser le comportement, que vous utilisiez l’interface IValidatableObject ou appuyez dans la DbContext.ValidateEntity méthode. Ces deux derniers moyens de validation sont disponibles via le DbContext, que vous utilisiez le flux de travail Code First, Model First ou Database First pour décrire votre modèle conceptuel.