Ověření dat

Poznámka:

EF4.1 a novější verze – funkce, rozhraní API atd. probírané na této stránce byly představeny od verze Entity Framework 4.1. Pokud používáte starší verzi, některé nebo všechny informace se nevztahují.

Obsah na této stránce je upraven z článku původně napsaného Julie Lermanem (https://thedatafarm.com).

Entity Framework poskytuje širokou škálu ověřovacích funkcí, které se dají předávat do uživatelského rozhraní pro ověřování na straně klienta nebo se používají pro ověřování na straně serveru. Při prvním použití kódu můžete zadat ověření pomocí anotace nebo konfigurace rozhraní API fluent. V kódu je možné zadat další ověření, a to i složitější, a bude fungovat, ať už váš model pochází z kódu, modelu nebo databáze.

Model

Předvedu ověření pomocí jednoduché dvojice tříd: Blog a Příspěvek.

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

Datové poznámky

Code First používá poznámky ze System.ComponentModel.DataAnnotations sestavení jako jeden způsob konfigurace první třídy kódu. Mezi těmito poznámkami jsou ty, které poskytují pravidla, jako Required, MaxLength a MinLength. Několik klientských aplikací .NET také rozpoznává tyto poznámky, například ASP.NET MVC. Pomocí těchto poznámek můžete dosáhnout ověření na straně klienta i na straně serveru. Můžete například vynutit, aby vlastnost Název blogu byla povinnou vlastností.

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

Bez dalších změn kódu nebo značek v aplikaci provede stávající aplikace MVC ověření na straně klienta, a to i dynamicky sestavení zprávy pomocí názvů vlastností a poznámek.

Obrázek 1

Ve zpětné metodě této vytvářecí zobrazení se pomocí Entity Framework ukládá nový blog do databáze, ale klientské ověřování v MVC se aktivuje dříve, než aplikace dosáhne tohoto kódu.

Ověření na straně klienta není však zcela spolehlivé. Uživatelé můžou mít vliv na funkce svého prohlížeče nebo ještě horší, hacker může pomocí některých triků zabránit ověřování uživatelského rozhraní. Entity Framework ale také rozpozná poznámku Required a ověří ji.

Jednoduchým způsobem, jak to otestovat, je zakázat funkci ověřování na straně klienta MVC. Můžete to udělat v souboru web.config aplikace MVC. Oddíl appSettings má klíč pro ClientValidationEnabled. Nastavením tohoto klíče na false zabráníte uživatelskému rozhraní provádět ověření.

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

I když je ověřování na straně klienta zakázané, dostanete stejnou odpověď ve své aplikaci. Chybová zpráva "Pole Název je povinné" se zobrazí jako předtím. S výjimkou této chvíle bude výsledkem ověření na straně serveru. Entity Framework provede ověření na Required anotaci (ještě předtím, než bude provádět sestavení INSERT příkazu k odeslání do databáze) a vrátí chybu do MVC, které zobrazí zprávu.

Plynulé rozhraní API

Místo použití poznámek můžete použít fluent API pro kód, abyste získali stejné ověření na straně klienta i na straně serveru. Místo použití Requiredvám to ukážu pomocí ověření MaxLength.

Konfigurace rozhraní FLUENT API se použijí jako první kód, který vytváří model z tříd. Konfigurace můžete vložit přepsáním metody OnModelCreating třídy DbContext. Zde je konfigurace určující, že bloggerName vlastnost nemůže být delší než 10 znaků.

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

Chyby ověřování vyvolané na základě konfigurací rozhraní Fluent API se automaticky nedostanou k uživatelskému rozhraní, ale můžete je zachytit v kódu a odpovídajícím způsobem na ně reagovat.

Zde je ukázka kódu pro zpracování výjimek ve třídě BlogController aplikace, který zachycuje chybu ověření, když se Entity Framework pokouší uložit blog s názvem BloggerName přesahujícím maximální délku 10 znaků.

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

Ověření se automaticky nepředá zpět do zobrazení, což je důvod, proč se používá další kód s ModelState.AddModelError. Tím se zajistí, že se podrobnosti o chybě dostanou do zobrazení, které pak pomocí ValidationMessageFor htmlhelperu zobrazí chybu.

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

IValidatableObject

IValidatableObject je rozhraní, které žije v System.ComponentModel.DataAnnotations. I když není součástí rozhraní API entity Framework, můžete ho stále využít k ověřování na straně serveru ve třídách Entity Framework. IValidatableObject poskytuje metodu Validate, kterou bude Entity Framework volat během SaveChanges, nebo ji můžete sami volat kdykoli potřebujete, abyste ověřili třídy.

Konfigurace, jako Required a MaxLength, provádí ověření na jednom poli. Validate V metodě můžete mít ještě složitější logiku, například porovnání dvou polí.

V následujícím příkladu byla třída Blog rozšířena, aby implementovala IValidatableObject, a následně poskytuje pravidlo, že Title a BloggerName nemohou odpovídat.

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

Konstruktor ValidationResult přebírá string chybovou zprávu a pole string představující názvy členů, které jsou přidružené k ověření. Vzhledem k tomu, že toto ověření kontroluje jak Title, tak BloggerName, vrátí se oba názvy vlastností.

Na rozdíl od ověřování poskytovaného rozhraním Fluent API bude tento výsledek ověření rozpoznán zobrazením, a proto není nutné použít dříve zmíněnou obsluhu výjimky pro přidání chyby do ModelState. Vzhledem k tomu, že jsem nastavil oba názvy vlastností v ValidationResult, MVC HtmlHelpers zobrazí chybovou zprávu pro obě tyto vlastnosti.

obrázek 2

DbContext.ValidateEntity

DbContext má přepisovatelnou metodu s názvem ValidateEntity. Při volání SaveChangesEntity Framework zavolá tuto metodu pro každou entitu v její mezipaměti, jejíž stav není Unchanged. Logiku ověřování můžete umístit přímo sem nebo můžete použít tuto metodu k volání, například metodu přidanou Blog.Validate v předchozí části.

Tady je příklad ValidateEntity přepsání, které ověří nové Postfunkce, aby se zajistilo, že se název příspěvku ještě nepoužil. Nejprve zkontroluje, jestli je entita příspěvkem a jestli je její stav přidaný. Pokud tomu tak je, podívá se do databáze, aby zjistila, zda už existuje příspěvek se stejným názvem. Pokud již existuje existující příspěvek, vytvoří se nový DbEntityValidationResult .

DbEntityValidationResult obsahuje DbEntityEntry a ICollection<DbValidationErrors> pro jednu entitu. Na začátku této metody je inicializována DbEntityValidationResult a všechny zjištěné chyby se přidají do její ValidationErrors kolekce.

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

Explicitní aktivace ověřování

Volání na SaveChanges spustí všechna ověření, která jsou popsána v tomto článku. Ale nemusíte se spoléhat na SaveChanges. Možná budete chtít ověřit na jiném místě ve vaší aplikaci.

DbContext.GetValidationErrors aktivuje všechna ověření, která jsou definovaná poznámkami nebo rozhraním Fluent API, ověřováním vytvořeným v IValidatableObject (například Blog.Validate) a ověřeními provedenými v DbContext.ValidateEntity metodě.

Následující kód bude volat GetValidationErrors na aktuální instanci DbContext. ValidationErrors jsou seskupené podle typu entity do DbEntityValidationResult. Kód iteruje nejprve prostřednictvím DbEntityValidationResult prvků vrácených metodou a poté prostřednictvím každého DbValidationError uvnitř.

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

Další aspekty použití ověřování

Tady je několik dalších bodů, které je potřeba vzít v úvahu při použití ověřování entity Framework:

  • Opožděné načítání je během ověřování zakázané.
  • EF ověří datové poznámky u nemapovaných vlastností (vlastnosti, které nejsou namapované na sloupec v databázi).
  • Ověření se provádí po zjištění změn během SaveChanges. Pokud během ověřování provedete změny, je vaší zodpovědností oznámit sledování změn.
  • DbUnexpectedValidationException je vyvolán, pokud během ověřování dojde k chybám.
  • Aspekty, které Entity Framework zahrnuje v modelu (maximální délka, povinné atd.), způsobí ověření, i když ve třídách nejsou žádné datové anotace nebo jste k vytvoření modelu použili Návrhář EF.
  • Pravidla priority:
    • Volání rozhraní API fluent přepisují odpovídající datové poznámky.
  • Pořadí provádění:
    • Ověření vlastnosti probíhá před ověřením typu.
    • K ověření typu dochází pouze v případě, že ověření vlastnosti proběhne úspěšně.
  • Pokud je vlastnost složitá, bude její ověření zahrnovat také:
    • Ověřování na úrovni vlastností u vlastností komplexního typu
    • Ověřování na úrovni typu u komplexního typu, včetně IValidatableObject ověření komplexního typu

Shrnutí

Rozhraní API pro ověřování v Entity Frameworku dobře spolupracuje s ověřováním na straně klienta v MVC, ale nemusíte se spoléhat na ověřování na straně klienta. Entity Framework se postará o ověření na straně serveru pro DataAnnotations nebo konfigurace, které jste definovali pomocí rozhraní *Fluent API* *Code First*.

Také jste viděli řadu bodů rozšiřitelnosti pro přizpůsobení chování bez ohledu na to, jestli používáte IValidatableObject rozhraní nebo klepněte na metodu DbContext.ValidateEntity . A tyto poslední dva způsoby ověření jsou k dispozici prostřednictvím DbContext, ať už používáte pracovní postup "Code First", "Model First" nebo "Database First" k popisu svého konceptuálního modelu.