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:
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.
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>
EnrollmentDate
Za pole a bezprostředně před závěrečnoufieldset
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ždouEnrollment
entitu ve vlastnosti se zobrazí název kurzu a známka. Název kurzu se načte zCourse
entity, která je uložená vCourse
navigační vlastnostiEnrollments
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í proCourses
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.)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:
Aktualizace stránky Pro vytvoření
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 doStudents
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í zForm
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*.
Spusťte stránku tak , že vyberete kartu Studenti a kliknete na Vytvořit nový.
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.
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 .
Aktualizace stránky UPRAVIT PŘÍSPĚVEK
V Controllers\StudentController.cs Edit
HttpGet
metoda (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. MetodaSaveChanges
musí vydatINSERT
příkaz.Unchanged
. Metodou není potřeba s touto entitouSaveChanges
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. MetodaSaveChanges
musí vydatUPDATE
příkaz.Deleted
. Entita byla označena k odstranění. MetodaSaveChanges
musí vydatDELETE
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í SaveChanges
Entity 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í SaveChanges
Entity 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.
Změňte některá data a klikněte na Uložit. Změněná data se zobrazí na stránce Index.
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.
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ě, žeHttpGet
Delete
metoda je volána bez předchozího selhání. Když je volána metodouHttpPost
Delete
v reakci na chybu aktualizace databáze, parametr jetrue
a do zobrazení se předá chybová zpráva.Nahraďte metodu
HttpPost
Delete
akce (pojmenovanouDeleteConfirmed
) 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řiSaveChanges
volání se vygeneruje příkaz SQLDELETE
. Změnili jste také název metody akce zDeleteConfirmed
naDelete
. Vygenerovaný kód pojmenovaný metodouHttpPost
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 proHttpPost
metody aHttpGet
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
aRemove
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 naDeleted
. 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.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:
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.