Gegevensvalidatie

Opmerking

EF4.1 en later: De op deze pagina besproken functies, API's, enzovoort, zijn geïntroduceerd in Entity Framework 4.1. Als u een eerdere versie gebruikt, zijn sommige of alle gegevens niet van toepassing

De inhoud op deze pagina is aangepast uit een artikel dat oorspronkelijk is geschreven door Julie Lerman (https://thedatafarm.com).

Entity Framework biedt een groot aantal validatiefuncties die kunnen worden doorgevoerd in een gebruikersinterface voor validatie aan de clientzijde of kunnen worden gebruikt voor validatie aan de serverzijde. Wanneer u code eerst gebruikt, kunt u validaties opgeven met behulp van aantekeningen of fluent API-configuraties. Aanvullende validaties, en wel complexere, kunnen worden opgegeven in code en zullen werken ongeacht of uw model afkomstig is van code-first, model-first of database-first.

Het model

Ik laat de validaties zien met een eenvoudig paar klassen: Blog en Post.

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; }
}

Gegevensaantekeningen

Code First maakt gebruik van aantekeningen uit de System.ComponentModel.DataAnnotations assembly als een manier om Code First-klassen te configureren. Onder deze aantekeningen vallen de aantekeningen die regels bieden, zoals de Required, MaxLength en MinLength. Een aantal .NET-clienttoepassingen herkent deze aantekeningen ook, bijvoorbeeld ASP.NET MVC. U kunt zowel client- als serverzijdevalidatie bereiken met deze aantekeningen. U kunt bijvoorbeeld afdwingen dat de eigenschap Blogtitel een vereiste eigenschap is.

[Required]
public string Title { get; set; }

Zonder extra code- of markeringswijzigingen in de toepassing zal een bestaande MVC-toepassing client-side validatie uitvoeren, zelfs dynamisch een bericht samenstellen met behulp van de eigenschaps- en annotatienamen.

afbeelding 1

In de postback-methode van deze weergave Maken wordt Entity Framework gebruikt om het nieuwe blog op te slaan in de database, maar de validatie aan de clientzijde van MVC wordt geactiveerd voordat de toepassing die code bereikt.

Validatie aan clientzijde is echter niet waterdicht. Gebruikers kunnen de functies van hun browser beïnvloeden of nog erger, een hacker kan wat trucjes gebruiken om de UI-validaties te voorkomen. Maar Entity Framework herkent ook de Required aantekening en valideert deze.

Een eenvoudige manier om dit te testen, is door de validatiefunctie aan de clientzijde van MVC uit te schakelen. U kunt dit doen in het web.config-bestand van de MVC-toepassing. De sectie appSettings heeft een sleutel voor ClientValidationEnabled. Als u deze sleutel instelt op false, wordt voorkomen dat de gebruikersinterface validaties uitvoert.

<appSettings>
    <add key="ClientValidationEnabled"value="false"/>
    ...
</appSettings>

Zelfs als de validatie aan de clientzijde is uitgeschakeld, krijgt u hetzelfde antwoord in uw toepassing. Het foutbericht 'Het veld Titel is vereist' wordt weergegeven zoals voorheen. Behalve dat dit nu het resultaat is van validatie aan de serverzijde. Entity Framework voert de validatie uit op de Required aantekening (voordat het zelfs de moeite neemt om een INSERT opdracht te maken om naar de database te verzenden) en de fout aan MVC retourneert die het bericht weergeeft.

Fluent-API

U kunt de fluent-API van code eerst gebruiken in plaats van aantekeningen om dezelfde validatie aan de clientzijde en serverzijde te verkrijgen. In plaats van te gebruiken Required, zal ik u dit laten zien met behulp van een MaxLength-validatie.

Fluent API-configuraties worden toegepast als code eerst het model bouwt vanuit de klassen. U kunt de configuraties injecteren door de OnModelCreating-methode van de DbContext-klasse te overschrijven. Hier volgt een configuratie die aangeeft dat de eigenschap BloggerName niet langer mag zijn dan 10 tekens.

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);
    }
}

Validatiefouten die zijn opgetreden op basis van de Fluent API-configuraties, bereiken niet automatisch de gebruikersinterface, maar u kunt deze vastleggen in code en erop reageren.

Hier is een foutafhandelingscode in de BlogController-klasse van de toepassing die de validatiefout vastlegt die optreedt wanneer Entity Framework probeert een blog op te slaan met een BloggerName die het maximum van 10 tekens overschrijdt.

[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();
    }
}

De validatie wordt niet automatisch doorgegeven aan de weergave, daarom wordt de aanvullende code die ModelState.AddModelError gebruikt, ingezet. Dit zorgt ervoor dat de foutdetails naar de weergave gaan, waarna de ValidationMessageFor Htmlhelper wordt gebruikt om de fout weer te geven.

@Html.ValidationMessageFor(model => model.BloggerName)

IValidatableObject

IValidatableObject is een interface waarin zich bevindt System.ComponentModel.DataAnnotations. Hoewel het geen deel uitmaakt van de Entity Framework-API, kunt u deze nog steeds gebruiken voor validatie aan de serverzijde in uw Entity Framework-klassen. IValidatableObject biedt een Validate methode die Entity Framework tijdens SaveChanges aanroept of u kunt uzelf op elk gewenst moment aanroepen om de klassen te valideren.

Configuraties zoals Required en MaxLength voeren validatie uit op één veld. In de Validate methode kunt u nog complexere logica hebben, bijvoorbeeld het vergelijken van twee velden.

In het volgende voorbeeld is de Blog klasse uitgebreid om IValidatableObject te implementeren en vervolgens een regel te definiëren dat de Title en BloggerName niet overeenkomen.

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) });
        }
    }
}

De ValidationResult constructor gebruikt een string die het foutbericht vertegenwoordigt en een matrix met strings die de lidnamen vertegenwoordigen die zijn gekoppeld aan de validatie. Omdat met deze validatie zowel de Title als de BloggerNameeigenschapsnamen worden gecontroleerd, worden beide eigenschapsnamen geretourneerd.

In tegenstelling tot de validatie van de Fluent-API, wordt dit validatieresultaat herkend door de weergave, en de uitzonderingshandler die ik eerder heb gebruikt om de fout ModelState toe te voegen is overbodig. Omdat ik beide eigenschappen in de ValidationResult heb ingesteld, geven de MVC HtmlHelpers voor beide eigenschappen het foutbericht weer.

afbeelding 2

DbContext.ValidateEntity

DbContext heeft een overschrijfbare methode met de naam ValidateEntity. Wanneer u aanroept SaveChanges, roept Entity Framework deze methode aan voor elke entiteit in de cache waarvan de status niet Unchangedis. U kunt hier rechtstreeks validatielogica plaatsen of zelfs deze methode gebruiken om, bijvoorbeeld, de methode Blog.Validate aan te roepen die in de vorige sectie is toegevoegd.

Hier is een voorbeeld van een ValidateEntity overschrijving die nieuwe Post gevalideert om te voorkomen dat de posttitel al eerder is gebruikt. Eerst wordt gecontroleerd of de entiteit een bericht is en of de status Toegevoegd is. Als dat het geval is, wordt er in de database gezocht om te zien of er al een bericht met dezelfde titel is. Als er al een bestaand bericht is, wordt er een nieuwe DbEntityValidationResult gemaakt.

DbEntityValidationResult bevat een DbEntityEntry en een ICollection<DbValidationErrors> voor één entiteit. Aan het begin van deze methode wordt een DbEntityValidationResult instantie geïnstantieerd en worden eventuele gedetecteerde fouten toegevoegd aan de ValidationErrors verzameling.

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);
    }
}

Validatie expliciet activeren

Een oproep naar SaveChanges triggert alle validaties die in dit artikel worden behandeld. Maar je hoeft er niet op SaveChangeste vertrouwen. Mogelijk wilt u liever ergens anders in uw toepassing valideren.

DbContext.GetValidationErrors activeert alle validaties, de validaties die zijn gedefinieerd door aantekeningen of de Fluent-API, de validatie die is gemaakt in IValidatableObject (bijvoorbeeld Blog.Validate) en de validaties die in de DbContext.ValidateEntity methode worden uitgevoerd.

De volgende code roept GetValidationErrors op het huidige exemplaar van een DbContext aan. ValidationErrors worden gegroepeerd op entiteitstype in DbEntityValidationResult. De code doorloopt eerst de door de methode geretourneerde DbEntityValidationResults en vervolgens door elke DbValidationError binnenin.

foreach (var validationResult in db.GetValidationErrors())
{
    foreach (var error in validationResult.ValidationErrors)
    {
        Debug.WriteLine(
            "Entity Property: {0}, Error {1}",
            error.PropertyName,
            error.ErrorMessage);
    }
}

Andere overwegingen bij het gebruik van validatie

Hier volgen enkele andere punten die u moet overwegen bij het gebruik van Entity Framework-validatie:

  • Lui laden is uitgeschakeld tijdens validatie
  • EF valideert gegevensaantekeningen op niet-toegewezen eigenschappen (eigenschappen die niet zijn toegewezen aan een kolom in de database)
  • Validatie wordt uitgevoerd nadat er wijzigingen zijn gedetecteerd tijdens SaveChanges. Als u tijdens de validatie wijzigingen aanbrengt, is het uw verantwoordelijkheid om de wijzigingentracker op de hoogte te stellen
  • DbUnexpectedValidationException wordt gegooid als er fouten optreden tijdens de validatie
  • Facetten die Entity Framework in het model bevat (maximale lengte, vereist, enzovoort) leiden tot validatie, zelfs als er geen gegevensaantekeningen in uw klassen staan en/of u ef Designer hebt gebruikt om uw model te maken
  • Prioriteitsregels:
    • Fluent API-aanroepen overschrijven de bijbehorende gegevensaantekeningen
  • Uitvoeringsvolgorde:
    • Eigenschapsvalidatie vindt plaats vóór typevalidatie
    • Typevalidatie vindt alleen plaats als de eigenschapsvalidatie slaagt
  • Als een eigenschap complex is, bevat de validatie ook:
    • Validatie op eigenschapsniveau voor de eigenschappen van het complexe type
    • Validatie op typeniveau voor het complexe type, inclusief IValidatableObject validatie van het complexe type

Samenvatting

De validatie-API in Entity Framework speelt heel mooi af met validatie aan de clientzijde in MVC, maar u hoeft niet te vertrouwen op validatie aan de clientzijde. Entity Framework zorgt voor de validatie aan de serverzijde voor DataAnnotations of configuraties die u hebt toegepast met de code eerste Fluent-API.

U hebt ook een aantal uitbreidbaarheidspunten gezien voor het aanpassen van het gedrag, ongeacht of u de IValidatableObject interface gebruikt of tikt op de DbContext.ValidateEntity methode. Deze laatste twee validatiemiddelen zijn beschikbaar via de DbContext, of u nu de Code First-, Model First- of Database First-werkstroom gebruikt om uw conceptuele model te beschrijven.