Zajištění akcí CRUD (Create, Read, Update, Delete) podporujících zápis dat do formuláře
od Microsoftu
Toto je krok 5 bezplatného kurzu aplikace NerdDinner , který vás provede sestavením malé, ale úplné webové aplikace pomocí ASP.NET MVC 1.
Krok 5 ukazuje, jak posunout naši třídu DinnersController dále tím, že povolí podporu pro úpravy, vytváření a odstraňování dinners s ní také.
Pokud používáte ASP.NET MVC 3, doporučujeme postupovat podle kurzů Začínáme S MVC 3 nebo MVC Music Store.
NerdDinner – krok 5: Scénáře vytvoření, aktualizace a odstranění formuláře
Zavedli jsme kontrolery a zobrazení a probrali jsme, jak je použít k implementaci výpisu a podrobností pro Večeře na webu. Naším dalším krokem bude posunout naši třídu DinnersController dále a povolit podporu pro úpravy, vytváření a odstraňování dinners s ní také.
Adresy URL, které zpracovává DinnersController
Dříve jsme do funkce DinnersController přidali metody akcí, které implementovaly podporu pro dvě adresy URL: /Dinners a /Dinners/Details/[id].
Adresa URL | SLOVESO | Účel |
---|---|---|
/Večeře/ | GET | Zobrazí seznam nadcházejících večeří ve formátu HTML. |
/Večeře/Podrobnosti/[id] | GET | Zobrazení podrobností o konkrétní večeři |
Teď přidáme metody akcí pro implementaci tří dalších adres URL: /Dinners/Edit/[id], /Dinners/Create a /Dinners/Delete/[id]. Tyto adresy URL umožní podporu pro úpravy stávajících večeří, vytváření nových večeří a odstraňování večeří.
S těmito novými adresami URL budeme podporovat interakce sloves HTTP GET i HTTP POST. Požadavky HTTP GET na tyto adresy URL zobrazí počáteční html zobrazení dat (formulář vyplněný daty Večeře v případě "edit", prázdný formulář v případě "create" a obrazovku potvrzení odstranění v případě "delete"). Požadavky HTTP POST na tyto adresy URL uloží, aktualizují nebo odstraní data DinnerRepository (a odtud do databáze).
Adresa URL | SLOVESO | Účel |
---|---|---|
/Dinners/Edit/[id] | GET | Zobrazí upravitelný formulář HTML naplněný daty Večeře. |
POST | Uložte změny formuláře pro konkrétní večeři do databáze. | |
/Večeře/Vytvořit | GET | Zobrazí prázdný formulář HTML, který uživatelům umožní definovat nové večeře. |
POST | Vytvořte novou večeři a uložte ji do databáze. | |
/Dinners/Delete/[id] | GET | Zobrazit obrazovku s potvrzením odstranění |
POST | Odstraní zadanou večeři z databáze. |
Upravit podporu
Začněme implementací scénáře úprav.
Metoda http-GET Edit Action
Začneme implementací chování HTTP GET naší metody edit action. Tato metoda bude vyvolána při požadavku adresy URL /Dinners/Edit/[id]. Naše implementace bude vypadat takto:
//
// GET: /Dinners/Edit/2
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
return View(dinner);
}
Výše uvedený kód používá DinnerRepository k načtení objektu Dinner. Pak pomocí objektu Dinner vykreslí šablonu Zobrazení. Protože jsme explicitně nepředali název šablony pomocné metodě View(), použije k vyřešení šablony zobrazení výchozí cestu založenou na konvencích: /Views/Dinners/Edit.aspx.
Teď vytvoříme tuto šablonu zobrazení. Provedeme to tak, že klikneme pravým tlačítkem myši na metodu Edit a vybereme příkaz místní nabídky Přidat zobrazení:
V dialogovém okně Přidat zobrazení označíme, že předáváme objekt Dinner do naší šablony zobrazení jako jeho model, a zvolíme automatické generování šablony Upravit:
Když klikneme na tlačítko Přidat, sada Visual Studio pro nás do adresáře \Views\Dinners přidá nový soubor šablony zobrazení Edit.aspx. Otevře se také nová šablona zobrazení Edit.aspx v editoru kódu– vyplněná počáteční implementací uživatelského rozhraní Edit, jak je uvedeno níže:
Pojďme provést několik změn výchozího vygenerovaného uživatelského rozhraní pro úpravy a aktualizovat šablonu zobrazení pro úpravy tak, aby měla níže uvedený obsah (čímž odebereme několik vlastností, které nechceme zveřejnit):
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Edit: <%=Html.Encode(Model.Title)%>
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit Dinner</h2>
<%=Html.ValidationSummary("Please correct the errors and try again.") %>
<% using (Html.BeginForm()) { %>
<fieldset>
<p>
<label for="Title">Dinner Title:</label>
<%=Html.TextBox("Title") %>
<%=Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="EventDate">EventDate:</label>
<%=Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate))%>
<%=Html.ValidationMessage("EventDate", "*") %>
</p>
<p>
<label for="Description">Description:</label>
<%=Html.TextArea("Description") %>
<%=Html.ValidationMessage("Description", "*")%>
</p>
<p>
<label for="Address">Address:</label>
<%=Html.TextBox("Address") %>
<%=Html.ValidationMessage("Address", "*") %>
</p>
<p>
<label for="Country">Country:</label>
<%=Html.TextBox("Country") %>
<%=Html.ValidationMessage("Country", "*") %>
</p>
<p>
<label for="ContactPhone">ContactPhone #:</label>
<%=Html.TextBox("ContactPhone") %>
<%=Html.ValidationMessage("ContactPhone", "*") %>
</p>
<p>
<label for="Latitude">Latitude:</label>
<%=Html.TextBox("Latitude") %>
<%=Html.ValidationMessage("Latitude", "*") %>
</p>
<p>
<label for="Longitude">Longitude:</label>
<%=Html.TextBox("Longitude") %>
<%=Html.ValidationMessage("Longitude", "*") %>
</p>
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
<% } %>
</asp:Content>
Když aplikaci spustíme a požádáme o adresu URL "/Dinners/Edit/1" , zobrazí se následující stránka:
Kód HTML vygenerovaný naším zobrazením vypadá následovně. Jedná se o standardní HTML – s elementem <formuláře> , který provede HTTP POST na adresu URL /Dinners/Edit/1 , když je stisknuto tlačítko "Uložit" <input type="submit"/> . Pro každou upravitelnou vlastnost byl výstupem elementu HTML <input type="text"/> :
Pomocné metody Html Html.BeginForm() a Html.TextBox()
Naše šablona zobrazení Edit.aspx používá několik metod Html Helper: Html.ValidationSummary(), Html.BeginForm(), Html.TextBox() a Html.ValidationMessage(). Kromě generování značek HTML pro nás tyto pomocné metody poskytují integrovanou podporu zpracování chyb a ověřování.
Pomocná metoda Html.BeginForm()
Pomocná metoda Html.BeginForm() je to, co výstupem elementu formuláře> HTML <v našem kódu. V naší šabloně zobrazení Edit.aspx si všimnete, že při použití této metody používáme příkaz "using" jazyka C#. Otevřená složená složená závorka označuje začátek <obsahu formuláře> a pravá složená složená závorka označuje konec elementu </form> :
<% using (Html.BeginForm()) { %>
<fieldset>
<!-- Fields Omitted for Brevity -->
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
<% } %>
Případně pokud zjistíte, že přístup k příkazu using není pro takový scénář nepřirozený, můžete použít kombinaci Html.BeginForm() a Html.EndForm() (která dělá totéž):
<% Html.BeginForm(); %>
<fieldset>
<!-- Fields Omitted for Brevity -->
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
<% Html.EndForm(); %>
Volání Html.BeginForm() bez jakýchkoli parametrů způsobí výstup prvku formuláře, který provede http-POST na adresu URL aktuálního požadavku. Proto naše zobrazení pro úpravy vygeneruje <element form action="/Dinners/Edit/1" method="post"> . Případně bychom mohli do html.BeginForm() předat explicitní parametry, pokud bychom chtěli publikovat na jinou adresu URL.
Pomocná metoda Html.TextBox()
Naše zobrazení Edit.aspx používá k výstupu <elementů input type="text"/ pomocnou metodu Html.TextBox():>
<%= Html.TextBox("Title") %>
Výše uvedená metoda Html.TextBox() přijímá jeden parametr – který se používá k určení atributů <id/name elementu input type="text"/> , který má být výstupem, a také vlastnost modelu, ze které se má naplnit hodnota textového pole. Například objekt Dinner, který jsme předali do zobrazení Pro úpravy, měl hodnotu vlastnosti "Title" ".NET Futures", a proto naše metoda Html.TextBox("Title") volá výstup: <input id="Title" name="Title" type="text" value=".NET Futures" />.
Případně můžeme použít první parametr Html.TextBox() k určení id/názvu elementu a potom explicitně předat hodnotu, která se má použít jako druhý parametr:
<%= Html.TextBox("Title", Model.Title)%>
Často budeme chtít pro hodnotu, která je výstupem, provést vlastní formátování. Pro tyto scénáře je užitečná statická metoda String.Format() integrovaná v .NET. Naše šablona zobrazení Edit.aspx tuto funkci používá k naformátování hodnoty EventDate (která je typu DateTime), aby se v ní nezobkazily sekundy za daný čas:
<%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %>
Třetí parametr html.TextBox() lze volitelně použít k výstupu dalších atributů HTML. Následující fragment kódu ukazuje, jak vykreslit další atribut size="30" a atribut class="mycssclass" u elementu <input type="text"/> . Všimněte si, jak unikáme názvu atributu třídy pomocí znaku "@", protože "třída" je vyhrazené klíčové slovo v jazyce C#:
<%= Html.TextBox("Title", Model.Title, new { size=30, @class="myclass" } )%>
Implementace metody http-POST Edit Action
Teď máme implementovanou verzi HTTP-GET naší metody akce Upravit. Když uživatel požádá o adresu URL /Dinners/Edit/1 , zobrazí se mu stránka HTML podobná následující:
Stisknutí tlačítka Uložit způsobí odeslání formuláře na adresu URL /Dinners/Edit/1 a odešle hodnoty vstupního> formuláře HTML <pomocí příkazu HTTP POST. Pojďme teď implementovat chování HTTP POST naší metody akce úpravy, která se bude zabývat uložením funkce Dinner.
Začneme přidáním přetížené metody akce Edit do třídy DinnersController s atributem AcceptVerbs, který označuje, že zpracovává scénáře HTTP POST:
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
...
}
Při použití atributu [AcceptVerbs] na přetížené metody akcí ASP.NET MVC automaticky zpracovává odesílání požadavků do příslušné metody akce v závislosti na příchozím příkazu HTTP. Požadavky HTTP POST na adresy URL /Dinners/Edit/[id] přejdou na výše uvedenou metodu Edit, zatímco všechny ostatní požadavky HTTP na adresy URL s příkazy /Dinners/Edit/[id] budou směrovat na první metodu Edit, kterou jsme implementovali (která neměla [AcceptVerbs]
atribut).
Vedlejší téma: Proč rozlišovat pomocí příkazů HTTP? |
---|
Možná se ptáte, proč používáme jednu adresu URL a odlišujeme její chování pomocí příkazu HTTP? Proč nemáte jenom dvě samostatné adresy URL, které zvládnou načítání a ukládání změn úprav? Například: /Dinners/Edit/[id] pro zobrazení počátečního formuláře a /Dinners/Save/[id] pro zpracování příspěvku ve formuláři pro uložení? Nevýhodou publikování dvou samostatných adres URL je to, že v případech, kdy publikujeme adresu /Dinners/Save/2 a pak kvůli chybě vstupu potřebujeme znovu zobrazit formulář HTML, koncový uživatel nakonec bude mít adresu URL /Dinners/Save/2 v adresní řádku prohlížeče (protože to byla adresa URL, na kterou se formulář publikoval). Pokud si koncový uživatel uloží tuto znovu zobrazenou stránku do záložek v seznamu oblíbených v prohlížeči nebo zkopíruje nebo vloží adresu URL a pošle ji e-mailem známému, uloží adresu URL, která v budoucnu nebude fungovat (protože tato adresa URL závisí na hodnotách příspěvku). Zveřejněním jedné adresy URL (například /Dinners/Edit/[id]) a odlizením zpracování pomocí příkazu HTTP je pro koncové uživatele bezpečné vytvořit záložku stránky pro úpravy nebo odeslat adresu URL ostatním uživatelům. |
Načítání hodnot příspěvku formuláře
Existuje mnoho způsobů, jak můžeme přistupovat k parametrům publikovaného formuláře v rámci naší metody "Edit" HTTP POST. Jedním z jednoduchých přístupů je použití vlastnosti Request v základní třídě Controller pro přístup ke kolekci formulářů a načtení zaúčtovaných hodnot přímo:
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
// Retrieve existing dinner
Dinner dinner = dinnerRepository.GetDinner(id);
// Update dinner with form posted values
dinner.Title = Request.Form["Title"];
dinner.Description = Request.Form["Description"];
dinner.EventDate = DateTime.Parse(Request.Form["EventDate"]);
dinner.Address = Request.Form["Address"];
dinner.Country = Request.Form["Country"];
dinner.ContactPhone = Request.Form["ContactPhone"];
// Persist changes back to database
dinnerRepository.Save();
// Perform HTTP redirect to details page for the saved Dinner
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
Výše uvedený přístup je trochu podrobný, zejména když přidáme logiku zpracování chyb.
Lepším přístupem pro tento scénář je využít integrovanou pomocnou metodu UpdateModel() v základní třídě Controller. Podporuje aktualizaci vlastností objektu, který mu předáváme pomocí parametrů příchozího formuláře. Používá reflexi k určení názvů vlastností objektu a pak je automaticky převede a přiřadí k nim hodnoty na základě vstupních hodnot odeslaných klientem.
Pomocí tohoto kódu můžeme zjednodušit akci úprav HTTP-POST pomocí metody UpdateModel():
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
Teď můžeme navštívit adresu URL /Dinners/Edit/1 a změnit název večeře:
Když klikneme na tlačítko Uložit, provedeme příspěvek formuláře pro akci Upravit a aktualizované hodnoty se zachovají v databázi. Pak budeme přesměrováni na adresu URL Podrobnosti pro večeři (kde se zobrazí nově uložené hodnoty):
Zpracování chyb úprav
Naše aktuální implementace HTTP-POST funguje správně – s výjimkou případů, kdy dojde k chybám.
Když uživatel při úpravě formuláře udělá chybu, musíme zajistit, aby se formulář znovu zobrazil s informativní chybovou zprávou, která ho při opravě provede. Patří sem případy, kdy koncový uživatel publikuje nesprávný vstup (například poškozený řetězec data), a také případy, kdy je vstupní formát platný, ale došlo k porušení obchodního pravidla. Pokud dojde k chybě, měl by formulář zachovat vstupní data, která uživatel původně zadal, aby nemusel znovu vyplňovat změny ručně. Tento proces by se měl opakovat tolikrát, kolikrát je to potřeba, dokud se formulář úspěšně neskončí.
ASP.NET MVC obsahuje několik pěkných integrovaných funkcí, které usnadňují zpracování chyb a opakované zobrazení formulářů. Pokud chcete vidět tyto funkce v akci, aktualizujeme metodu akce Edit následujícím kódem:
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
foreach (var issue in dinner.GetRuleViolations()) {
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
return View(dinner);
}
}
Výše uvedený kód se podobá předchozí implementaci – s tím rozdílem, že teď kolem naší práce zabalíme blok pro zpracování chyb try/catch. Pokud dojde k výjimce buď při volání UpdateModel(), nebo když se pokusíme uložit DinnerRepository (což vyvolá výjimku, pokud objekt Dinner, který se pokoušíme uložit, je neplatný z důvodu porušení pravidla v rámci našeho modelu), náš blok zpracování chyb zachycení se spustí. V ní přejdou všechna porušení pravidel, která existují v objektu Dinner, a přidáme je do objektu ModelState (o kterém se za chvíli budeme zabývat). Pak zobrazení znovu zobrazíme.
Abychom viděli, že to funguje, znovu spusťte aplikaci, upravíme aplikaci Dinner a změníme ji tak, aby měla prázdný název, datum události "BOGUS" a použijte telefonní číslo Spojeného království s hodnotou země/oblast USA. Když stiskneme tlačítko Uložit, metoda HTTP POST Edit nebude moct večeři uložit (protože dojde k chybám) a znovu zobrazí formulář:
Naše aplikace má slušné chybové prostředí. Textové prvky s neplatným vstupem jsou zvýrazněné červeně a koncovému uživateli se zobrazí chybové zprávy ověření. Formulář také zachovává vstupní data, která uživatel zadal – aby nemusel nic znovu vyplňovat.
Můžete se zeptat, jak k tomu došlo? Jak se textová pole Title (Název), EventDate (Datum události) a ContactPhone (KontaktPhone) zvýraznily červeně a věděly, že mají vypsat původně zadané uživatelské hodnoty? A jak se chybové zprávy zobrazovaly v seznamu v horní části? Dobrou zprávou je, že k tomu nedošlo magicky, ale proto, že jsme použili některé integrované funkce ASP.NET MVC, které usnadňují scénáře ověřování vstupu a zpracování chyb.
Principy modelového stavu a pomocných metod HTML pro ověření
Třídy kontroleru mají kolekci vlastností ModelState, která poskytuje způsob, jak indikovat, že při předávání objektu modelu do zobrazení existují chyby. Chybové položky v kolekci ModelState identifikují název vlastnosti modelu s problémem (například Title, EventDate nebo ContactPhone) a umožňují zadat chybovou zprávu popisnou člověkem (například Název je povinný).
Pomocná metoda UpdateModel() automaticky naplní kolekci ModelState, když dojde k chybám při pokusu o přiřazení hodnot formuláře k vlastnostem objektu modelu. Například vlastnost EventDate objektu Dinner je typu DateTime. Když metoda UpdateModel() nemohla přiřadit hodnotu řetězce "BOGUS" k ní ve výše uvedeném scénáři, metoda UpdateModel() přidala položku do kolekce ModelState, která označuje, že s danou vlastností došlo k chybě přiřazení.
Vývojáři mohou také napsat kód pro explicitní přidání chybových položek do kolekce ModelState, jak to děláme níže v rámci našeho bloku "catch" zpracování chyb, který naplňuje kolekci ModelState položkami na základě aktivního porušení pravidel v objektu Dinner:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
foreach (var issue in dinner.GetRuleViolations()) {
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
return View(dinner);
}
}
Integrace pomocné rutiny HTML s ModelState
Pomocné metody HTML – například Html.TextBox() – při vykreslování výstupu zkontrolujte kolekci ModelState. Pokud u položky dojde k chybě, vykreslí uživatelem zadanou hodnotu a třídu chyb CSS.
Například v našem zobrazení "Edit" používáme pomocnou metodu Html.TextBox() k vykreslení EventDate objektu Dinner:
<%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %>
Když se zobrazení vykreslilo v chybovém scénáři, metoda Html.TextBox() zkontrolovala kolekci ModelState, aby provedla, jestli nedošlo k nějakým chybám spojeným s vlastností "EventDate" objektu Dinner. Když se zjistilo, že došlo k chybě, vygeneroval jako hodnotu odeslaný uživatelský vstup ("BOGUS") a přidal třídu chyb css do <vstupního type="textbox"/> , který vygeneroval:
<input class="input-validation-error"id="EventDate" name="EventDate" type="text" value="BOGUS"/>
Vzhled třídy chyb css můžete přizpůsobit tak, aby vypadal, jak chcete. Výchozí třída chyb šablon stylů CSS – input-validation-error – je definovaná v šabloně stylů \content\site.css a vypadá takto:
.input-validation-error
{
border: 1px solid #ff0000;
background-color: #ffeeee;
}
Toto pravidlo šablon stylů CSS způsobilo zvýraznění neplatných vstupních prvků, jak je znázorněno níže:
Html.ValidationMessage() – metoda pomocné rutiny
Pomocnou metodu Html.ValidationMessage() lze použít k výstupu chybové zprávy ModelState přidružené ke konkrétní vlastnosti modelu:
<%= Html.ValidationMessage("EventDate")%>
Výstupy výše uvedeného kódu: <span class="field-validation-error"> Hodnota BOGUS je neplatná</span>.
Pomocná metoda Html.ValidationMessage() také podporuje druhý parametr, který umožňuje vývojářům přepsat zobrazenou chybovou zprávu:
<%= Html.ValidationMessage("EventDate","*") %>
Výstup výše uvedeného kódu: <span class="field-validation-error">*</span> místo výchozího textu chyby, pokud je k dispozici chyba pro vlastnost EventDate.
Html.ValidationSummary() – pomocná metoda
Pomocnou metodu Html.ValidationSummary() lze použít k vykreslení souhrnné chybové zprávy, spolu se seznamem <ul><li/></ul> všech podrobných chybových zpráv v kolekci ModelState:
Pomocná metoda Html.ValidationSummary() přebírá volitelný parametr řetězce – který definuje souhrnnou chybovou zprávu, která se zobrazí nad seznamem podrobných chyb:
<%= Html.ValidationSummary("Please correct the errors and try again.") %>
Volitelně můžete pomocí šablon stylů CSS přepsat, jak vypadá seznam chyb.
Použití pomocné metody AddRuleViolations
Naše počáteční implementace HTTP-POST Edit použila příkaz foreach v rámci jeho bloku catch, aby se přemístila porušení pravidel objektu Dinner a přidala je do kolekce ModelState kontroleru:
catch {
foreach (var issue in dinner.GetRuleViolations()) {
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
return View(dinner);
}
Tento kód můžeme trochu čistší přidáním třídy ControllerHelpers do projektu NerdDinner a implementovat rozšiřující metodu "AddRuleViolations", která přidá pomocnou metodu do třídy ASP.NET MVC ModelStateDictionary. Tato rozšiřující metoda může zapouzdřovat logiku potřebnou k naplnění ModelStateDictionary seznamem chyb RuleViolation:
public static class ControllerHelpers {
public static void AddRuleViolations(this ModelStateDictionary modelState, IEnumerable<RuleViolation> errors) {
foreach (RuleViolation issue in errors) {
modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
}
}
Pak můžeme aktualizovat naši metodu akce HTTP-POST Edit tak, aby tuto rozšiřující metodu použila k naplnění kolekce ModelState našimi porušeními pravidel večeře.
Dokončit implementaci metody akce úpravy
Následující kód implementuje veškerou logiku kontroleru potřebnou pro scénář úprav:
//
// GET: /Dinners/Edit/2
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
return View(dinner);
}
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddRuleViolations(dinner.GetRuleViolations());
return View(dinner);
}
}
Na naší implementaci Edit je dobré vědět, že ani naše třída Controller, ani šablona Zobrazení nevědí nic o konkrétních ověřovacích nebo obchodních pravidlech, která jsou vynucována naším modelem Dinner. V budoucnu můžeme do našeho modelu přidat další pravidla a nemusíme provádět žádné změny kódu v kontroleru nebo zobrazení, aby je bylo možné podporovat. Díky tomu můžeme v budoucnu snadno vyvíjet požadavky na aplikace s minimem změn kódu.
Vytvořit podporu
Dokončili jsme implementaci chování "Edit" naší třídy DinnersController. Pojďme teď přejít k implementaci podpory "Vytvořit", která uživatelům umožní přidávat nové večeře.
Metoda vytvoření akce HTTP-GET
Začneme implementací chování HTTP GET naší metody akce vytvoření. Tato metoda se zavolá, když někdo navštíví adresu URL /Dinners/Create . Naše implementace vypadá takto:
//
// GET: /Dinners/Create
public ActionResult Create() {
Dinner dinner = new Dinner() {
EventDate = DateTime.Now.AddDays(7)
};
return View(dinner);
}
Výše uvedený kód vytvoří nový objekt Dinner a přiřadí jeho vlastnost EventDate na jeden týden v budoucnosti. Potom vykreslí Zobrazení, které je založené na novém objektu Dinner. Protože jsme pomocné metodě View() explicitně nepředali název, použijeme k překladu šablony zobrazení výchozí cestu založenou na konvencích: /Views/Dinners/Create.aspx.
Teď vytvoříme tuto šablonu zobrazení. Můžeme to udělat tak, že klikneme pravým tlačítkem myši na metodu akce Vytvořit a vybereme příkaz místní nabídky Přidat zobrazení. V dialogovém okně Přidat zobrazení označíme, že do šablony zobrazení předáváme objekt Dinner, a zvolíme automatické generování šablony Vytvořit:
Když klikneme na tlačítko Přidat, Visual Studio uloží nové zobrazení Create.aspx založené na generování do adresáře \Views\Dinners a otevře ho v integrovaném vývojovém prostředí:
Pojďme provést několik změn výchozího souboru vygenerovaného pro vytvoření a upravit ho tak, aby vypadal takto:
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Host a Dinner
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Host a Dinner</h2>
<%=Html.ValidationSummary("Please correct the errors and try again.") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<p>
<label for="Title">Title:</label>
<%= Html.TextBox("Title") %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="EventDate">EventDate:</label>
<%=Html.TextBox("EventDate") %>
<%=Html.ValidationMessage("EventDate", "*") %>
</p>
<p>
<label for="Description">Description:</label>
<%=Html.TextArea("Description") %>
<%=Html.ValidationMessage("Description", "*") %>
</p>
<p>
<label for="Address">Address:</label>
<%=Html.TextBox("Address") %>
<%=Html.ValidationMessage("Address", "*") %>
</p>
<p>
<label for="Country">Country:</label>
<%=Html.TextBox("Country") %>
<%=Html.ValidationMessage("Country", "*") %>
</p>
<p>
<label for="ContactPhone">ContactPhone:</label>
<%=Html.TextBox("ContactPhone") %>
<%=Html.ValidationMessage("ContactPhone", "*") %>
</p>
<p>
<label for="Latitude">Latitude:</label>
<%=Html.TextBox("Latitude") %>
<%=Html.ValidationMessage("Latitude", "*") %>
</p>
<p>
<label for="Longitude">Longitude:</label>
<%=Html.TextBox("Longitude") %>
<%=Html.ValidationMessage("Longitude", "*") %>
</p>
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
<% }
%>
</asp:Content>
Když teď aplikaci spustíme a v prohlížeči získáme přístup k adrese URL "/Dinners/Create" , vykreslí se uživatelské rozhraní jako v následující implementaci akce Vytvořit:
Implementace metody http-POST Create Action
Máme implementovanou verzi HTTP-GET naší metody vytvořit akci. Když uživatel klikne na tlačítko Uložit, provede formulářový příspěvek na adresu URL /Dinners/Create a odešle hodnoty vstupního> formuláře HTML <pomocí příkazu HTTP POST.
Pojďme teď implementovat chování HTTP POST naší metody akce vytvoření. Začneme přidáním přetížené metody akce Create do třídy DinnersController, která má atribut AcceptVerbs, který označuje, že zpracovává scénáře HTTP POST:
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create() {
...
}
Existuje řada způsobů, jak můžeme přistupovat k parametrům publikovaného formuláře v rámci naší metody Create s povolenou funkcí HTTP-POST.
Jedním z přístupů je vytvoření nového objektu Dinner a následné použití pomocné metody UpdateModel() (jako jsme to udělali u akce Upravit) k jeho naplnění hodnotami publikovaného formuláře. Pak ho můžeme přidat do naší VečeřeRepository, zachovat ho do databáze a přesměrovat uživatele na naši akci Podrobnosti, abychom pomocí následujícího kódu zobrazili nově vytvořenou večeři:
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create() {
Dinner dinner = new Dinner();
try {
UpdateModel(dinner);
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new {id=dinner.DinnerID});
}
catch {
ModelState.AddRuleViolations(dinner.GetRuleViolations());
return View(dinner);
}
}
Případně můžeme použít přístup, kdy naše metoda akce Create() používá objekt Dinner jako parametr metody. ASP.NET MVC nám pak automaticky vytvoří instanci nového objektu Dinner, naplní jeho vlastnosti pomocí vstupů formuláře a předá ho naší metodě akce:
//
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = "SomeUser";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new {id = dinner.DinnerID });
}
catch {
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(dinner);
}
Naše metoda akce výše ověřuje, že objekt Dinner byl úspěšně naplněn hodnotami formuláře post kontrolou Vlastnosti ModelState.IsValid. Tato možnost vrátí hodnotu false, pokud dojde k problémům s převodem vstupu (například řetězec "BOGUS" pro vlastnost EventDate) a pokud dojde k problémům, naše metoda akce znovu zobrazí formulář.
Pokud jsou vstupní hodnoty platné, pak se metoda akce pokusí přidat a uložit novou večeři do DinnerRepository. Zabalí tuto práci do bloku try/catch a znovu zobrazí formulář, pokud dojde k porušení obchodních pravidel (což by způsobilo, že metoda dinnerRepository.Save() vyvolá výjimku).
Abychom viděli toto chování při zpracování chyb v akci, můžeme požádat o adresu URL /Dinners/Create a vyplnit podrobnosti o nové večeři. Nesprávný vstup nebo hodnoty způsobí, že se formulář vytvoření znovu zobrazí se zvýrazněnými chybami, jako je následující:
Všimněte si, že náš formulář Pro vytvoření dodržuje přesně stejná ověřovací a obchodní pravidla jako formulář pro úpravy. Důvodem je to, že naše ověřovací a obchodní pravidla byly definovány v modelu a nebyly vloženy do uživatelského rozhraní nebo kontroleru aplikace. To znamená, že později můžeme změnit nebo vyvíjet naše ověřovací nebo obchodní pravidla na jednom místě a nechat je použít v celé naší aplikaci. Nebudeme muset měnit žádný kód v rámci našich metod akce Upravit nebo Vytvořit, abychom automaticky dodržovali nová pravidla nebo úpravy stávajících pravidel.
Když opravíme vstupní hodnoty a znovu klikneme na tlačítko Uložit, naše přidání do VečeřeRepository proběhne úspěšně a do databáze se přidá nová večeře. Pak budeme přesměrováni na adresu URL /Dinners/Details/[id], kde se zobrazí podrobnosti o nově vytvořené večeři:
Odstranit podporu
Pojďme teď do našeho dinnersControlleru přidat podporu Pro odstranění.
Metoda akce ODSTRANĚNÍ HTTP-GET
Začneme implementací chování HTTP GET naší metody akce odstranění. Tato metoda se zavolá, když někdo navštíví adresu URL /Dinners/Delete/[id]. Níže je uvedena implementace:
//
// HTTP GET: /Dinners/Delete/1
public ActionResult Delete(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View(dinner);
}
Metoda akce se pokusí načíst večeři, která má být odstraněna. Pokud večeře existuje, vykreslí zobrazení založené na objektu Dinner. Pokud objekt neexistuje (nebo byl již odstraněn), vrátí zobrazení, které vykreslí šablonu zobrazení NotFound, kterou jsme vytvořili dříve pro naši metodu akce Podrobnosti.
Šablonu zobrazení Odstranit můžeme vytvořit kliknutím pravým tlačítkem myši v metodě akce Odstranit a výběrem příkazu místní nabídky Přidat zobrazení. V dialogovém okně Přidat zobrazení označíme, že předáváme objekt Dinner do naší šablony zobrazení jako jeho model a zvolíme vytvoření prázdné šablony:
Když klikneme na tlačítko Přidat, Visual Studio pro nás do adresáře \Views\Dinners přidá nový soubor šablony zobrazení Delete.aspx. Do šablony přidáme kód HTML a kód pro implementaci potvrzovací obrazovky pro odstranění, jak je uvedeno níže:
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Delete Confirmation: <%=Html.Encode(Model.Title) %>
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Delete Confirmation
</h2>
<div>
<p>Please confirm you want to cancel the dinner titled:
<i> <%=Html.Encode(Model.Title) %>? </i>
</p>
</div>
<% using (Html.BeginForm()) { %>
<input name="confirmButton" type="submit" value="Delete" />
<% } %>
</asp:Content>
Výše uvedený kód zobrazí název večeře, která se má odstranit, a vypíše <prvek formuláře> , který provede post na adresu URL /Dinners/Delete/[id], pokud koncový uživatel klikne na tlačítko Odstranit.
Když spustíme naši aplikaci a přistupujeme k adrese URL /Dinners/Delete/[id]" pro platný objekt Dinner, vykreslí se uživatelské rozhraní takto:
Vedlejší téma: Proč děláme POST? |
---|
Mohli byste se zeptat – proč jsme prošli úsilím o <vytvoření formuláře> na naší potvrzovací obrazovce Odstranění? Proč k propojení s metodou akce, která provede skutečnou operaci odstranění, nepoužívat jenom standardní hypertextový odkaz? Důvodem je to, že chceme být opatrní a chránit se před webovými prohledávači a vyhledávacími weby, které by zjišťovaly naše adresy URL a neúmyslně způsobovaly odstranění dat, když budou sledovat odkazy. Adresy URL založené na HTTP-GET jsou pro ně považovány za bezpečné pro přístup nebo procházení a neměly by se řídit adresami HTTP-POST. Dobrým pravidlem je zajistit, abyste vždy za požadavky HTTP-POST umístili destruktivní operace nebo operace úprav dat. |
Implementace metody akce odstranění HTTP-POST
Teď máme implementovanou verzi HTTP-GET naší metody akce Odstranění, která zobrazuje obrazovku s potvrzením odstranění. Když koncový uživatel klikne na tlačítko Odstranit, odešle formulář na adresu URL /Dinners/Dinner/[id].
Pojďme teď implementovat chování HTTP POST metody akce odstranění pomocí následujícího kódu:
//
// HTTP POST: /Dinners/Delete/1
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id, string confirmButton) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
dinnerRepository.Delete(dinner);
dinnerRepository.Save();
return View("Deleted");
}
Verze HTTP-POST naší metody akce Delete se pokusí načíst objekt dinner, který se má odstranit. Pokud ji nemůže najít (protože už byla odstraněna), vykreslí naši šablonu Nenalezeno. Pokud najde položku Večeře, odstraní ji z pole DinnerRepository. Potom vykreslí odstraněnou šablonu.
Pokud chcete implementovat šablonu Odstraněno, klikneme pravým tlačítkem myši na metodu akce a zvolíme místní nabídku Přidat zobrazení. Toto zobrazení pojmenujeme "Odstraněno" a necháme ho jako prázdnou šablonu (a nebrat objekt modelu silného typu). Pak do něj přidáme nějaký obsah HTML:
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Dinner Deleted
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Dinner Deleted</h2>
<div>
<p>Your dinner was successfully deleted.</p>
</div>
<div>
<p><a href="/dinners">Click for Upcoming Dinners</a></p>
</div>
</asp:Content>
A když teď spustíme naši aplikaci a získáme přístup k adrese URL /Dinners/Delete/[id]" platného objektu Dinner, zobrazí se obrazovka s potvrzením odstranění večeře, jak je uvedeno níže:
Když klikneme na tlačítko Odstranit, provede se http-POST na adresu URL /Dinners/Delete/[id], která odstraní večeři z naší databáze a zobrazí šablonu zobrazení Odstraněno:
Zabezpečení vazby modelu
Probrali jsme dva různé způsoby použití integrovaných funkcí vazby modelu ASP.NET MVC. První používá metodu UpdateModel() k aktualizaci vlastností existujícího objektu modelu a druhá pomocí ASP.NET podporu MVC pro předávání objektů modelu jako parametrů metody akce. Obě tyto techniky jsou velmi výkonné a velmi užitečné.
Tato moc s sebou přináší také zodpovědnost. Při přijímání jakéhokoli vstupu uživatele je důležité být vždy paranoidní, pokud jde o zabezpečení, a to platí i při vazbě objektů na vstup formuláře. Měli byste být opatrní, abyste vždy kódovali hodnoty zadané uživatelem ve formátu HTML, abyste se vyhnuli útokům prostřednictvím injektáže HTML a JavaScriptu, a dávejte pozor na útoky prostřednictvím injektáže SQL (poznámka: pro naši aplikaci používáme LINQ to SQL, který automaticky kóduje parametry, aby se těmto typům útoků zabránilo). Nikdy byste se neměli spoléhat jenom na ověřování na straně klienta a vždy používat ověřování na straně serveru, abyste se ocházili před hackery, kteří se vám pokoušejí poslat falešné hodnoty.
Jednou z dalších položek zabezpečení, abyste měli jistotu, že při použití funkcí vazby ASP.NET MVC myslíte na rozsah objektů, které vytváříte vazbu. Konkrétně chcete mít jistotu, že rozumíte bezpečnostním dopadům vlastností, které chcete vázat, a zajistit, abyste povolili aktualizaci pouze těch vlastností, které by měly být skutečně aktualizovatelné koncovým uživatelem.
Ve výchozím nastavení se metoda UpdateModel() pokusí aktualizovat všechny vlastnosti objektu modelu, které odpovídají příchozím hodnotám parametrů formuláře. Stejně tak objekty předávané jako parametry metody akce také ve výchozím nastavení mohou mít všechny své vlastnosti nastavené prostřednictvím parametrů formuláře.
Uzamčení vazby na základě využití
Zásady vazby můžete uzamknout na základě jednotlivých využití tím, že poskytnete explicitní seznam zahrnutí vlastností, které je možné aktualizovat. To lze provést předáním dalšího parametru pole řetězců metodě UpdateModel(), jak je uvedeno níže:
string[] allowedProperties = new[]{ "Title","Description",
"ContactPhone", "Address",
"EventDate", "Latitude",
"Longitude"};
UpdateModel(dinner, allowedProperties);
Objekty předané jako parametry metody akce také podporují atribut [Bind], který umožňuje zadat seznam povolených vlastností takto:
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Title,Address")] Dinner dinner ) {
...
}
Uzamykání vazby na základě typu
Pravidla vazby můžete také uzamknout na základě jednotlivých typů. To vám umožní zadat pravidla vazby jednou a pak je nechat použít ve všech scénářích (včetně scénářů parametrů metody UpdateModel a action) na všech kontrolérech a metodách akcí.
Pravidla vazby jednotlivých typů můžete přizpůsobit přidáním atributu [Bind] do typu nebo jeho registrací v souboru Global.asax aplikace (užitečné pro scénáře, kdy typ nevlastníte). Potom můžete použít vlastnosti Include a Exclude atributu Bind k řízení, které vlastnosti jsou vázat pro konkrétní třídu nebo rozhraní.
Tuto techniku použijeme pro třídu Dinner v naší aplikaci NerdDinner a přidáme do ní atribut [Bind], který omezí seznam vázatelných vlastností na následující:
[Bind(Include="Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longitude")]
public partial class Dinner {
...
}
Všimněte si, že nepovolujeme manipulaci s kolekcí RVP prostřednictvím vazby, ani neumožňujeme nastavit vlastnosti DinnerID nebo HostedBy prostřednictvím vazby. Z bezpečnostních důvodů budeme místo toho manipulovat pouze s těmito konkrétními vlastnostmi pomocí explicitního kódu v rámci našich metod akcí.
Wrap-Up CRUD
ASP.NET MVC obsahuje řadu předdefinovaných funkcí, které pomáhají s implementací scénářů publikování formulářů. Celou řadu těchto funkcí jsme použili k poskytování podpory uživatelského rozhraní CRUD nad naším DinnerRepository.
K implementaci naší aplikace používáme přístup zaměřený na model. To znamená, že veškerá logika ověřování a obchodních pravidel je definována v rámci naší vrstvy modelu, a ne v rámci našich kontrolerů nebo zobrazení. Naše třída Kontroleru ani šablony zobrazení nevědí nic o konkrétních obchodních pravidlech vynucovaná naší třídou modelu Dinner.
Díky tomu bude architektura aplikací čistá a bude jednodušší ji testovat. Do naší vrstvy modelu můžeme v budoucnu přidat další obchodní pravidla a nebudeme muset provádět žádné změny kódu v kontroleru nebo zobrazení, aby je bylo možné podporovat. Díky tomu budeme moct v budoucnu aplikaci vyvíjet a měnit.
Náš DinnersController teď umožňuje výpisy a podrobnosti o večeři a také vytvářet, upravovat a odstraňovat podporu. Úplný kód pro třídu najdete níže:
public class DinnersController : Controller {
DinnerRepository dinnerRepository = new DinnerRepository();
//
// GET: /Dinners/
public ActionResult Index() {
var dinners = dinnerRepository.FindUpcomingDinners().ToList();
return View(dinners);
}
//
// GET: /Dinners/Details/2
public ActionResult Details(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View(dinner);
}
//
// GET: /Dinners/Edit/2
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
return View(dinner);
}
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id= dinner.DinnerID });
}
catch {
ModelState.AddRuleViolations(dinner.GetRuleViolations());
return View(dinner);
}
}
//
// GET: /Dinners/Create
public ActionResult Create() {
Dinner dinner = new Dinner() {
EventDate = DateTime.Now.AddDays(7)
};
return View(dinner);
}
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = "SomeUser";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new{id=dinner.DinnerID});
}
catch {
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(dinner);
}
//
// HTTP GET: /Dinners/Delete/1
public ActionResult Delete(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View(dinner);
}
//
// HTTP POST: /Dinners/Delete/1
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id, string confirmButton) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
dinnerRepository.Delete(dinner);
dinnerRepository.Save();
return View("Deleted");
}
}
Další krok
V naší třídě DinnersController teď máme implementovanou základní podporu CRUD (Create, Read, Update a Delete).
Podívejme se teď na to, jak můžeme pomocí tříd ViewData a ViewModel povolit ještě bohatší uživatelské rozhraní v našich formulářích.