ASP.NET – Diskusní forum (5)

V předposledním dílu našeho seriálu o diskusním fóru si povíme o tom, jak funguje vykreslování komponent na ASP.NET stránce, ukážeme si jak se dá obsah stránky generovat dynamicky a podrobněji si vysvětlíme mechanismus celé aplikace.

Struktura stránky

Jednu .aspx stránku si můžete představit jako strom různých komponent. Některé z nich sami vytvářejí HTML kód pro stránku, další slouží pouze jako kontainery pro jiné komponenty. Ku příkladu – komponenta Label je reprezentována pomocí tagů <span> - sama vytváří HTML kód. Naproti tomu komponenta Datagrid jenom využívá již existujících komponent a pouze je podle šablony seřazuje do tabulky.

Každá komponenta pro web dědí ze základní třídy System.Web.UI.Control. Ta obsahuje mimo jiné i vlastnosti Parent a Controls.

Parent je další objekt typu System.Web.UI.Control a určuje nadřazenou komponentu.

Controls je kolekce objektů typu System.Web.UI.Control a obsahuje všechny dceřiné komponenty.

Ukažme si to celé na příkladu. Vytvořil jsem stránku, která obsahuje komponentu DataGrid:

Klepněte pro větší obrázek

Objektová struktura části takovéto stránky vypadá takto:

Klepněte pro větší obrázek

Tato část stromu pochopitelně zobrazuje jenom velice malou část celé struktury stránky – sledoval jsem pouze cestu k prvnímu políčku druhého řádku, který obsahuje text “Bett, Vercelloti,Lovergen”. Každá z komponent uvedených ve stromu má několik dalších komponent, které nebyli vykresleny. Podívejme se nyní konkrétněji na vztah dvou objektů mezi sebou, ku příkladu HTMLForm a DataGrid.

Klepněte pro větší obrázek

Tento UML diagram ukazuje, že HTMLForm může vlastnit několik DataGridů ve vlastnosti Controls. Naproti tomu každý z DataGridů bude mít ve vlastnosti Parent referenci na nadřazený HTMLForm. Jak je na obrázku naznačeno, platí toto obecně pro všechny objekty, které dědí ze třídy System.Web.UI.Control.

Při vykreslování HTML je výsledná stránka v podstatě složena z HTML jednotlivých komponent. HTML kód je sestaven po sloučení všech dílčích HTML výstupů v pořadí, v jakém jsou přítomny ve stromu.

Měníme obsah stránky dynamicky

Samotná aspx stránka dědí ze třídy System.Web.UI.Page. Tato třída je také potomkem třídy System.Web.UI.Control, takže pro ni stejná pravidla jako pro jiné komponenty. Kolekci obsažených komponent můžeme měnit v průběhu programu. Uveďme si jednoduchý příklad. Do ovladače události Page_Load přidáme následující kód:

      private void Page_Load(object sender, System.EventArgs e)
      {
        Label napis = new Label();
        napis.Text = "Hello";
        FindControl(“Form1”).Controls.Add(napis);
      }

Výsledek bude vypadat:

Klepněte pro větší obrázek

Jak jsme tedy dostali tento nápis do stránky ? Vytvořil jsem novou instanci třídy Label, a následně ji přidal do formuláře. Příkaz FindControl našel na stránce objekt formuláře reprezentující tagy <form></form> a vložil do kolekce Controls tohoto objektu nově vzniklou komponentu Label. Vkládání do kolekce se provádí příkazem Add.

Proč jsem musel vyhledat objekt formuláře pomocí metody FindControl ? Pokud bychom napsali pouze Controls.Add(napis) – vložil by se nápis nakonec celé stránky – což by znamenalo až za konec </html> tagu.

Další příklad ukazuje, jak vytvoříme dynamicky více komponent:

      private void Page_Load(object sender, System.EventArgs e)
      {
        for(int c=1;c<10;c++)
        {
            Label napis = new Label();
            napis.Text = "Nápis č."+c+"<br>";
            FindControl("Form1").Controls.Add(napis);
        }
      }

Klepněte pro větší obrázek

To by byl teoretický úvod do problematiky objektového modelu stránky v ASP.NET. Podívejme se nyní jak je podle tohoto modelu vytvořena komponenta pro diskusní forum.

Diskusní Forum jako strom komponent

Třída ForumControl představující komponentu forum obsahuje ve své kolekci Controls pro každý příspěvek na kořenové úrovni objekt ForumControlLinkedItem. Tato kolekce je vytvářena při načítání stránky v metodě CreateControlsHierarchy():

protected void CreateControlHierarchy() // ForumControl
{
this.Controls.Add(new LiteralControl("<table width=\"100%\" cellpadding=0 cellspacing=0>"));

...

foreach(ForumMessage m in _forum.Messages)
{
ForumControlLinkedItem i = new ForumControlLinkedItem(m);

...

this.Controls.Add(new LiteralControl("</td><td width=\"100%\">"));
this.Controls.Add(i);
this.Controls.Add(new LiteralControl("</td></tr>"));
}

this.Controls.Add(new LiteralControl("</table>"));

...
}

Můžeme vidět, že se jedná v podstatě akorát o trošičku komplikovanější příklad generování komponent dynamicky. Na začátku funkce je vložena tabulka, která ohraničí celou komponentu. Po ní pak následuje smyčka foreach která prohledává všechny příspěvky z datového zdroje. Pro každý příspěvek vytvoří novou instanci třídy ForumControlLinkedItem, předá ji jako parametr příspěvek, kterou má tato komponenta zobrazit a přidá jej do vlastní kolekce Controls. Všimněte si, že funkce se už dále nestará, jak je jedna konkrétní zpráva (příspěvek) vykreslena – to už je v kompetenci třídy ForumControlLinkedItem.

Ta představuje jeden konkrétní příspěvek a obsahuje také metodu CreateChildControls():

protected override void CreateChildControls() // ForumControlLinkedItem.CreateChildControls
{
this.Controls.Clear();
...
base.CreateChildControls();
...

if(this.Expanded) foreach(ForumMessage m in this.Message.Replies)
{
ForumControlLinkedItem i = new ForumControlLinkedItem(m);
...
this.Controls.Add(i);
}
...

}

Můžete si všimnou, že základní myšlenka je opět velice jednoduchá. Nejdříve vytvoří sama sebe (vykreslí údaje daného příspěvku pomocí metody rodičovské třídy base.CreateChildControls() – k této metodě se dostaneme za chvíli.), následně pak opakuje rekurzivně vytváření dalších komponent typu ForumControlLinkedItem, které zobrazí odpovědi na aktuální příspěvek.

Výsledný strom objektů (komponent) pak přesně kopíruje strom příspěvků v daném fóru. Ku příkladu:

Klepněte pro větší obrázek

Objektový UML model celého systému pak vypadá následovně:

Klepněte pro větší obrázek

Proč máme dvě třídy pro reprezentaci samotných příspěvků ? ForumControlItem reprezentuje základní funkčnost samotného jednoho příspěvku – vykresluje jenom daný příspěvek bez závislostí na dalších příspěvcích (reakcí). Metoda CreateChildControls() tohoto objektu, kterou jsme volali pomocí base.CreateChildControls(), vykreslí všechny potřebné políčka pro zobrazení příspěvku:

protected override void CreateChildControls() // ForumControlItem.CreateChildControls
        {
this.Controls.Add(new LiteralControl("<table cellSpacing=\"0\" cellpadding=\"0\" width=\"100%\">"));
this.Controls.Add(new LiteralControl("<tr><td class=\"frametop\">"));

if(Mode==ControlMode.View)
{

Label title = new Label();
title.Text="<a href=\"mailto:"+this.Message.AuthorEmail+"\">"+this.Message.Author+"</a>    >    "+this.Message.Subject+"  >  "+this.Message.PostDate.ToShortDateString()+" "+this.Message.PostDate.ToShortTimeString();

title.Font.Bold=true;
this.Controls.Add(title);
this.Controls.Add(new LiteralControl(" "));
             

if(_replyAllowed)
{
ReplyButton.Text = "Reply";
this.Controls.Add(ReplyButton);
this.Controls.Add(new LiteralControl(" "));
}

if(_editAllowed)
{
EditButton.Text = "Edit";
this.Controls.Add(EditButton);
this.Controls.Add(new LiteralControl(" "));
}

if(_deleteAllowed)
{
DeleteButton.Text = "Delete";
this.Controls.Add(DeleteButton);
this.Controls.Add(new LiteralControl(" "));
}
this.Controls.Add(new LiteralControl("</td></tr>"));
           
if(this.Expanded)
{
this.Controls.Add(new LiteralControl("<tr><td class=\"frame\">"));
   
Label text = new Label();
text.Text=this.Message.Body;
this.Controls.Add(text);
}

else  // Edit mode
{
NameBox.Text=_message.Author;
              NameBox.Width=150;
              this.Controls.Add(new LiteralControl("  Name:"));

              this.Controls.Add(NameBox);
              EmailBox.Text=_message.AuthorEmail;
              EmailBox.Width=150;
              this.Controls.Add(new LiteralControl("  Email:"));
              this.Controls.Add(EmailBox);

              DateLabel.Text= _message.PostDate.ToShortDateString()+" "+_message.PostDate.ToShortTimeString();
              this.Controls.Add(DateLabel);

              SubjectBox.Text=_message.Subject;
              SubjectBox.Width=300;
              this.Controls.Add(new LiteralControl("  Subject:"));
              this.Controls.Add(SubjectBox);

              this.Controls.Add(new LiteralControl("</td></tr>"));
              this.Controls.Add(new LiteralControl("<tr><td class=\"frame\">"));

              BodyBox.Text=_message.Body;
              BodyBox.Width=Unit.Percentage(100);
              BodyBox.TextMode=TextBoxMode.MultiLine;
              BodyBox.Height=300;
              this.Controls.Add(BodyBox);

              this.Controls.Add(new LiteralControl("</td></tr>"));
              this.Controls.Add(new LiteralControl("<tr><td class=\"frame\">"));

              SaveButton.Text="Save";
              this.Controls.Add(SaveButton);

              CancelButton.Text="Cancel";
              this.Controls.Add(CancelButton);
            }
            this.Controls.Add(new LiteralControl("</td></tr>"));
            this.Controls.Add(new LiteralControl("</table>"));
        }

Tato na první pohled složitá metoda má dvě základní části: Rozlišuje v jakém módu se aktuální příspěvek právě nachází. Pokud je mód prohlížecí (Mode=View), vykreslí příspěvek pomocí Labelů. Pokud je mód editovací (Mode=Edit), vykreslí jej pomocí TextBoxů, aby umožnil uživateli měnit údaje příspěvku.

Dále také rozlišuje, zda je povoleno přidávat nové příspěvky, měnit obsah stávajících, nebo mazat příspěvky. Pokud je daná vlastnost (ReplyAllowed, EditAllowed, DeleteAllowed) nastavena na true, přidá také odpovídající tlačítko umožňující danou akci.

Tímto přístupem jsme zabili dvě mouchy jednou ranou – pokud totiž nastavíme:

ReplyAllowed = true;
DeleteAllowed = false;
EditAllowed = false;

máme připravené forum pro normální uživatele systému. Můžou pouze přidávat nové příspěvky a reakce. Pokud se ale do systému přihlásí administrátor, nastavení

ReplyAllowed = true;
DeleteAllowed = true;
EditAllowed = true;

mu umožní moderovat danou diskuzi – může měnit, mazat nebo přidávat příspěvky.

Tímto bychom měli zvládnutou strukturu celé komponenty. Jak ale probíhá interakce s uživatelem ? Co se vlastně děje po zmáčknutí tlačítka Save, nebo Edit ? Na to se podíváme v následující kapitole.

Předávání událostí

Celé forum funguje na základě předávání událostí mezi jednotlivými objekty. ForumControlLinkedItem vystavuje následující události, které může kdokoliv použít pro reagování na kroky uživatele:

  • Reply – je vyvolána, pokud uživatel zmáčknul tlačítko pro odpověď na daný příspěvek.
  • Save – poté, co uživatel ukončil zadávání (nebo změnu) dat a zmáčkl tlačítko Save.
  • Cancel – vyvolána při zrušení zadávaných dat.
  • ExpandedChanged – vyvolá se pokud uživatel rozbalil, nebo zabalil daný příspěvek.
  • Edit – při přepnutí do editovacího módu (Mode=Edit)
  • Delete – při zmáčknutí tlačítka pro smazání příspěvku.

Při vytváření stromu objektů v metodách CreateChildControls registruje rodičovský příspěvek pro všechny události každé z reakcí svůj ovladač pro danou událost. To má za následek probublávání události ve stromu příspěvků až na nejvyšší vrstvu, kde ji odchytí uživatel. Přehledně je to znázorněno na ukázkovém diagramu:

Klepněte pro větší obrázek

Tento obrázek ukazuje situaci, kdy uživatel zmáčknul tlačítko Save. V příslušném objektu ForumControlLinkedItem byla vyvolána událost Save, která upozorní rodiče, že je potřeba danou zprávu uložit. Rodičovský objekt toto volání přepošle dál svému rodiči, až zpráva dorazí do kořenového příspěvku, který už žádného rodiče nemá. Tam ji odchytí samotný ForumControl:

Klepněte pro větší obrázek

Poté, co zprávu obdrží ForumControl, uloží data do databáze a sám vyvolá svoji událost Save. Tu pak může zpracovat uživatel bez toho, aby znal celý mechanismus zpráv uvnitř komponenty. Takže ku příkladu můžeme napsat:

OnForumSave(object sender, EventArgs e)
{
ForumMessage zprava = ((ForumControlLinkedItem)sender).Message;
// udelej neco se správou, např. vypiš, ze byla uložena
}

Tato výsledná architektura je velice flexibilní, přehledná a umožňuje jednoduché rozšíření o další funkce. Mechanismus zpráv je také velice rychlý, jedno předání zprávy je vlastně zavolání jedné funkce, přičemž hloubka stromu se většinou pohybuje v jednotkách příspěvků.

Komponenty jsou také velice soběstačné, ForumControlLinkedItem může existovat bez jakýchkoliv návazností na ForumControl, který tvoří jenom jakousi obálku pro jednodušší přístup ze strany uživatele.

Závěr

Na závěr bych chtěl dodat, že vytvářet takovýto pokročilý systém v neobjektovém skriptovacím jazyku by bylo téměř nemožné, nemluvě o výsledném kódu, který by byl velice těžce udržitelný.

Co bude příště ? Dnes jsem se opět ještě nedostal k označování přečtených/nepřečtených zpráv. Za to se čtenářům omlouvám, ale cítil jsem potřebu důkladněji vysvětlit, jak ASP.NET pracuje s komponentami při zobrazování stránky a také ozřejmit celkovou architekturu celé komponenty.

Váš názor Další článek: Grafická karta Matrox Parhelia

Témata článku: Software, Programování, Tlačítko umožňující, Save, Strom, Příspěvek, Code, Cancel, Komponenta, Label, LTO, ASP, Základní struktura


Určitě si přečtěte

Japonská MANA může být 80× výkonnější než sebelepší tranzistorový procesor

Japonská MANA může být 80× výkonnější než sebelepší tranzistorový procesor

** Tranzistory současných počítačů vyzařují při přepínání teplo ** Na Tokijské univerzitě proto vyvíjejí adiabatické procesory ** Využívají supravodivost a jsou 80× úspornější

Jakub Čížek | 48

Jakub Čížek
TranzistoryProcesoryTechnologie
Microsoft pořád myslí i na odpůrce předplatného. Letos vydá Office 2021
Lukáš Václavík
Microsoft OfficeMicrosoft
Kudy proudí doprava? Na mapách můžete sledovat autobusy, vlaky, letadla i lodě

Kudy proudí doprava? Na mapách můžete sledovat autobusy, vlaky, letadla i lodě

Současná situace cestování zrovna dvakrát nepřeje, kvůli covidu můžeme jezdit leda tak prstem po mapě. A nebo můžeme prsty nechat volné a koukat, jak po mapě cestuje někdo jiný. Díky otevřeným datům dopravních či přepravních společností je to hračka.

Lukáš Václavík | 10

Lukáš Václavík
Doprava
26 užitečných rozšíření pro Chrome: Naučte prohlížeč nové věci

26 užitečných rozšíření pro Chrome: Naučte prohlížeč nové věci

** Prohlížeč Chrome obsahuje širokou škálu funkcí, neumí ale všechno ** Jeho schopnosti můžete rozšířit pomocí rozšíření ** Vybrali jsme pro vás zajímavé a užitečné doplňky

Karel Kilián | 47

Karel Kilián
Doplňky do prohlížečeChromeProhlížeče
Můžete mít dvakrát rychlejší VDSL? Mapa Cetinu ukazuje, kde je dostupný bonding
Lukáš Václavík
CETINPřipojení k internetu
Platby kartou se můžou rozšířit úplně všude. Jako terminál poslouží mobil
Lukáš Václavík
BankaPlacení mobilemNFC