Píšeme webovou aplikaci v ASP.NET krok za krokem (část 4)

11. díl - Píšeme webovou aplikaci v ASP.NET krok za krokem (část 4)

Tomáš Herceg       26.08.2008       C#, VB.NET, ASP.NET WebForms, HTTP/HTML       20573 zobrazení

V tomto díle se podíváme na editaci uživatelů, práci s třídou Membership a obecným API pro práci s uživateli. Popíšeme si také celý životní cyklus webové stránky v ASP.NET a pořadí jednotlivých událostí. Také si vysvětlíme pojem NamingContainer.

V minulém díle jsme úspěšně napsali správu kategorií, do kterých budeme naše výpůjčky řadit. V tomto díle si napíšeme správu uživatelů a jejich přiřazování do rolí. Ještě než začneme, sepsal jsem seznam 10 bodů, které byste v ASP.NET měli zvládat. Pokud ne, mrkněte se do předchozích dílů tohoto seriálu, jistě je tam najdete. Vřele doporučuji si vymyslet nějaký ukázkový příklad, na kterém si všechny tyto body provcičíte, a opravdu si ho udělat.

  • Vytvořit MasterPage a začlenit do ní ContentPages.
  • Vytvořit v databázi tabulku a provázat ji cizím klíčem s jinou tabulkou.
  • Pomocí komponent SqlDataSource a GridView zobrazit obsah této tabulky ve stránce.
  • Umožnit editaci záznamů v komponentě GridView.
  • Umožnit přidávání záznamů v komponentě FormView.
  • Umožnit přidávání a editaci záznamů v komponentě FormView na jiné stránce, než je GridView.
  • Používat validátory a kontrolovat zadání hodnot jednotlivých polí.
  • Vytvořit sitemapu webu a doplnit do ní vlastní seznam položek z databáze.
  • Používat různé typy fieldů - BoundField, HyperLinkField, TemplateField, CommandField.
  • Umět používat libovolného Membership a Role providera a nastavit přístup do různých částí aplikace různým uživatelům, případně rolím.

Jak spravovat uživatele?

Jsou dvě možnosti. Protože používáme sadu providerů od Michala A. Valáška, víme, že si uživatele ukládají do tabulky Users v databázi. Takže by neměl být problém napsat tabulku, kde budeme moci uživatele zobrazit a mazat. Přidávání uživatelů, na to je komponenta CreateUserWizard, takže to ani nemusíme řešit, a co by se na nich upravovalo? Uživatelské jméno jim měnit nebudeme, a přiřazovat a vyřazovat z role Admin, to se taky nějak udělá, konec konců, je to v databázi.

Tak to by bylo to špatné řešení, resp. ne úplně špatné, ale to horší. Pokud víme, že providery nikdy v životě nebudeme měnit, tak to vůbec nevadí a klidně ho můžeme použít. Na druhou stranu se na tom už nic nového nenaučíme, což by byla v tomto seriálu škoda, proto použijeme jiné řešení, které bude fungovat s libovolnou sadou providerů, ať už s vestavěnými ASP.NET molochy, s těmi, které používáme my, anebo s kterýmikoliv jinými.

Všechny důležité funkce pro práci s uživateli sdružuje třída Membership. Všechny funkce, které obsahuje, vlastně jen volají funkce konkrétní implementace providerů nastavených v souboru web.config. Třída Membership má třeba metodu GetAllUsers, jež vrátí seznam všech uživatelů v aplikaci, je to kolekce objektů User.

Zobrazení seznamu uživatelů v GridView

Jednou z mnoha výhod datových komponent v ASP.NET je to, že dovedou zobrazit téměř libovolnou sadu dat. Buď jim jako datový zdroj předáte nějaký SqlDataSource, který jim jako data "podstrčí" výsledek SQL dotazu nad databází, případně můžete použít jiný zdroj dat. Nebo můžete nechat zobrazit libovolnou kolekci objektů, což uděláme my nyní. Neobejde se to bez trochy kódu, ale nebude to nic hrozného. Pro každý objekt v kolekci pak GridView vytvoří nový řádek a umožní přistupovat k vlastnostem tohoto objektu. Třída User, kolekci jejíchž instancí dostaneme, má například vlastnost UserName a Email.

Otevřete si tedy projekt z minula a do stránky Admin/Users.asp přidejte komponentu GridView a nadpis Správa kategorií:

<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" Title="Správa uživatelů" %>

<script runat="server">

</script>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">

    <h1>Správa uživatelů</h1>
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
    
</asp:Content>

Abychom náš GridView naplnili daty, předáme mu kolekci uživatelů do vlastnosti DataSource. Aby si komponenta data načetla, musíme na ní zavolat ještě DataBind. To celé provedeme v události Page_Load. Tento kus kódu vložte dovnitř elementu script:

Kód v jazyce Visual Basic .NET

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        GridView1.DataSource = Membership.GetAllUsers()
        GridView1.DataBind()
    End Sub
Kód v jazyce C#
    protected void Page_Load(object sender, EventArgs e)
    {
        GridView1.DataSource = Membership.GetAllUsers();
        GridView1.DataBind();
    }

Když si stránku zobrazíme, bude to vypadat dost šíleně. Jednak je informací o uživateli mnoho, takže se nám tabulka ani nevejde do stránky a "vyleze" nám ven, a za druhé by to chtělo změnit názvy sloupců a zobrazit jen smysluplné informace. Nespokojíme se tedy s automaticky vygenerovanou strukturou a nadefinujeme si vlastní sloupce tak, jak potřebujeme. Nastavte tedy komponentě GridView vlastnost AutoGenerateColumns na false, čímž jí řekneme, že sloupce určujeme ručně. Dovnitř musíme pak založit element Columns, ve kterém jednotlivé sloupce přesně popíšeme.

    <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" DataKeyNames="UserName">
        <Columns>
            <asp:BoundField HeaderText="Uživatelské jméno" ItemStyle-Font-Bold="true" DataField="UserName" />
            <asp:BoundField HeaderText="E-mail" DataField="Email" />
            <asp:BoundField HeaderText="Naposledy přihlášen" DataField="LastLoginDate" />
        </Columns>
    </asp:GridView>

Komponenta GridView tedy nyní obsahuje 3 sloupce typu BoundField. Důležité vlastnosti, které byste měli znát, jsou HeaderText (text v záhlaví) a DataField (sloupce z DB či vlastnost objektu apod., která se v buňce zobrazí).

Všimněte si, že jsem komponentě GridView přidal ještě vlastnost DataKeyNames a nastavil jí hodnotu UserName. Každá tabulka v databázi by měla mít svůj primární klíč, objekty v kolekci by také měly mít něco podobného, nějakou hodnotu, podle které je možné je jednoznačně určit. Touto hodnotou jsou uživatelská jména, která musí být jednoznačná. Řekneme tedy, že "primární klíč" pro identifikaci řádků je hodnota vlastnosti UserName. To se nám bude později hodit.

V minulém díle jsme sloupce upravovali pomocí editačního okna. Podotýkám, že editor sloupců jen do GridView vygeneruje kód, který jsme do něj teď vložili ručně. Tu samou věc můžeme udělat více způsoby, je jen na vás, kterou si vyberete. Já vhodně kombinuji obě podle toho, která je v dané chvíli rychlejší a pohodlnější.

Po nadefinování sloupců už tabulka bude vypadat poněkud příčetněji:

Tabulka s nadefinovanými sloupci

Přiřazování uživatelů do rolí

U každého řádku by se nám hodil CheckBox (zaškrtávací políčko), kterým bychom mohli na jedno kliknutí uživatele přiřadit do role admin anebo jej z této role vyřadit. Přidáme tedy do GridView nový sloupec TemplateField a sami si přesně nadefinujeme, co ve sloupci bude. Přidáme dovnitř komponentu CheckBox a nastavíme jí vlastnost AutoPostBack na true, což způsobí, že pokud zaškrtnutí políčka změníme, stránka se hned odešle na server (jinak by se událost CheckedChanged určující, že zaškrtnutí bylo změněno, zavolala až po odeslání stránky jiným způsobem, například nějakým tlačítkem).

Toho, aby byly zaškrtnuté právě ty řádky, kde uživatel v roli admin už je, dosáhneme nejjednodušeji použitím binding expression, které už známe. Obyčejně v nich bývá Eval či Bind a v závorce za nimi název sloupce, ale obecně tam může být jakýkoliv výraz, jehož hodnota se vyhodnotí a výsledek se do příslušné vlastnosti nastaví. Jen připomínám, že Bind je obousměrná vazba, jednak z datového zdroje hodnotu nastaví do komponenty, a druhak umí zase hodnotu vlastnosti vytáhnout z komponenty do datového zdroje. Eval je jen jednosměrná vazba, kterou nyní použijeme.

To, jestli je uživatel v nějaké roli, zjistíme pomocí funkce Roles.IsUserInRole, které předáme jako parametry jméno uživatele a jméno role. Funkce nám vrátí true či false. Výsledek můžeme rovnou přiřadit do vlastnosti Checked našeho CheckBoxu. Tento kód tedy přidejte jako další sloupec komponenty GridView.

            <asp:TemplateField HeaderText="Administrátor" ItemStyle-HorizontalAlign="Center">
                <ItemTemplate>
                    <asp:CheckBox ID="AdminCheckBox" runat="server" AutoPostBack="true" Checked='<%#Roles.IsUserInRole(Eval("UserName").ToString(), "admin") %>' />
                </ItemTemplate>
            </asp:TemplateField>

Nyní nám tedy zbývá ošetřit událost CheckedChanged našeho CheckBoxu. Podle toho, jestli bude zaškrtnutý, nebo ne, uživatele do role admin přidáme, nebo z této role odebereme. Nahoře v okně kódu vyberte v prvním rozbalovacím seznamu komponentu AdminCheckBox a ve druhém událost CheckedChanged. Tím se nám vygeneruje její kód.

Protože komponenta AdminCheckBox je uvnitř šablony (ItemTemplate) a bude se ve stránce pravděpodobně vyskytovat víckrát, nemůžeme k ní přistupovat pomocí jejího ID. V tomto případě se k ní dostaneme pomocí parametru sender, který naše událost dostane, je jím totiž vždy komponenta, která událost vyvolala. Protože model událostí je obecný, je parametr sender typu Control. Z tohoto typu jsou odvozeny všechny komponenty. Abychom se ale dostali k vlastnosti Checked, musíme si sender přetypovat na CheckBox. To můžeme udělat, protože víme, že tuto událost vždycky vyvolá CheckBox a nic jiného. Typ Control tuto vlastnost nemá, typ CheckBox ano.

Dále si musíme zjistit jméno uživatele. Komponenta GridView si pamatuje hodnoty klíčů pro každý řádek. Jako klíč jsme označili sloupec UserName, pro každý řádek jsme tedy schopni zjistit si jméno uživatele, kterého se řádek týká. Klíče má komponenta GridView uloženy ve vlastnosti DataKeys. Abychom ale mohli klíč daného řádku najít, musíme vědět, na kterém řádku leží náš CheckBox. Jak to zjistit?

Pokud máme nějakou šablonu, která se bude ve stránce opakovat víckrát, hrozí tak, že by se nám do stránky dostalo několik komponent se stejným ID. Typickým příkladem je náš nový sloupec, který má CheckBox s ID AdminCheckBox. Pro každý řádek se tato komponenta do stránky vygeneruje právě jednou, proto aby nenastávaly kolize ve jménech, má ASP.NET pro tyto účely zaveden pojem NamingContainer. Každý exemplář šablony, který se do stránky vyrenderoval, musí být ve vlastním NamingContaineru. V rámci tohoto containeru má svoje původní ID, v rámci celé stránky se k němu už pod jeho ID nedostaneme. Do výsledného HTML ve stránce se pak před všechna ID elementů přidá nějaký unikátní prefix, např. ctl00_. Tak zůstanou ID komponent unikátní. Zkuste se do HTML kódu stránky schválně podívat.

Pokud byste si psali vlastní komponentu a potřebovali byste, aby byla NamingContainer, jednoduše v ní naimplementujete rozhraní INamingContainer. Celá stránka je také jeden velký NamingContainer, nesmí obsahovat víc komponent se stejným ID. Z nadřízeného NamingContaineru (třeba ze stránky) se na ID komponent uvnitř nedostanete přímo, jedině přes metodu FindControl.

V případě komponenty GridView je takovým NamingContainerem každý řádek tabulky. Řádek v GridView je datového typu GridViewRow, stačí si tedy přetypovat NamingContainer našeho CheckBoxu na typ GridViewRow a dostaneme tím řádek, na kterém náš CheckBox leží. Index v poli DataKeys komponenty GridView pak zjistíme z vlastnosti DataItemIndex, kterou má náš nalezený řádek. Tak už získáme dotyčnou hodnotu klíče.

Tento kód vložte do procedury AdminCheckBox_CheckedChanged:

Kód v jazyce Visual Basic .NET

        Dim chb As CheckBox = CType(sender, CheckBox)
        Dim row As GridViewRow = CType(chb.NamingContainer, GridViewRow)
        Dim username As String = GridView1.DataKeys(row.DataItemIndex).Value
        Page.Title = username

Kód v jazyce C#

        CheckBox chb = (CheckBox)sender;
        GridViewRow row = (GridViewRow)chb.NamingContainer;
        string username = (string)GridView1.DataKeys[row.DataItemIndex].Value;
        this.Title = username;

Zatím to nedělá nic, jen získáme náš CheckBox a aktuální řádek, a pomocí něj si vytáhneme do proměnné username jméno uživatele na daném řádku. Abychom viděli, že to funguje, přiřadíme si toto jméno do Page.Title, čímž se zobrazí v titulkovém pruhu prohlížeče.

Když to vyzkoušíte, fungovat to ovšem nebude. Po kliknutí se nic nestane. Kbybyste projekt pustili v režimu Debug a dali si breakpoint dovnitř události CheckedChanged, nezachytí se. Z toho plyne, že se nám událost nevyvolá. Jak to?

Životní cyklus stránky

Od chvíle, kdy webový server obdrží HTTP požadavek a začne jej pomocí ASP.NET zpracovávat, až do okamžiku, kdy se vygeneruje výsledné HTML, se stane mnoho věcí. Celému tomuto procesu říkáme životní cyklus ASP.NET stránky. Podrobně je popsaný na MSDN, já jej ve stručnosti a zjednodušeně vysvětlím zde. Pak pochopíme, proč se nám ta potvora událost nevyvolá.

PreInit Vytvoření komponent ve stránce, začlenění do MasterPage, aplikování vzhledu a témat.
Init Inicializace stránky a všech komponent na výchozí hodnoty
PreLoad Načtení hodnot z ViewState a POST dat do komponent
Load Zde můžeme např. nastavovat a číst hodnoty komponent, nastavovat parametry spojení s databází, SelectCommand u SqlDataSource, hodnoty parametrů příkazu atd. Ještě nemáme k dispozici dynamicky generované komponenty, neproběhl databinding!
Zpracování událostí komponent V této fázi dochází k databindingu, vyvolají se události DataBinding, pak se data načtou a vytvoří se dle nich potřebné struktury, bezprostředně poté následuje událost DataBound.
Také se zjistí, které vlastnosti se změnily a které události nastaly, proč se uskutečnil postback (která komponenta ho vyvolala), a podle toho se vyvolají procedury, které tyto události obsluhují.
LoadComplete Nastane, když jsou zpracovány všechny události a databinding je hotov.
PreRender Poslední možnost, kdy je možné změnit vlastnosti komponent a pracovat s kompletně načtenou stránkou. Po této události se uloží změněné vlastnosti komponent do ViewState.
Render Vyrenderování celé stránky do HTML.
Unload Finalizace, uzavření spojení s databází, uvolnění použitých zdrojů atd.

Důležité je především to, kdy se načítá a ukládá ViewState. Načítá se až po události PreLoad, do té doby tedy komponenty ve stránce mají výchozí hodnoty. Nedávno v diskusi na tomto webu byl dotaz, jak nastavit komponentě hodnotu jen při prvním načtení. Nakonec to tazatel sám "vyřešil" tak, že použil událost Init. Ono to sice funguje, tazatel byl spokojen, přestože nevěděl, proč to tak je. Při prvním načtení stránky ve ViewState nic nemáme, hodnota se tedy nastaví ve fázi Init a taková už zůstane. Pokud ale hodnotu změníme po události PreLoad, uloží se její změna do ViewState. Při odeslání stránky se událost Init vyvolá opět a nastavíme do ní hodnotu původní, po PreLoad se ale tato hodnota přepíše hodnotou z ViewState, takže se to chová opravdu tak, jak tazatel potřeboval. Jeho inicializace ve fázi Init se projeví jen při prvním načtení, pak už je přepsána hodnotou z ViewState anebo z toho, co uživatel ve stránce změnil (např. text v textovém poli) a co se odeslalo v POST požadavku. Důležité je ale vědět, proč to tak je, a navíc Init je událost, která nastává dost brzo, takže ještě nemáme stránku v "kompletním" stavu, s mnoha komponentami tedy nemůžeme pracovat.

Už chápete, v čem je problém, proč se nám událost nevyvolává? My v Loadu načteme do GridView všechna data o uživatelích, pak se stránka odešle na klienta. V Loadu se opět načte aktuální stav do tabulky, událost CheckedChanged ale nastává až potom. Když se ASP.NET podívá, jestli se hodnota vlastnosti Checked změnila, zjistí, že ne. Hodnota, která nám přišla z formuláře, se přepsala v události Load, kdy jsme do GridView natáhli všechna data znovu. Při zjišťování, jestli se hodnota změnila, se totiž kontroluje, jestli je ve ViewState stejná hodnota jako v komponentě. Tady bude, ViewState obsahuje hodnoty v minulém požadavku a v Loadu jsme do zaškrtávacího políčka nahráli to, co je v databázi. Hodnoty tedy budou stejné a událost se nevyvolá.

Jak z toho ven? Můžeme využít ViewState, když už ho máme. Data tedy do tabulky stačí totiž jen načíst při prvním načtení stránky, GridView si je pak automaticky uloží do ViewState a při odeslání zpět na server je zase pěkně obnoví. Je nutné si ale uvědomit, že celý obsah tabulky se v tomto případě uloží do ViewState, při velkém počtu uživatelů tedy stránka bude poměrně velká. Na druhou stranu se dá nastavit, aby se ViewState ukládal na serveru v session a neposílal se tam a zpátky na klienta, čímž nám nebude navyšovat objem přenášených dat. Navíc naše aplikace bude spravovat jen malý počet uživatelů, takže tam nás to nezabije, ViewState nebude mít v našem případě nijak závratnou velikost.

Použijeme tedy toto řešení, realizace je jednoduchá. Data do GridView nalijeme pouze v případě, že nemáme postback, tedy jen když se stránka načítá poprvé. Zbytek už vyřeší ViewState. To, jestli na stránku přistupujeme poprvé, zjistíme tak, že vlastnost Page.IsPostBack má hodnotu False.

Změňte tedy kód v události Load takto:

Kód v jazyce Visual Basic .NET
        If Not Me.IsPostBack Then
            GridView1.DataSource = Membership.GetAllUsers()
            GridView1.DataBind()
        End If

Kód v jazyce C#

        if (!this.IsPostBack)
        {
            GridView1.DataSource = Membership.GetAllUsers();
            GridView1.DataBind();
        }

Teď to bude fungovat správně, událost CheckedChanged se vyvolá a do názvu stránky se uloží uživatelské jméno, které jsme zaškrtli nebo odškrtli. Teď už můžeme jednoduše provést triviální přiřazení či vyhození uživatele do/z role.

K tomu slouží metody Roles.AddUserToRole a Roles.RemoveUserFromRole, kterým se předá jméno uživatele a jméno role. Je to tedy velmi jednoduché. Tento kód dejte místo přiřazování username do titulku stránky:

Kód v jazyce Visual Basic .NET

        If chb.Checked Then   'přiřadit uživatele do role
            Roles.AddUserToRole(username, "admin")
        Else                  'odebrat uživatele z role
            Roles.RemoveUserFromRole(username, "admin")
        End If

Kód v jazyce C#

        if (chb.Checked)    // přiřadit uživatele do role 
            Roles.AddUserToRole(username, "admin");
        else                // odebrat uživatele z role
            Roles.RemoveUserFromRole(username, "admin");

Pokud je CheckBox zaškrtlý, pak uživatele do role admin přidáme, pokud není, uživatele odebereme. Samozřejmě tohle by chtělo ještě ošetřit kvůli chybám, v okamžiku, kdy budou dva lidé upravovat jeden záznam, může nastat problém, protože se budeme snažit přidat do role nějakého uživatele, který tam už je. Šlo by před přidávání a odebírání udělat test, jestli uživate je admin a podle stavu CheckBoxu zjistit, jestli je stav správný. A samozřejmě by to chtělo ještě udělat zámek na role a výše uvedenou kontrolu a samotné manipulace provést uvnitř tohoto zámku, abychom měli jistotu, že nebudeme do tabulky rolí hrabat v jednu dobu. Pokud jste líní, můžete celou šarádu místo zámků a kontrol dát do Try bloku a při chybě uživateli stránku refreshnout do aktuálního stavu.

To jsou samozřejmě krajní případy, ale i ty mohou v reálných aplikacích nastat (a stávají se neuvěřitelně často, prostě a jednoduše zákon schválnosti funguje). Bez ošetření to jednomu z uživatelů vyhodí chybovou stránku, metody AddUserToRole a RemoveUserFromRole totiž vyhodí výjimku v případě, že chceme uživatele do role přiřadit a už tam je, či ho vyřadit z role, kde pro změnu není.

Mazání uživatelů

Tohle bude jednoduché, přidáme si do tabulky TemplateField a dovnitř vložíme LinkButton. Nastavíme mu CommandName třeba na DeleteUser a do CommandArgument nabindujete přes binding expression jméno uživatele. V události RowCommand komponenty GridView pak v podmínce zjistíte, zda-li je e.CommandName rovno DeleteUser a pokud ano, tak zavoláte Membership.DeleteUser a jako argument předáte e.CommandArgument. Je to velmi jednoduché, takže si to zkuste sami. Pokud potřebujete pomoct, zde je správné řešení.

            <asp:TemplateField HeaderText="">
                <ItemTemplate>
                    <asp:LinkButton ID="DeleteUserButton" runat="server" CommandName="DeleteUser" CommandArgument='<%#Eval("UserName") %>' OnClientClick="javascript: return confirm('Opravdu chcete uživatele smazat?');">Odstranit</asp:LinkButton>
                </ItemTemplate>
            </asp:TemplateField>

Kód v jazyce Visual Basic .NET

    Protected Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs)
        If e.CommandName = "DeleteUser" Then        'smazat uživatele
            Membership.DeleteUser(e.CommandArgument)
        End If
    End Sub

Kód v jazyce C#

    protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        if (e.CommandName == "DeleteUser")      // smazat uživatele
            Membership.DeleteUser(e.CommandArgument.ToString());
    }

To je celé mazání uživatelů. Zase by to chtělo ošetřit výjimky, co kdyby mi někdo pod rukama uživatele smazal, ale to už je detail. Jen doplňuji, že LinkButton odešle komponentě GridView příkaz DeleteUser s parametrem rovným uživatelskému jménu daného uživatele. My na tento příkaz zareagujeme v události RowCommand. Některé příkazy GridView ošetřuje sám, vlastní si můžeme přidat právě tímto způsobem.

Jak si nepodříznout větev pod zadkem

V této fázi máme stránku skoro hotovou, ještě bych možná provedl jednu věc - zakázal bych vyřadit uživatele admin z role admin a zakázal bych ho i smazat. Pokud si omylem kliknete na první řádek (anebo jste to už omylem udělali jako já, ostatně proto tady tato sekce je) a vyhodíte se tím z role admin, už se do administrace nedostanete.

Chtělo by to tedy CheckBoxu v řádku admin nastavit vlastnost Enabled na False a tlačítku odstranění vlastnost Visible na False. Nejjednodušší cesta je použít opět binding expression. Pokud se obě strany budou rovnat, výsledkem bude False, čili pro admina se komponenty uzamknou. Pokud se rovnat nebudou, výsledkem bude True, čímž pádem se komponenty odemknou.

Komponentě CheckBox tedy přidejte tuto vlastnost (v každém programovacím jazyce je to trošku jinak!):

Kód v jazyce Visual Basic .NET
 Enabled='<%#Eval("UserName") <> "admin" %>' 

Kód v jazyce C#

 Enabled='<%#Eval("UserName").ToString() != "admin" %>'

To samé udělejte u komponenty LinkButton, akorát slovíčko Enabled zaměňte za Visible. Výraz bude mít hodnotu True vždy, když uživatelské jméno nebude admin. To je přesně to, co potřebujeme, vypadá to takto:

Hotová administrace uživatelů

Samozřejmě bychom neměli po těchto akcích zapomenout na přesměrování na stránku, na níž právě jsme, díky čemuž se zamezí vzniku chyby při opětovném odeslání. To jsme už dělali minule upravením komponenty SqlDataSource, takže víte, jak na to. V některých aplikacích se ani tento případ neošetřuje, já to zde již demonstrovat nebudu.

Nastylování komponenty GridView

Aby tabulky se seznamy v naší aplikaci vypadaly hezky, trochu je obarvíme. Už dříve v tomto seriálu jsem ukazoval, jak jednoduše obarvit GridView pomocí CSS stylů. Mohli bychom barvy pozadí nastavovat přímo, ale do stránky by se vygenerovaly šílené inline styly pro každou buňku a řádek, což rozhodně nechceme, při větších tabulkách by to zabíralo moc místa a nebylo by to efektivní.

Jednoduše tedy namapujeme jednu CSS třídu na celé GridView, další dvě použijeme na sudé a liché řádky. Záhlaví obarvíme jednoduše, používají se v něm elementy th místo td u normálních buněk. Do souboru se styly tedy přidejte tyto definice:

.grid
{
    
border-collapse: collapse;      border: solid 1px #808080; } .grid th {
    
background-color: #990000;      color: White;      font-weight: bold; } .grid th, .grid td {
    
padding: 3px 10px; } .grid .r0 {
    
background-color: #f6f0ca; } .grid .r1 {
    
background-color: white; }

Celé tabulce jsme tedy přidali šedý rámeček, buňkám záhlaví jsme nastavili tmavě červené pozadí, všem buňkám tabulky (záhlaví i ostatním) jsme nastavili padding, tedy vnitřní okraj 3 pixely ve vertikálním a 10 pixelů v horizontálním směru, aby se text nemačkal a byl "v prostoru". Dále jsme vytvořili dvě třídy pro řádky se světle žlutým a bílým pozadím. Nyní tyto CSS třídy namapujeme na náš GridView. A uděláme to přímo v tématu, takže se změn projeví ve všech GridView v naší aplikaci. Otevřete tedy soubor SkinFile.skin a přidejte dovnitř tento záznam:

<asp:GridView runat="server" GridLines="None" CssClass="grid">
    <RowStyle CssClass="r0" />
    <AlternatingRowStyle CssClass="r1" />
</asp:GridView>

Výsledná tabulka bude vypadat takto:

Nastylovaná tabulka

To by bylo pro tento díl vše. V příštím díle aplikaci zkusíme dokončit, uděláme přehled výpůjček a zkusíme stihnout i stránku pro zadávání a úpravu. Nebude to moc složité, protože vlastně skoro všechno umíme. Jakékoliv dotazy a náměty pište do diskuse.

Jinak ještě oznamuji, že jakmile dokončíme aplikaci Výpůjčky, tak tento seriál o ASP.NET ukončím. V současné době totiž mám rozepsanou knihu o ASP.NET, která bude obsahovat kompletní a ucelený kurz pro všechny, kteří se chtějí ASP.NET naučit a myslí to s ním vážně. Kniha bude obsahovat jednak stručný úvod do HTML, CSS, databází a programování v jazycích VB.NET a C#, protože málokdo má potřebné zkušenosti a znalosti na to, aby s ASP.NET mohl opravdu začít a rozumně v něm něco vyvíjet. Nutit čtenáře, aby si kromě mé knihy kupovali ještě dalších 5 kniho o těchto technologiích, jejichž znalost ASP.NET vyžaduje, není podle mě vhodné. Budete mít tedy k dispozici vše v jedné velké publikaci, pokud to už umíte, můžete to samozřejmě přeskočit a vrhnout se rovnou na ASP.NET. Další podrobnosti uvádět nebudu, protože je nevím, jakmile bude kniha hotová, určitě se to na tomto webu dozvíte.

Update: S knihou to vypadá bledě, nemám na to čas, za posledního půl roku se toho na chystané knize moc nezměnilo. Snad za dalšího půl roku …

Vzhledem k tomu, že mě desítky čtenářů žádají o zveřejnění aktuálního stavu aplikace Výpůjčky, dávám ho tedy ke stažení (i když nerad, nenutí vás to potom pozorně číst a rozumět tomu, co děláte; půlka lidí si to prostě jenom stáhne a má radost, že umí programovat v ASP.NET). Pokud v článku najdete nějakou chybu, zkuste mi, prosím, napsat e-mail, je možné, že jsem nějakou část kódu jednoduše zapomněl uvést nebo je uvedena jinde, než je potřeba.

Na další díl seriálu si budete muset trochu počkat, opravdu jsem na tom s časem dost mizerně.

(čtěte pozorně readme.txt!)

 

hodnocení článku

4 bodů / 4 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Mohlo by vás také zajímat

Genericita, rozhraní a dědičnost

Jazyk C# je multiparadigmatický, což v praxi znamená, že v něm můžeme dělat hodně věcí. Jak ale do sebe jednotlivá paradigma zapadají? Co se hezky doplňuje a co není vzájemně kompatibilní? V tomto článku chci popsat, jak se chová IEquatable vzhledem k dědičnosti typu T.

Hledáme .NET vývojáře (Praha, Brno, Frýdek-Místek)

Visual Studio 2017 je venku!

 

 

Nový příspěvek

 

dalsie diely

Dobry den,

kde najdem dalsie diely serialu? Posledna cast je 4. ktoru tu vidim.

dakujem

Peter

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Enable a Visible

Dobry den,

nechal som sa inspirovat tymto clankom kde pouzivate Enable a Checked cez binding:Enabled='<%#Eval("UserName").ToString() != "admin" %>'

Checked='<%#Roles.IsUserInRole(Eval("UserName").ToString(), "admin") %>'

neviem preco ale mne to nejde...

Doplnil som do gridu aj stlpce s pomocnym vyhodnotenim kedy je bude vysledok TRUE a kedy FALSE

a napriek vysledku kdy som admin ci user ostava checkbox enabled a pri link button prvku visible.

Uz som skontroloval cely kod 3x ci som sa nepomylil ale nie.

vsetko ostatne mi ide...

Dakujem.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Otázka zní jestli se role opravdu jmenuje admin a né třeba Admin, Administrator a podobně ;)

Na to bych se nejprve zaměřil.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback