Sdílet prostřednictvím


Implementace základních funkcí CRUD pomocí Entity Frameworku v aplikaci ASP.NET MVC (2 z 10)

Tom Dykstra

Ukázková webová aplikace Contoso University ukazuje, jak vytvářet aplikace ASP.NET MVC 4 pomocí entity Framework 5 Code First a sady Visual Studio 2012. Informace o sérii kurzů najdete v prvním kurzu série.

Poznámka:

Pokud narazíte na problém, který nemůžete vyřešit, stáhněte si dokončenou kapitolu a zkuste problém reprodukovat. Obecně můžete najít řešení problému porovnáním kódu s dokončeným kódem. Informace o některých běžných chybách a jejich řešení najdete v tématu Chyby a alternativní řešení.

V předchozím kurzu jste vytvořili aplikaci MVC, která ukládá a zobrazuje data pomocí Entity Framework a SQL Server LocalDB. V tomto kurzu zkontrolujete a přizpůsobíte kód CRUD (vytvoření, čtení, aktualizace, odstranění), který pro vás generování MVC automaticky vytvoří v řadičích a zobrazeních.

Poznámka:

Běžným postupem je implementovat vzor úložiště, abyste vytvořili abstraktní vrstvu mezi kontrolerem a vrstvou přístupu k datům. Chcete-li tyto kurzy zachovat jednoduché, nebudete implementovat úložiště až do pozdějšího kurzu v této sérii.

V tomto kurzu vytvoříte následující webové stránky:

Snímek obrazovky se stránkou Podrobnosti o studentech Contoso pro vysokoškoláky

Snímek obrazovky se stránkou Pro úpravy studenta contoso pro vysokoškoláky

Snímek obrazovky se stránkou Vytvoření studenta contoso pro vysokoškoláky

Snímek obrazovky se stránkou Odstranit studenta

Vytvoření stránky podrobností

Vygenerovaný kód pro stránku Students Index opustil Enrollments vlastnost, protože tato vlastnost obsahuje kolekci. Details Na stránce se zobrazí obsah kolekce v tabulce HTML.

V controllers\StudentController.cs metoda akce pro Details zobrazení používá metodu Find k načtení jedné Student entity.

public ActionResult Details(int id = 0)
{
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

Hodnota klíče se předá metodě jako id parametr a pochází ze směrovacích dat v hypertextovém odkazu Podrobnosti na stránce Index.

  1. Otevřete Views\Student\Details.cshtml. Každé pole se zobrazí pomocí pomocné rutiny DisplayFor , jak je znázorněno v následujícím příkladu:

    <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
    
  2. EnrollmentDate Za pole a bezprostředně před závěrečnou fieldset značku přidejte kód pro zobrazení seznamu registrací, jak je znázorněno v následujícím příkladu:

    <div class="display-label">
            @Html.LabelFor(model => model.Enrollments)
        </div>
        <div class="display-field">
            <table>
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </div>
    </fieldset>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Tento kód prochází entitami v Enrollments navigační vlastnosti. Pro každou Enrollment entitu ve vlastnosti se zobrazí název kurzu a známka. Název kurzu se načte z Course entity, která je uložená v Course navigační vlastnosti Enrollments entity. Všechna tato data se z databáze načítají automaticky, když je to potřeba. (Jinými slovy, používáte opožděné načítání. Nezadali jste dychtivé načítání pro Courses navigační vlastnost, takže při prvním pokusu o přístup k této vlastnosti se do databáze odešle dotaz, který načte data. Další informace o opožděné načítání a dychtivé načítání si můžete přečíst v kurzu čtení souvisejících dat později v této sérii.)

  3. Spusťte stránku tak , že vyberete kartu Studenti a kliknete na odkaz Podrobnosti pro Alexander Carson. Zobrazí se seznam kurzů a známek pro vybraného studenta:

    Student_Details_page

Aktualizace stránky Pro vytvoření

  1. V Controllers\StudentController.cs nahraďte metodu HttpPost``Create akce následujícím kódem pro přidání try-catch bloku a atributu Bind do vygenerované metody:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(
       [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
       Student student)
    {
       try
       {
          if (ModelState.IsValid)
          {
             db.Students.Add(student);
             db.SaveChanges();
             return RedirectToAction("Index");
          }
       }
       catch (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
          ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
       }
       return View(student);
    }
    

    Tento kód přidá entitu Student vytvořenou pořadačem modelu ASP.NET MVC do Students sady entit a uloží změny do databáze. (Pořadač modelů odkazuje na funkci ASP.NET MVC, která usnadňuje práci s daty odeslanými formulářem. Pořadač modelu převede publikované hodnoty formuláře na typy CLR a předá je metodě akce v parametrech. V tomto případě pořadač modelů vytvoří Student instanci entity za vás pomocí hodnot vlastností z Form kolekce.)

    Tento ValidateAntiForgeryToken atribut pomáhá zabránit útokům typu útok typu cross-site.forgery .

> [!WARNING]
    > Security - The `Bind` attribute is added to protect against *over-posting*. For example, suppose the `Student` entity includes a `Secret` property that you don't want this web page to update.
    > 
    > [!code-csharp[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample5.cs?highlight=7)]
    > 
    > Even if you don't have a `Secret` field on the web page, a hacker could use a tool such as [fiddler](http://fiddler2.com/home), or write some JavaScript, to post a `Secret` form value. Without the [Bind](https://msdn.microsoft.com/library/system.web.mvc.bindattribute(v=vs.108).aspx) attribute limiting the fields that the model binder uses when it creates a `Student` instance*,* the model binder would pick up that `Secret` form value and use it to update the `Student` entity instance. Then whatever value the hacker specified for the `Secret` form field would be updated in your database. The following image shows the fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
    > 
    > ![](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/_static/image6.png)  
    > 
    > The value "OverPost" would then be successfully added to the `Secret` property of the inserted row, although you never intended that the web page be able to update that property.
    > 
    > It's a security best practice to use the `Include` parameter with the `Bind` attribute to *allowed attributes* fields. It's also possible to use the `Exclude` parameter to *blocked attributes* fields you want to exclude. The reason `Include` is more secure is that when you add a new property to the entity, the new field is not automatically protected by an `Exclude` list.
    > 
    > Another alternative approach, and one preferred by many, is to use only view models with model binding. The view model contains only the properties you want to bind. Once the MVC model binder has finished, you copy the view model properties to the entity instance.

    Other than the `Bind` attribute, the `try-catch` block is the only change you've made to the scaffolded code. If an exception that derives from [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) is caught while the changes are being saved, a generic error message is displayed. [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Although not implemented in this sample, a production quality application would log the exception (and non-null inner exceptions ) with a logging mechanism such as [ELMAH](https://code.google.com/p/elmah/).

    The code in *Views\Student\Create.cshtml* is similar to what you saw in *Details.cshtml*, except that `EditorFor` and `ValidationMessageFor` helpers are used for each field instead of `DisplayFor`. The following example shows the relevant code:

    [!code-cshtml[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample6.cshtml)]

    *Create.cshtml* also includes `@Html.AntiForgeryToken()`, which works with the `ValidateAntiForgeryToken` attribute in the controller to help prevent [cross-site request forgery](../../security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages.md) attacks.

    No changes are required in *Create.cshtml*.
  1. Spusťte stránku tak , že vyberete kartu Studenti a kliknete na Vytvořit nový.

    Student_Create_page

    Ve výchozím nastavení funguje ověření některých dat. Zadejte jména a neplatné datum a kliknutím na Vytvořit zobrazíte chybovou zprávu.

    Students_Create_page_error_message

    Následující zvýrazněný kód ukazuje kontrolu ověření modelu.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Student student)
    {
        if (ModelState.IsValid)
        {
            db.Students.Add(student);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    
        return View(student);
    }
    

    Změňte datum na platnou hodnotu, například 1. 9. 2005, a kliknutím na Vytvořit zobrazíte nový student na stránce Rejstřík .

    Students_Index_page_with_new_student

Aktualizace stránky UPRAVIT PŘÍSPĚVEK

V Controllers\StudentController.cs Edit HttpGetmetoda (bez atributuHttpPost) používá Find metodu k načtení vybrané Student entity, jak jste viděli Details v metodě. Tuto metodu nemusíte měnit.

Nahraďte však metodu HttpPost Edit akce následujícím kódem pro přidání try-catch bloku a atributu Bind:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "StudentID, LastName, FirstMidName, EnrollmentDate")]
   Student student)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(student).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
      ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
   }
   return View(student);
}

Tento kód je podobný tomu, co jste viděli HttpPost Create v metodě. Místo přidání entity vytvořené pořadačem modelu do sady entit ale tento kód nastaví příznak entity, který označuje, že byla změněna. Když se volá metoda SaveChanges , příznak Modified způsobí, že Entity Framework vytvoří příkazy SQL aktualizovat řádek databáze. Všechny sloupce řádku databáze se aktualizují, včetně sloupců, které uživatel nezměnil, a konflikty souběžnosti se ignorují. (V pozdějším kurzu této série se dozvíte, jak zvládnout souběžnost.)

Stavy entit a metody Attach a SaveChanges

Kontext databáze sleduje, jestli se entity v paměti synchronizují s odpovídajícími řádky v databázi a tyto informace určují, co se stane při volání SaveChanges metody. Když například předáte novou entitu metodě Add , stav této entity je nastaven na Added. Když pak zavoláte metodu SaveChanges , kontext databáze vydá příkaz SQL INSERT .

Entita může být v jednom znásledujících stavů:

  • Added. Entita ještě v databázi neexistuje. Metoda SaveChanges musí vydat INSERT příkaz.
  • Unchanged. Metodou není potřeba s touto entitou SaveChanges nic dělat. Při čtení entity z databáze začne entita s tímto stavem.
  • Modified. Některé nebo všechny hodnoty vlastností entity byly změněny. Metoda SaveChanges musí vydat UPDATE příkaz.
  • Deleted. Entita byla označena k odstranění. Metoda SaveChanges musí vydat DELETE příkaz.
  • Detached. Entita není sledována kontextem databáze.

V desktopové aplikaci se změny stavu obvykle nastaví automaticky. V desktopovém typu aplikace přečtete entitu a provedete změny některých hodnot jeho vlastností. To způsobí, že se stav entity automaticky změní na Modified. Při volání SaveChangesEntity Framework vygeneruje příkaz SQL UPDATE , který aktualizuje pouze skutečné vlastnosti, které jste změnili.

Odpojená povaha webových aplikací neumožňuje tuto průběžnou sekvenci. DbContext, který čte entitu, je uvolněn po vykreslení stránky. HttpPost Edit Když je volána metoda akce, provede se nový požadavek a máte novou instanci DbContext, takže při volání musíte ručně nastavit stav entity na Modified. Hodnotu Pak při volání SaveChangesEntity Framework aktualizuje všechny sloupce řádku databáze, protože kontext nemá způsob, jak zjistit, které vlastnosti jste změnili.

Pokud chcete, aby příkaz SQL Update aktualizoval pouze pole, která uživatel skutečně změnil, můžete původní hodnoty uložit nějakým způsobem (například skrytá pole), aby byly k dispozici při HttpPost Edit volání metody. Pak můžete vytvořit entitu Student pomocí původních hodnot, volat metodu Attach s původní verzí entity, aktualizovat hodnoty entity na nové hodnoty a potom volat SaveChanges. Další informace, viz Stavy entit a SaveChanges a místní data v Centru pro vývojáře MSDN.

Kód v Views\Student\Edit.cshtml se podobá kódu, který jste viděli v souboru Create.cshtml a nejsou vyžadovány žádné změny.

Spusťte stránku tak, že vyberete kartu Studenti a potom kliknete na upravit hypertextový odkaz.

Student_Edit_page

Změňte některá data a klikněte na Uložit. Změněná data se zobrazí na stránce Index.

Students_Index_page_after_edit

Aktualizace stránky Pro odstranění

V Controllers\StudentController.cs kód šablony pro metodu HttpGet Delete používá metodu Find k načtení vybrané Student entity, jak jste viděli v metodách Details a Edit metodách. Pokud ale chcete implementovat vlastní chybovou zprávu v případě selhání volání SaveChanges , přidáte do této metody některé funkce a odpovídající zobrazení.

Jak jste viděli o operacích aktualizace a vytváření, operace odstranění vyžadují dvě metody akcí. Metoda, která je volána v reakci na požadavek GET, zobrazí zobrazení, které uživateli umožní schválit nebo zrušit operaci odstranění. Pokud ho uživatel schválí, vytvoří se žádost POST. V takovém případě HttpPost Delete se volá metoda a pak tato metoda skutečně provede operaci odstranění.

Do metody přidáte try-catch blok HttpPost Delete pro zpracování všech chyb, ke kterým může dojít při aktualizaci databáze. Pokud dojde k chybě, HttpPost Delete metoda volá metodu HttpGet Delete a předá jí parametr, který indikuje, že došlo k chybě. Metoda HttpGet Delete pak znovu zobrazí potvrzovací stránku spolu s chybovou zprávou, která uživateli dává příležitost zrušit nebo zkusit znovu.

  1. Nahraďte metodu HttpGet Delete akce následujícím kódem, který spravuje hlášení chyb:

    public ActionResult Delete(bool? saveChangesError=false, int id = 0)
    {
        if (saveChangesError.GetValueOrDefault())
        {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
        }
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return HttpNotFound();
        }
        return View(student);
    }
    

    Tento kód přijímá volitelný logický parametr, který označuje, jestli byl volána po selhání uložení změn. Tento parametr je false v případě, že HttpGet Delete metoda je volána bez předchozího selhání. Když je volána metodou HttpPost Delete v reakci na chybu aktualizace databáze, parametr je true a do zobrazení se předá chybová zpráva.

  2. Nahraďte metodu HttpPost Delete akce (pojmenovanou DeleteConfirmed) následujícím kódem, který provede skutečnou operaci odstranění a zachytí všechny chyby aktualizace databáze.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            // uncomment dex and log error. 
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }
    

    Tento kód načte vybranou entitu a potom zavolá metodu Remove , která nastaví stav entity na Deleted. Při SaveChanges volání se vygeneruje příkaz SQL DELETE . Změnili jste také název metody akce z DeleteConfirmed na Delete. Vygenerovaný kód pojmenovaný metodou HttpPost Delete DeleteConfirmed HttpPost , který metodě poskytne jedinečný podpis. ( CLR vyžaduje přetížené metody, aby měly různé parametry metody.) Teď, když jsou podpisy jedinečné, můžete držet konvence MVC a použít stejný název pro HttpPost metody a HttpGet metody delete.

    Pokud je zvýšení výkonu ve vysokoobsáhové aplikaci prioritou, můžete se vyhnout zbytečnému dotazu SQL pro načtení řádku tak, že nahradíte řádky kódu, které volají Find a Remove metody, následujícím kódem, jak je znázorněno ve žlutém zvýraznění:

    Student studentToDelete = new Student() { StudentID = id };
    db.Entry(studentToDelete).State = EntityState.Deleted;
    

    Tento kód vytvoří Student instanci entity pouze pomocí hodnoty primárního klíče a pak nastaví stav entity na Deleted. To je vše, co Entity Framework potřebuje k odstranění entity.

    Jak je uvedeno, HttpGet Delete metoda neodstraní data. Provedení operace odstranění v reakci na požadavek GET (nebo za tímto účelem, provedení jakékoli operace úprav, vytvoření operace nebo jakékoli jiné operace, která mění data) vytváří bezpečnostní riziko. Další informace najdete v tématu ASP.NET MVC Tip #46 — Nepoužívejte odstranit odkazy, protože vytvářejí bezpečnostní díry na Blogu Stephena Walthera.

  3. V zobrazeních\Student\Delete.cshtml přidejte mezi nadpis a nadpis chybovou h3 zprávuh2, jak je znázorněno v následujícím příkladu:

    <h2>Delete</h2>
    <p class="error">@ViewBag.ErrorMessage</p>
    <h3>Are you sure you want to delete this?</h3>
    

    Spusťte stránku tak , že vyberete kartu Studenti a kliknete na Odstranit hypertextový odkaz:

    Student_Delete_page

  4. Klepněte na tlačítko Odstranit. Stránka Rejstříku se zobrazí bez odstraněného studenta. (V akci uvidíte příklad kódu pro zpracování chyb v akci. Zpracování kurzu souběžnosti dále v této sérii.)

Zajištění, že připojení k databázi nejsou otevřená

Abyste měli jistotu, že jsou připojení k databázi správně uzavřená a prostředky, které jsou uvolněné, měli byste vidět, že je instance kontextu uvolněna. Proto kód vygenerovaný vygenerovaný kód poskytuje metodu Dispose na konci StudentController třídy v StudentController.cs, jak je znázorněno v následujícím příkladu:

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

Základní Controller třída již implementuje IDisposable rozhraní, takže tento kód jednoduše přidá přepsání metody Dispose(bool) explicitně odstranit kontextovou instanci.

Shrnutí

Teď máte úplnou sadu stránek, které pro entity provádějí jednoduché operace Student CRUD. Použili jste pomocné rutiny MVC k vygenerování prvků uživatelského rozhraní pro datová pole. Další informace o pomocných rutinách MVC naleznete v tématu Vykreslování formuláře pomocí pomocných rutin HTML (stránka je pro MVC 3, ale stále je relevantní pro MVC 4).

V dalším kurzu rozbalíte funkce stránky Rejstřík přidáním řazení a stránkování.

Odkazy na další prostředky Entity Framework najdete v mapě obsahu ASP.NET Data Accessu.