V minulém díle jsme se naučili pracovat se sitemapou a napsali jsme si vlastního Sitemap Providera. Na tom se dalo velice krásně demonstrovat, jak je ASP.NET rozšiřitelné. Oproti jiným technologiím (např. PHP, ale nejenom to) je daleko těžší se ho naučit a sžít se s ním, ale o to silnější nástroj máte v ruce a když víte, kde, co a jak zaonačit, ušetříte si mnoho práce, přičemž výsledné řešení je velice elegantní a systémové.
Dosti už ale obecných keců, vrhneme se do práce. V tomto díle napíšeme správu kategorií. Nebude to nic nového, přidávání, úpravu a mazání záznamů jsme už dělali v předchozích dílech. Abyste si ověřili, jestli víte opravdu všechno, zkuste si zodpovědět na několik následujících otázek. Odpovědi na ně najdete také v tomto článku, ale zkuste nejdřív odpovídat bez nápovědy. Pokud máte nějaké nejasnosti, račte se do minulých dílů podívat a pořádně si to nastudovat.
Kvíz pro začátek
- Co je to ViewState a k čemu slouží?
- Co dělá komponenta SqlDataSource?
- Jaký je rozdíl mezi komponentami GridView a FormView? K čemu tyto komponenty slouží?
- Co je to membership, role a profile provider a co dělá? Kde a jak jsou uložena uživatelská jména a hesla?
- Co dělá třída StaticSitemapProvider?
- Co to je connectionString a kde bývá uložen?
- Když mám na stránce odkaz a chci, aby zezelenal, když na něj najedu myší, která technologie se k tomu použije? CSS, HTML, Javascript nebo ASP.NET?
- Zkuste si na papír nakreslit strukturu databáze aplikace Výpůjčky a šipkami znázorněte, jak na sobě které tabulky závisí a jak jsou provázané.
- Co dělají MasterPage a ContentPage? Do prohlížeče udáváme adresu na MasterPage nebo na ContentPage?
- V jakých programovacích jazycích můžeme v ASP.NET programovat?
Kvíz - správné odpovědi
Co je to ViewState a k čemu slouží?
ViewState je mechanismus, který zajišťuje uchování stavu stránky od jejího prvního načtení až do doby jejího opuštění. Pokud komponentám ve stránce nastavíme nějakou vlastnost na nějakou hodnotu, tato změna se uloží do ViewState a odešle se do prohlížeče spolu se stránkou. Když klient stránku odesílá zpět na server (tzv. PostBack), pošle se i uložený ViewState, díky čemuž se hodnoty vlastností komponent a stránky obnoví do stavu, v jakém byly po posledním zpracování na serveru. Tento mechanismus má jednu nevýhodu - ViewState může být na některých stránkách hodně velký, což zvyšuje objem přenášených dat v síti a de facto rychlost načítání stránky. V některých případech je tedy třeba zvážit, kterým komponentám ViewState povolit a kterým jej raději zakázat vlastností EnableViewState.
Co dělá komponenta SqlDataSource?
Komponenta SqlDataSource se používá pro provázání datových komponent a datového zdroje, v tomto konkrétním případě SQL databáze. Tato komponenta umí provádět základní čtyři operace - select, insert, update a delete, tedy vybrání dat, přidávání, úpravy a mazání jednotlivých záznamů. Datové komponenty se na SqlDataSource napojí vlastností DataSourceID a ten jim data z databáze poskytuje. Díky tomu je možno datové komponenty používat pro prakticky jakýkoliv zdroj dat, stačí je napojit na jiný DataSource.
Jaký je rozdíl mezi komponentami GridView a FormView?
Komponenta GridView slouží k zobrazování tabulky záznamů a případně editaci a mazání jednotlivých záznamů. Dovede zobrazit více záznamů najednou a umí je také třídit a stránkovat.
Komponenta FormView je určena k manipulaci s jedním konkrétním záznamem - umí jej zobrazit, upravit a vytvořit nový.
Co je to membership, role a profile provider a co dělá? Kde a jak jsou uložena uživatelská jména a hesla?
Membership provider je poskytovatel informací o uživatelských účech. Je to třída, která má naimplementované základní funkce pro správu uživatelů - jejich vytváření, úpravy, mazání, změny hesla, e-mailů, odemykání a zamykání účtů atd. Podobně Role provider slouží k uchovávání informací o rolích uživatelů, na jejichž základě lze provádět autorizaci požadavků uživatele. Profile provider si o uživatelích pamatuje další námi zvolené informace (např. jméno, příjmení atd.).
Nikde není přímo řečeno, kde a jak se uživatelská jména, hesla a další uchovávají, to záleží na konkrétním námi použitém providerovi. Můžeme si samozřejmě napsat vlastního providera, který jména a hesla uživatelů uloží třeba do SQL databáze, do textového souboru nebo na disketu. O tom, který provider se použije, rozhoduje naše nastavení v souboru web.config.
Co dělá třída StaticSitemapProvider?
Třídu StaticSiteMapProvider jsme použili v minulém díle, když jsme implementovali vlastního Sitemap providera. Tato třída zjednodušuje tvorbu vlastních sitemap providerů a obsahuje metodu BuildSiteMap, ve které naši sitemapu nebo její část sestavíme.
Co to je connectionString a k čemu slouží?
ConnectionString je řetězec, který popisuje umístění, parametry a způsob připojení k databázi. V ASP.NET se tyto connectionStringy ukládají do konfiguračního souboru web.config a v aplikaci se na ně odkazuje jejich názvem. Díky tomu při změně databáze není třeba měnit celou aplikaci, ale stačí opravit jeden záznam v konfiguraci.
Když mám na stránce odkaz a chci, aby zezelenal, když na něj najedu myší, která technologie se k tomu použije? CSS, HTML, Javascript nebo ASP.NET?
Tohle sice v minulých dílech seriálu nebylo, ale neustále upozorňuji, že byste měli mít přehled o HTML, CSS a Javascriptu. Změna barvy elementu po najetí myši a vůbec věci spojené se vzhledem se řeší pomocí kaskádových stylů CSS. Samozřejmě by se to dalo udělat i pomocí javascriptu, ale je to zbytečně kompilované.
Pokud jste na tuto otázku odpověděli, že to je věc ASP.NET, tak absolutně netušíte nic o tom, jak funguje HTML, CSS a Javascript a žádám vás, abyste si tyto technologie pořádně nastudovali, než začnete klást zbytečné dotazy ve fórech způsobené tím, že nechápete základní principy a nerespektujete fakt, že úplní začátečníci nemají s ASP.NET vůbec pracovat. Píšu to téměř v každém díle, bez znalosti HTML, CSS a obecného přehledu se v ASP.NET opravdu dělat nedá. ASP.NET je pouze nadstavba nad těmito technologiemi, je to nástroj, který generuje stránky v HTML a CSS. Změna barvy odkazu není věc ASP.NET.
Zkuste si na papír nakreslit strukturu databáze aplikace Výpůjčky a šipkami znázorněte, jak na sobě které tabulky závisí a jak jsou provázané.
Tohle je trochu pracné, ale měli byste dospět k něčemu takovému:
Zjednodušeně řečeno máme tabulku uživatelů Users a tabulku Roles s rolemi. Mezi nimi je vazebná tabulka UsersInRoles, která obsahuje všechny dvojice uživatel-role. Dále máme tabulku výpůjček Items a tabulku kategorií Categories. Jednotlivé položky jsou zařazeny v kategoriích (každá položka může být max. v jedné kategorii) a ke každé položce je přiřazen právě jeden uživatel, který ji vytvořil.
Co dělají MasterPage a ContentPage? Do prohlížeče udáváme adresu na MasterPage nebo na ContentPage?
MasterPage je stránka obsahující šablonu, do které se na místa určená (tzv. placeholdery) vkládá obsah z obsahových stránek (ContentPages). Například máme stránku Default.aspx, která je vložena do stránky MasterPage.master. V prohlížeči odkážeme na stránku Default.aspx, ASP.NET si zjistí, že tato stránka je vložena do MasterPage, takže požadovanou stránku "obalí" šablonou z MasterPage. Odkazujeme tedy vždy na ContentPage.
V jakých programovacích jazycích můžeme v ASP.NET programovat?
V ASP.NET můžeme programovat v jakémkoliv programovacím jazyce, který podporuje .NET framework a má pro něj vhodný kompilátor schopný zkompilovat ASP.NET aplikaci. Nejběžněji se používají jazyky C# a Visual Basic .NET, v ASP.NET se ale dá psát například v jazyce PHP.
Doufám, že jste většinu otázek byli schopni zodpovědět. Pokud ne, nevadí, ale pořádně si to dostudujte a ujasněte, abyste chápali, co se děje a jak to funguje. Není možné kopírovat a skládat kousky kódu, aniž byste chápali princip, pak něco uděláte špatně a nemůžete najít chybu, protože netušíte, kdy a jak se co provádí. Vzorové odpovědi nejsou rozhodně vyčerpávající a 100% přesné, ale k připomenutí, pochopení fungování a základní myšlenky by měly stačit. Jde o to vědět, co a jak funguje, a mít obecný přehled o technologii a jejích funkcích.
Administrační sekce - správa kategorií
Jak už bylo předesláno, vrhneme se v tomto díle na administrační sekci. Otevřete si tedy stránku Admin/Categories.aspx a přepněte se do režimu kódu. Do stránky přidejte nadpis:
<h1>Správa kategorií</h1>
Pod nadpis přetáhněte z toolboxu komponenty SqlDataSource a GridView. Pomocí nich zobrazíme kategorie, které už v systému jsou, a umožníme je upravovat a mazat. Pod tyto dvě komponenty přidejte menší nadpis:
<h2>Přidat kategorii</h2>
Za tento nadpis přidejte komponentu FormView. Nyní by vaše stránka v Design režimu měla vypadat nějak takto:
Pokud máte starší verzi Visual Studia, nebude to tak barevné a komponenty z MasterPage budou trochu rozházené, Visual Studio 2005 totiž v design režimu nevykresluje styly (pokud se používají skiny). To ale vůbec nevadí.
Nyní nastavíme SqlDataSource tak, aby uměl zobrazovat, přidávat, upravovat a odebírat jednotlivé kategorie. Rozbalte jeho nabídku úloh a vyberte Configure Data Source....
V prvním okně průvodce vyberte příslušný connectionString:
Nyní nastavíme SELECT dotaz, vyberte tabulku Categories a zaškrtněnte hvězdičku, aby se z databáze vytáhly všechny sloupce:
Ještě než okno potvrdíte, klikněte na tlačítko Advanced, abychom nechali vygenerovat i příkazy pro vytváření, úpravu a mazání záznamů:
Potvrďte a proklikejte průvodce tlačítkem Next až do konce. Tím jsme nastavili komponentu SqlDataSource pro správu kategorií. To už byste z předchozích dílů měli znát.
Nyní vyberte komponentu GridView a v okně úloh vyberte příslušný SqlDataSource:
GridView se změní tak, aby reflektoval strukturu tabulky Categories, jejíž popis si zjistil ze SELECT dotazu. Protože budeme chtít zobrazení tabulky trošku upravit, změníme sloupečky. Vybrete v okně úkolů položku Edit Columns.... Zobrazí se editor kolekce sloupců.
Smažte sloupec CategoryId, ID kategorie uživatele vůbec nezajímá, to máme jen pro účely databáze.
Nyní upravíme sloupeček Title. V seznamu vlastností napravo mu nastavte vlastnost HeaderText na hodnotu Název kategorie. Tím zajistíme, že v záhlaví tabulky se jako název sloupce zobrazí tento text. Dále nastavte vlastnost ControlStyle-Width na 400px, ať tabulka není titěrně úzká, ale vždy stále stejně široká. Stejně tak nastavte hodnotu vlastnosti ItemStyle-Width na tutéž hodnotu. ItemStyle je styl celé buňky, ControlStyle je styl textového políčka při editaci záznamu.
V naší tabulce budeme chtít záznamy upravovat a mazat, proto přidejte do seznamu sloupců ještě dva CommandFieldy - Edit, Update, Cancel a Delete.
CommandField jsou speciální sloupce, které volají na datových komponentách příkazy. Po kliknutí na ně se komponentě GridView odešle příkaz a číslo řádku, na kterém byl příkaz vyvolán. Komponenta na základě tohoto příkazu změní stav řádku nebo provede nějakou operaci. První CommandField (Edit, Update, Cancel) se zobrazí jako sloupeček se slovem Upravit. Pokud na toto slovo klikneme, komponenta dostane příkaz Edit. Co udělá? Přepne daný řádek do režimu úpravy záznamu. CommandField je nyní v řádku, který je právě upravován, nezobrazí tedy tlačítko Upravit, ale dvojici tlačítek Uložit a Zrušit. Kliknutím na první z nich odešleme komponentě příkaz Update, ona se podívá, co jsme v daném řádku vyplnili, tyto námi vyplněné hodnoty "nacpe" do parametrů komponentě SqlDataSource a provede v databázi příkaz UPDATE. Pak přepne řádek zpět do normálního režimu zobrazení. Tlačítko Zrušit pošle příkaz Cancel, který řádek jen vrátí do původního režimu. CommandField Delete odesílá příkaz Delete, čímž naznačí komponentě GridView, že má tento řádek smazat. Ta tedy nastaví do parametru SqlDataSource ID příslušného řádku a zavolá příkaz DELETE v databázi.
Proč tak složitě o těch příkazech mluvím? Úplně bez problémů můžete komponentu GridView naučit vlastní příkazy, třeba tak, že si vytvoříte TemplateField (vlastní sloupec) a do jeho šablony ItemTemplate mu přidáte třeba komponentu LinkButton. Této komponentě nastavíte vlastnosti CommandName (název příkazu) a CommandArgument (sem dáte třeba ID záznamu nebo nějaký parametr tohoto příkazu) a ošetříte komponentě GridView událost RowCommand. Tam z parametru e zjistíte název příkazu a jeho argument a můžete na základě toho třeba uživateli v daném řádku odeslat e-mail, vygenerovat fakturu nebo já nevím co ještě.
Vraťme se ale k naší tabulce - prvnímu CommandFieldu nastavte vlastnosti takto:
CancelText |
Zrušit |
EditText |
Upravit |
UpdateText |
Uložit |
Druhému CommandFieldu nastavte vlastnosti takto:
V některém z minulých dílů jsme si ukazovali, jak změnit CommandField tak, aby se před smazáním zeptal, jestli to opravdu chceme. Znovu to ukazovat nebudu, je nutné vzít třídu CommandField, podědit ji a ve vhodnou chvíli najít vygenerované tlačítko a nastavit mu vlastnost OnClientClick, což je kód v javascriptu (nebo jiném klientském skriptovacím jazyce), který se spustí před kliknutím na tlačítko. V případě, že vrátí hodnotu false, kliknutí se zruší a nic se nestane. Úplně toho samého efektu byste docílili tak, že byste do tabulky přidali komponentu TemplateField, do ní umístili LinkButton a nastavili mu CommandName na Delete a OnClientClick na hodnotu javascript: return confirm('Opravdu chcete záznam smazat?');. Funkce confirm zobrazí okénko s danou hláškou a tlačítky OK a Storno. Pokud uživatel klikne na OK, funkce vrátí hodnotu true, čímž pádem se kliknutí na tlačítko provede a stránka se odešle.
Upozornění: Komponenty CommandField nebo LinkButton nefungují v prohlížečích, které nepodporují javascript. Ne kvůli funkci OnClientClick, ta nevadí, v prohlížečích, které klientský skript neumí, by se akorát nezobrazilo potvrzení. Problém je v tom, že v HTML nelze odeslat formulář (resp. stránku) na server pomocí odkazu. To jde pouze tlačítkem submit nebo prvkem input type=image, jinak ale ne. CommandField má vlastnost ButtonType, kterou můžeme říct, že se tlačítka nemají renderovat jako odkazy, ale jako submit tlačítka nebo jako image. Submit tlačítka v tabulce nevypadají hezky, ale pokud máte nějaké pěkné ikonky, můžete místo textových tlačítek použít ikonky. Naše administrační sekce bude jen pro velmi omezenou skupinu uživatelů, takže nám nefunkčnost bez javascriptu nevadí. Leč je dobré si uvědomit, co ještě bez JS fungovat bude, a co už ne.
V tuto chvíli už umíme upravovat a mazat kategorie. Vyzkoušet to můžeme, nějaké kategorie jsme tam kvůli otestování menu již přidali minule. Spusťte si tedy aplikaci a přihlašte se. Zkuste jednu kategorii upravit a jednu smazat, pak ji zase přidáte, až to napíšeme.
Vytváření záznamů
Pro přidávání záznamů máme nachystanou komponentu FormView, také jsme již pomocí ní záznamy přidávali. Šlo by udělat i to, že bychom přidávání záznamů zaintegrovali do komponenty GridView. Podobně, jako má tabulka záhlaví (header), má také i zápatí (footer), do kterého by se přidávání záznamů nacpat dalo, ale toto řešení mi nepřijde příliš elegantní, protože všechny sloupce musíte změnit na TemplateField, abyste mohli nastavit, co bude v zápatí, pomocí šablony FooterTemplate. Proto raději použijeme samostatnou komponentu.
Klikněte tedy na FormView a propojte ho s naším datovým zdrojem:
Nyní se už přepneme do režimu kódu a vnitřní šablonu InsertItemTemplate (co se má zobrazit v režimu přidávání položek) změníme takto (ostatní šablony uvádět nebudeme, náš FormView bude vždy v režimu přidávání):
<asp:FormView ID="FormView1" runat="server" DataKeyNames="CategoryId" DataSourceID="SqlDataSource1" DefaultMode="Insert">
<InsertItemTemplate>
<asp:TextBox ID="TitleTextBox" runat="server" Text='<%# Bind("Title") %>' Width="400px" />
<asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" Text="Přidat kategorii" />
</InsertItemTemplate>
</asp:FormView>
Teď nám bude fungovat i přidávání nových kategorií. Důležité je, že jsme nastavili vlastnost DefaultMode na hodnotu Insert, čímž říkáme, že se FormView nastaví do režimu přidávání záznamů, pokud neřekneme jinak. Režim Edit by záznamy upravoval a zobrazilo by se to, co je uvnitř šablony EditItemTemplate. Ještě je šablona ItemTemplate, která je společná pro všechny režimy a zobrazí se tehdy, když není k dispozici konkrétní šablona určená přímo pro daný režim. Také se zobrazí v ReadOnly režimu, kde se jen záznam zobrazí. Úplně stejně fungují šablony u sloupců v komponentě GridView.
Validace
Na správě kategorií zbývá ještě jedna věc - validace dat. Je nutné zajistit, aby nám uživatel nevytvořil kategorii s prázdným názvem. To už jsme dříve také dělali, takže do komponenty FormView přidejte komponentu RequiredFieldValidator mezi textové pole a tlačítko. Její vlastnost ControlToValidate nastavte na TitleTextBox (ID textového pole, které kontrolujeme) a ErrorMessage na hodnotu *. RequiredFieldValidator kontroluje, jestli je v poli něco vyplněno. Pokud ne, oznámí stránce, že není validní (vlastnost Page.IsValid bude mít hodnotu False). Komponenta FormView to zjistí a žádný příkaz provádět nebude. Až všechny komponenty projdou validací, teprve pak FormView vykoná příkaz Insert.
Protože v jednu chvíli můžeme mít zobrazeno jak přidávání, tak i úpravu záznamů, je nutné vytvořit validační skupiny (pojmenujeme je třeba Insert a Edit). Díky nim se při kliknutí na tlačítko Přidat bude validovat jen textové pole pro přidávání kategorií, a ne i pole pro úpravu záznamu, které zrovna do databáze neodesíláme. Validační skupiny určujeme vlastností ValidationGroup a musíme ji nastavit jak tlačítku, tak textovému poli, tak i validátoru. Když klikneme na tlačítko, bude se validovat vše, co má stejnou ValidationGroup, jako dané tlačítko.
Změňte tedy obsah komponenty FormView takto:
<InsertItemTemplate>
<asp:TextBox ID="TitleTextBox" runat="server" Text='<%# Bind("Title") %>' Width="400px" ValidationGroup="Insert" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ErrorMessage="*" ControlToValidate="TitleTextBox" ValidationGroup="Insert"></asp:RequiredFieldValidator>
<asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" Text="Přidat kategorii" ValidationGroup="Insert" />
</InsertItemTemplate>
Obdobně musíme ošetřit editaci záznamů v komponentě GridView, tam také uživatel může odeslat prázdný záznam. Do obyčejného BoundField ale nemůžeme přidat validátor, musíme tedy sloupec nadefinovat sami, to se dělá pomocí TemplateFieldu. Otevřete si opět editor sloupců komponenty GridView, vyberte první sloupec a klikněte na odkaz Convert this field into a TemplateField:
První sloupeček byl typu BoundField, tímto tlačítkem jsme z něj udělali TemplateField. Přepněte se do režimu kódu a změňte sloupce v komponentě GridView takto (jenom do šablony EditItemTemplate přidáme validátor a textboxu, validátoru a příslušnému CommandFieldu nastavíme ValidationGroup na Edit, jiné změny tam nejsou):
<Columns>
<asp:TemplateField HeaderText="Název kategorie" SortExpression="Title">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Title") %>' ValidationGroup="Edit"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ErrorMessage="*" ControlToValidate="TextBox1" ValidationGroup="Edit"></asp:RequiredFieldValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Title") %>'></asp:Label>
</ItemTemplate>
<ControlStyle Width="400px" />
<ItemStyle Width="400px" />
</asp:TemplateField>
<asp:CommandField CancelText="Zrušit" EditText="Upravit" ShowEditButton="True" UpdateText="Uložit" ValidationGroup="Edit" />
<asp:CommandField DeleteText="Odstranit" ShowDeleteButton="True" />
</Columns>
Máme hotovou validaci dat, takže správa kategorií je téměř hotová. Víceméně. Máme zde jeden nepříjemný problém, k jehož pochopení je nutná malá odbočka do principu protokolu HTTP.
Jak funguje HTTP protokol?
Když zadáte do webového prohlížeče adresu www.vbnet.cz, prohlížeč si (nějak, to je teď jedno) zjistí, na kterém serveru se web nachází, a k tomuto serveru se připojí. Pošle mu příkaz GET, dále adresu, kterou chce, a pak ještě další data, která říkají, co prohlížeč umí, co neumí, jaká data očekává jako odpověď atd. Server požadavek zpracuje a odešle do prohlížeče stránku. Prohlížečka zjistí, že na stránce jsou nějaké obrázky, že stránka používá soubor se styly a nějaké javascriptové soubory, takže se opět připojí k serveru a metodou GET si o všechny tyto soubory zažádá - úplně stejným způsobem. Jakmile je všechny má, zobrazí stránku uživateli.
Uživatel se na ni chvíli dívá a pak si řekne, že klikne na nějaké tlačítko. Co se stane? Prohlížeč vezme celou stránku, všechny hodnoty vyplněné v polích input, které jsou ohraničené v elementu form sesbírá a odešle na server metodou POST. Server si hodnoty přečte, požadavek zpracuje a vrátí prohlížeči opět výsledný HTML kód té stránky.
Takhle to přesně funguje i s naší správou kategorií. Ja napíšu název nové kategorie a odešlu stránku tlačítkem Přidat kategorii. Server kategorii přidá a vrátí mi novou stránku s přidanou kategorií. Jenže prohlížeče mají tlačítko Aktualizovat, které nedělá nic jiného, než že zopakuje poslední požadavek. Když tedy přidáte kategorii a pak stránku zaktualizujete, kategorie se stejným názvem se přidá znovu, protože server dostane znovu poslední požadavek. Možná si říkáte, že to není moc pravděpodobné, kdo by na to klikal, to se přece nemůže stát. Ono stačí, když se najednou trochu zpomalí síť a po pár sekundách čekání začnete mačkat tlačítko Aktualizovat jako zběsilí. No a jakmile se stránka načte, tak uvidíte, že máte v tabulce deset tisíc kategorií se stejným názvem, protože se stránka odeslala desettisíckrát. To naštve.
Jaké je řešení? Použijeme přesměrování neboli redirect. Jakmile server přidá záznam do databáze, musíme zavolat Response.Redirect a přesměrovat prohlížeč na stránku s adresou Admin/Categories.aspx. On na ní sice už je, ale to nevadí. Jak to pak funguje? Klient odešle stránku na server metodou POST, server požadavek zpracuje, přidá novou kategorii, ale klientovi nepošle novou stránku, jenom mu řekne "nazdar, jako dobrý, ale novou stránku si vyzvedni na adrese ...". Prohlížeč tedy ihned vytvoří nový požadavek (tentokrát zase GET, nemá totiž co odesílat) a server mu podstrčí novou stránku s aktuálním seznamem kategorií. Pokud uživatel zopakuje poslední požadavek, už je to ten GET, který žádnou kategorii nepřidá, jen vypíše aktuální seznam kategorií.
Jak na to? Chytře!
SqlDataSource má přece události OnInserted, OnUpdated a OnDeleted, takže můžeme tyto tři události ošetřit a napsat do nich příkaz pro přesměrování. Pokud bychom tohle potřebovali jen na jedné stránce, bylo by to nepochybně správné, a také to jistě zvládnete sami. My si ale ukážeme jiné řešení, které se nám bude možná nekdy hodit i v budoucnu. Pomocí dědičnosti si vytvoříme vlastní SqlDataSource, který bude umět naprosto to samé, akorát bude mít jednu vlastnost navíc - RedirectAfterDataOperation. Pokud bude mít hodnotu True, pak nás SqlDataSource po provedení operace Insert, Update či Delete automaticky přesměruje na stránku, kde teď jsme.
Přidejte si tedy do projektu novou třídu s názvem RedirectingSqlDataSource a jako její kód použijte toto:
Imports Microsoft.VisualBasic
Namespace MojeKomponenty
''' <summary>
''' Vylepšená komponenta SqlDataSource, která po datové operaci provádí redirect
''' </summary>
Public Class RedirectingSqlDataSource
Inherits SqlDataSource
End Class
End Namespace
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MojeKomponenty
{
/// <summary>
/// Vylepšená komponenta SqlDataSource, která po datové operaci provádí redirect
/// </summary>
public class RedirectingSqlDataSource : SqlDataSource
{
}
}
Tímto kódem jsme vytvořili třídu RedirectingSqlDataSource, která je odvozená ze třídy SqlDataSource. Umí tedy naprosto to samé, co původní SqlDataSource. My si do ní akorát přidáme jednu věc, která nám tam chybí; nemusíme zahazovat 99% funkcionality, která nám vyhovuje, kvůli 1% té, co tam není. S pomocí dědičnosti si můžeme přizpůsobit jakoukoliv komponentu v ASP.NET a doplnit jí funkce, která nemá, ale které by se nám hodily. Je to velice elegantní přístup, opravdu málokdy je nutné něco psát od úplného základu.
Dovnitř naší třídy přidejte tuto vlastnost RedirectAfterDataOperation:
''' <summary>
''' Určuje, zda-li se má po provedení datové operace přesměrovat na adresu, na které se právě nacházíme
''' </summary>
<Category("Behavior"), DefaultValue(False)> _
Public Property RedirectAfterDataOperation() As Boolean
Get
If ViewState("RedirectAfterDataOperation") Is Nothing Then Return False
Return ViewState("RedirectAfterDataOperation")
End Get
Set(ByVal value As Boolean)
ViewState("RedirectAfterDataOperation") = value
End Set
End Property
/// <summary>
/// Určuje, zda-li se má po provedení datové operace přesměrovat na adresu, na které se právě nacházíme
/// </summary>
[Category("Behavior"), DefaultValue(false)]
public bool RedirectAfterDataOperation
{
get { return (bool)(this.ViewState["RedirectAfterDataOperation"] ?? false); }
set { this.ViewState["RedirectAfterDataOperation"] = value; }
}
Pokud jste ještě tento divný zápis vlastností nikdy neviděli, neděste se. V klasických aplikacích se vlastností dělají tak, že se nadeklaruje privátní proměnná a vlastnost akorát vrací a nastavuje hodnotu této proměnné. V serverových komponentách se takto vlastnosti nepíší, protože jakmile stránku odešlete klientovi, ztratí se všechny privátní proměnné. Ty se nikam neukládají, prostě zmizí. V okamžiku, kdy klient odešle stránku na server, vytvoří se všechny objekty ve stránce znovu, ale hodnoty proměnných se nemají z čeho obnovit.
Právě kvůli tomu se používá ViewState. Jak tedy tyto vlastnosti fungují? Pokud si vyžádáme hodnotu, vlastnost se podívá se do ViewState. Když tam hodnotu nenajde, vrátí nějakou výchozí hodnotu. Když už požadovaná hodnota je ve ViewState, tak vrátí tuto nalezenou hodnotu. Pokud vlastnost nastavujeme, předaná hodnota se do ViewState uloží.
V tomto konkrétním případě jsme dokonce mohli přidat klasickou vlastnost tak, jak ji známe, nic by se totiž nestalo. Do stránky, kde komponentu používáme, totiž stejně tuto vlastnost nastavíme natvrdo na true. Takže i při odeslání stránky se její hodnota nastaví tak, jak je potřeba, máme to uvedeno natvrdo ve stránce. Kdybychom ale tuto vlastnost chtěli kódem změnit, pak už by to nefungovalo. S použitím ViewState se hodnota této vlastnosti ale zachová i přes postback.
Určité kritické vlastnosti, jejichž hodnota se nesmí nikdy ztratit, se neukládají do ViewState, ten je totiž možné vypnout a často se to taky dělá, protože velikost stránky poměrně rychle roste, když se tam dá GridView plný dat. Pokud hodnotu vlastnosti potřebujeme uchovat opravdu nutně, musíme ji uložit do tzv. ControlState. Ten se ukládá na stejné místo do stránky jako ViewState (do skrytého pole __VIEWSTATE; když na úrovni aplikace zakážete ViewState, toto skryté pole ve stránce bude pořád, je v něm totiž právě ControlState). Do ControlState se ukládá jiným způsobem - vytvoří se normální vlastnost s privátní proměnnou a v komponentě se přepíší dvě metody SaveControlState a LoadControlState, kde se hodnoty proměnných uloží resp. načtou ve chvíli, kdy se ControlState načítá, resp. ukládá. To je už docela složitá věc, takže je to jen pro informaci, nemusíte si to pamatovat. Co je to ControlState byste ale vědět měli.
Dva atributy Category a DefaultValue nad vlastnostmi jsou takové popisky pro designer Visual Studia, první je kategorie, do které se má tato vlastnost zařadit, když si okno vlastností zobrazíte v režimu kategorií, a druhá určuje výchozí hodnotu, aby Visual Studio vědělo, jestli jste hodnotu vlastnosti změnili oproti výchozímu stavu, nebo ne. Hodnoty vlastností, které změníte oproti výchozímu nastavení, se totiž zvýrazní tučně.
Jak provést přesměrování?
Pomocí dědičnosti jsme vytvořili komponentu, která má oproti SqlDataSource ještě jednu vlastnost navíc. Nyní do třídy přidejte metodu RedirectIfEnabled, která, pokud to je povoleno, provede přesměrování na stránku, na které se právě nacházíme. Dovnitř třídy přidejte tento kód:
''' <summary>
''' Pokud je tato funkce zapnuta, provede přesměrování
''' </summary>
Private Sub RedirectIfEnabled(ByVal sender As Object, ByVal e As SqlDataSourceStatusEventArgs)
If Me.RedirectAfterDataOperation Then
Page.Response.Redirect(Page.Request.RawUrl)
End If
End Sub
/// <summary>
/// Pokud je tato funkce zapnuta, provede přesměrování
/// </summary>
private void RedirectIfEnabled(object sender, SqlDataSourceStatusEventArgs e)
{
if (this.RedirectAfterDataOperation)
Page.Response.Redirect(Page.Request.RawUrl);
}
Této metodě jsme nachystali dva parametry, stejné mají události Inserted, Updated a Deleted, které nás zajímají. Tyto události nastanou hned po provedení příslušných příkazů. Všechny je nasměrujeme na naši metodu RedirectIfEnabled. Ta se podívá, jestli je tato funkce přesměrování zapnuta, a pokud ano, přesměruje nás na RawUrl, což je přesná adresa, kterou si vyžádal prohlížeč, včetně všech parametrů. Na tu se přesměrujeme.
Jak obsluhu událostí nastavit? Například v metodě OnLoad, ta se totiž spustí před provedením jakýchkoliv příkazů. Abychom mohli změnit kód třídy, od které dědíme, musíme použít klíčové slovo Overrides (ve VB.NET) resp. override (v C#). Stačí, když ho napíšete, a dál si už jen z nabídky vyberete metodu, kterou chcete přepsat. V tomto případě je nutné nechat tam řádek MyBase.OnLoad(e) resp. base.OnLoad(e), který nedělá nic jiného, než že zavolá kód z původní události SqlDataSource. Nikdy nevíme, co mohl SqlDataSource v této metodě dělat, takže ho nejdříve necháme tímto řádkem udělat to, co by normálně udělal v OnLoad, a pak ještě spustíme naše tři příkazy. Kdybychom tento řádek smazali, komponenta by mohla přestat fungovat, protože by se nevykonal nějaký kód, co se má v OnLoad provést!
Tento kód tedy přidejte do naší třídy:
''' <summary>
''' Nastavit obsluhu událostí Inserted, Updated a Deleted
''' </summary>
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
AddHandler Me.Inserted, AddressOf RedirectIfEnabled
AddHandler Me.Updated, AddressOf RedirectIfEnabled
AddHandler Me.Deleted, AddressOf RedirectIfEnabled
End Sub
/// <summary>
/// Nastavit obsluhu událostí Inserted, Updated a Deleted
/// </summary>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Inserted += new SqlDataSourceStatusEventHandler(RedirectIfEnabled);
this.Updated += new SqlDataSourceStatusEventHandler(RedirectIfEnabled);
this.Deleted += new SqlDataSourceStatusEventHandler(RedirectIfEnabled);
}
Tím jsme na události navázali jako obsluhu naši přesměrovávací metodu. A naše komponenta je hotová. Díky dědičnosti a přepisování metod je možné rozšířit a pozměnit si téměř cokoliv, pokud to nenarušuje nějak zásadně princip fungování komponenty. Jen musíte vědět, co se kdy dělá; to ale není problém zjistit, když máme uvolněné referenční zdrojové kódy .NET frameworku, do kterých můžete nahlížet (kopírovat ale už ne, jen se podívat, jak to je napsáno).
Jak naši komponentu použít ve stránce? Vraťte se zpět do Admin/Categories.aspx přidejte nahoru tento řádek:
<%@ Register Namespace="MojeKomponenty" TagPrefix="moje" %>
Tím řekneme Visual Studiu, že všechny komponenty, které najde ve jmenném prostoru MojeKomponenty, do kterého jsme umístili náš vylepšený SqlDataSource, zpřístupní jako komponenty ve stránce pod prefixem moje. Najděte tedy v naší stránce definici komponenty SqlDataSource1 a změňte název tagu asp:SqlDataSource na moje:RedirectingSqlDataSource (je potřeba ho změnit na dvou místech - na začátku i na konci), mělo by to vypadat takto:
<moje:RedirectingSqlDataSource ID="SqlDataSource1" runat="server"
...
</moje:RedirectingSqlDataSource>
Této komponentě nyní nastavíme ještě vlastnost RedirectAfterDataOperation (přesměrovat po operaci s daty) na hodnotu true, aby se přesměrování provedlo:
RedirectAfterDataOperation="true"
A to je celé, teď by to mělo fungovat i s přesměrováním. Když přidáte kategorii a zmáčknete F5, nic se už nestane. Tak je to správné.
Pro tento díl je to vše, příště se podíváme na správu uživatelů, ta bude vypadat trochu jinak, ale využijeme to, co jsme se naučili dnes. Celá tabulka by chtěla ještě nějak dostylovat, aby vypadala hezky, to uděláme taky příště.