Jak jsme si posledně slíbili, dnes doděláme podporu pro přidávání a mazání večeří.
Začneme vytvořením podpory pro přidávání večeří a s tím související akční metodou Create.
Akční metoda Create
HTTP-GET
Nejdříve vytvoříme GET verzi naší nové metody, která se zavolá tehdy, když uživatel zadá URL „/Dinners/Create“, a vygeneruje formulář pro přidání nové večeře. Není tady moc co řešit, vše potřebné už umíme, takže do DinnersControlleru jen přidáme následující metodu:
// GET: /Dinners/Create
public ActionResult Create()
{
Dinner dinner = new Dinner()
{
EventDate = DateTime.Now.AddDays(7)
};
return View(dinner);
}
Tato metoda vytvoří nový Dinner objekt s předem nastaveným časem konání večeře (to aby měl uživatel představu, v jakém formátu má datum zadat) a pak vrátí view, který teď přidáme.
Už tradičně klikneme pravým tlačítkem dovnitř metody, zvolíme položku „Add View“ a dialog nastavíme podle obrázku:
Vygenerovanou šablonu rovnou upravíme tak, aby vypadala následovně (je téměř identická s tou, kterou jsme vytvořili pro upravování večeří):
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.Dinner>" %>
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Vytvořit večeři
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Vytvořit večeři</h2>
<%=Html.ValidationSummary("Opravte prosím všechny chyby a zkuste to znovu.") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<p>
<label for="Title">Název:</label>
<%= Html.TextBox("Title") %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="EventDate">Datum:</label>
<%=Html.TextBox("EventDate") %>
<%=Html.ValidationMessage("EventDate", "*") %>
</p>
<p>
<label for="Description">Popis:</label>
<%=Html.TextArea("Description") %>
<%=Html.ValidationMessage("Description", "*") %>
</p>
<p>
<label for="Address">Adresa:</label>
<%=Html.TextBox("Address") %>
<%=Html.ValidationMessage("Address", "*") %>
</p>
<p>
<label for="Country">Země:</label>
<%=Html.TextBox("Country") %>
<%=Html.ValidationMessage("Country", "*") %>
</p>
<p>
<label for="ContactPhone">Telefon:</label>
<%=Html.TextBox("ContactPhone") %>
<%=Html.ValidationMessage("ContactPhone", "*") %>
</p>
<p>
<label for="Latitude">Zem. šířka:</label>
<%=Html.TextBox("Latitude") %>
<%=Html.ValidationMessage("Latitude", "*") %>
</p>
<p>
<label for="Longitude">Zem. délka:</label>
<%=Html.TextBox("Longitude") %>
<%=Html.ValidationMessage("Longitude", "*") %>
</p>
<p>
<input type="submit" value="Uložit"/>
</p>
</fieldset>
<% }
%>
</asp:Content>
A je to, během pár minut jsme vytvořili šablonu pro přidávání nových večeří! Teď je na řadě vytvořit POST verzi metody Create.
HTTP-POST
Tato verze metody Create se zavolá ve chvíli, kdy uživatel klikne na tlačítko „Uložit“ ve formuláři. Stejně jako v případě upravování večeří, i zde nejdříve přidáme přetíženou verzi akční metody Create označenou atributem „AcceptVerbs“:
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner)
{
// ...
}
Tím, že tato metoda přijímá Dinner parametr, docílíme toho, že ASP.NET MVC automaticky vezme zadaná data ve formuláři, vytvoří objekt typu Dinner a ten předá naší metodě, která ho zkontroluje a, pokud bude vše v pořádku, uloží do databáze. Implementace metody bude vypadat takto:
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
try
{
dinner.HostedBy = "Někdo";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
catch
{
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(dinner);
}
Abychom viděli, jak to funguje, aplikaci spustíme a zadáme URL „/Dinners/Create“:
Všimněte si, že naše už dávno vytvořená validační logika funguje bezchybně i během vytváření večeří. Pořadatele večeře (vlastnost HostedBy) nastavujeme natvrdo v kódu, protože v budoucnu se budou moci uživatelé registrovat a jméno se bude doplňovat dynamicky podle právě přihlášeného uživatele.
Když opravíme všechny chyby a klikneme na tlačítko „Uložit“, vše by mělo proběhnout v pořádku a aplikace nás přesměruje na stránku s detaily nové večeře.
Akční metoda Delete
Díky této metodě, jak už název napovídá, budeme moci večeře mazat. Nejdříve implementujeme HTTP-GET, pak HTTP-POST.
HTTP-GET
Tato verze metody se zavolá, když uživatel zadá URL „/Dinners/Delete/{id}“, implementace bude vypadat takto:
// GET: /Dinners/Delete/1
public ActionResult Delete(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View(dinner);
}
Pokud nebude večeře, kterou chceme smazat, existovat, použijeme NotFound view, v opačném případě se použije Delete view, který teď vytvoříme. Tento krok děláme asi posté – jednoduše klikneme dovnitř metody Delete, zvolíme „Add View“ a dialog nastavíme podle obrázku:
Vytvoří se nám téměř prázdná šablona, kterou trochu doplníme, abychom dostali něco takového, jako ukazuje následující kód:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.Dinner>" %>
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Potvrzení smazání: <%=Html.Encode(Model.Title) %>
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Potvrzení smazání
</h2>
<div>
<p>Skutečně chcete smazat večeři s názvem:
<i> <%=Html.Encode(Model.Title) %>? </i>
</p>
</div>
<% using (Html.BeginForm()) { %>
<input name="confirmButton" type="submit" value="Smazat" />
<% } %>
</asp:Content>
Hned to můžeme na nějaké večeři vyzkoušet (URL ve tvaru „/Dinners/Delete/{id}“):
HTTP-POST
Teď máme stránku, která zobrazuje potvrzení ke smazání. Když uživatel klikne na tlačítko „Smazat“, zavolá se POST verze naší metody, to už teď jistě víte. Proto tuto přetíženou metodu hned vytvoříme, bude to opět záležitost minutky. Implementace je jednoduchá, zase vše zařídí repository třída, kterou jsme si kdysi napsali:
// 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");
}
Budeme potřebovat vytvořit nový view, který zobrazí zprávu o tom, že smazání proběhlo úspěšně. Klikneme dovnitř metody pravým tlačítkem a zvolíme „Add View“ (jaké to překvapení). Ovšem je tady malá změna, nic nebudeme nastavovat, políčko „Create a strongly-typed view“ by mělo být odškrtlé. Jen přejmenujeme šablonu na „Deleted“ a klikneme na tlačítko „Add“. Teď do šablony přidáme takovéto HTML:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Večeře smazána
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Večeře smazána</h2>
<div>
<p>Večeře byla úspěšně smazána.</p>
</div>
<div>
<p><a href="/dinners">Klikněte pro zobrazení chystaných večeří</a></p>
</div>
</asp:Content>
Když teď večeři smažeme, všechno proběhne v pořádku:
Bezpečnost při databindingu
Ukázali jsme si, jak je pomocí metody UpdateModel snadné automaticky vytáhnout zadaná data z formuláře a vytvořit pomocí nich nový Dinner objekt. Stejně jako spousta dalších věcí ve webovém programování, i toto přináší určitá rizika – uživatel ani náhodou nesmí být schopný upravit kolekci RSVPs (v ní budou uloženi uživatelé přihlášení k večeřím), nesmí ani moci upravit vlastnosti DinnerID nebo HostedBy. Zamezíme tomu tím, že třídu Dinner odekorujeme atributem Bind, ve kterém explicitně definujeme vlastnosti, které mají být zahrnuty v databindingu:
[Bind(Include = "Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longitude")]
public partial class Dinner
{ //...
CRUD je hotov!
Gratuluji, že jste to zvládli až sem, CRUD máme hotový. Naše aplikace tedy podporuje čtení, vytváření, mazání i upravování večeří. Ukažme si ještě zdrojový kód kompletního DinnersControlleru a na shledanou u příštího dílu, ve kterém si povíme o třídách ViewData a ViewModel.
using System;
using System.Linq;
using System.Web.Mvc;
using NerdDinner.Models;
namespace NerdDinner.Controllers
{
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 = "Někdo";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
catch
{
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(dinner);
}
// GET: /Dinners/Delete/1
public ActionResult Delete(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View(dinner);
}
// 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");
}
}
}