Serverový ovládací prvek se šablonou v ASP.NET

2. prosince 2003

S ASP.NET jsou dodávány ovládací prvky, jejichž vzhled si může vývojář plně přizpůsobit pomocí šablon. Jedná se například o Repeater či DataList a jejich šablony ItemTemplate, AlternatingItemTemplate, FooterTemplate a HeaderTemplate. V dnešním článku vytvoříme serverový ovládací prvek s názvem LoginControl, který bude obsahovat jednu šablonu pro přihlášeného uživatele a jinou šablonu pro nepřihlášeného uživatele.

Proč používat šablony pro přihlášené a nepřihlášené uživatele

S připravovanou verzí ASP.NET 2.0 Whidbey bude dodáván serverový ovládací prvek, jenž umožní definovat šablony pro přihlášené a nepřihlášené uživatele, a navíc v něm bude možné vytvářet šablony založené na rolích uživatelů. V ASP.NET 1.1 ale žádný podobný ovládací prvek neexistuje, a proto většina vývojářů stránek, jejichž obsah se má lišit pro přihlášené a nepřihlášené uživatele, píše obsáhlý kód v události Page_Load, který skryje nebo zpřístupní některé elementy stránky pro přihlášené uživatele a jiné zase pro nepřihlášené uživatele. Orientace v takovém kódu je velmi nepříjemná a vy si velmi rychle uvědomíte, jak těžká a nevděčná živnost je jasnovidectví, zvláště když jste kód přebrali po kolegovi, který se důsledně řídí zásadou, že psaní komentářů do kódu je zbytečnost. Šablony uvedený problém odstraňují, protože na ASPX stránce deklarativně uvedete, jaká množina prvků je určena přihlášenému uživateli a jaká zase anonymnímu uživateli.

Vytvoření prvku LoginControl

Vytvoříme serverový ovládací prvek s názvem LoginControl, který bude mít dvě šablony. Do šablony AnonymousTemplate vývojář umístí prvky pro nepřihlášené uživatele, do šablony LoggedInTemplate prvky pro přihlášené uživatele. LoginControl má vlastní události, které zjednodušují práci s prvkem. Když se chce anonymní uživatel přihlásit, je vyvolána událost LoginRequest. Naopak, když se chce přihlášený uživatel odhlásit, je vyvolána událost LogoutRequest. V události ItemCommand jsou zpřístupněny všechny bublané události z ovládacích prvků v šablonách.

Jestliže uživatel některou šablonu neposkytne, LoginControl použije výchozí šablony (třídy DefaultLoggedInTemplate a DefaultAnonymousTemplate). Jméno přihlášeného uživatele a zprávu pro přihlášeného i nepřihlášeného uživatele nese třída LoginControlContent, která pro třídu LoginControl a její šablony figuruje jako kontejner (Container), jehož hodnoty vlastností je možné zjistit použitím ASP.NET syntaxe pro získání obsahu z datového zdroje (<%#… %>).

Pokud nebudete v článku či zdrojovém kódu některým věcem rozumět, projděte si mé předchozí články, jejichž seznam naleznete na konci dnešního článku. Kompletní zdrojový kód si můžete stáhnout.

[DefaultProperty(„AnonymousUserMessage“)] [DefaultEvent(„LoginRequest“)] [ToolboxData(„<{0}:LoginControl runat=server></{0}:LoginControl>“)] [Designer(typeof(LoginControlDesigner))] public class LoginControl : WebControl, INamingContainer
{
  public delegate void LoginItemCommandEventHandler (object sender, CommandEventArgs e);
  public const string LOGIN_BUTTON_NAME = „Login“;
  public const string LOGOUT_BUTTON_NAME = „Logout“;
  public static readonly Object LoginRequestKey = new Object();
  public static readonly Object LogoutRequestKey = new Object();
  public static readonly Object ItemCommandKey = new Object();
  public event EventHandler LoginRequest
  {
    add
    {
      Events.AddHandler(LoginRequestKey, value);
    }
    remove
    {
      Events.RemoveHandler(LoginRequestKey, value);
    }
  }
  public event EventHandler LogoutRequest
  {
    add
    {
      Events.AddHandler(LogoutRequestKey, value);
    }
    remove
    {
      Events.RemoveHandler(LogoutRequestKey, value);
    }
  }
  public event LoginItemCommandEventHandler ItemCommand
  {
    add
    {
      Events.AddHandler(ItemCommandKey, value);
    }
    remove
    {
      Events.RemoveHandler(ItemCommandKey, value);
    }
  }
  private LoginControlContent m_content;
  private ITemplate m_anonymousTemplate;
  private ITemplate m_loggedInTemplate;
  public LoginControl()
  {
  }
  public override ControlCollection Controls
  {
    get
    {
      EnsureChildControls();
      return base.Controls;
    }
  }
  [Browsable(false)]   [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]   public LoginControlContent Content
  {
    get
    {
      EnsureChildControls();
      return m_content;
    }
 &nbsp}
  [Browsable(false)]   [DefaultValue(null)]   [PersistenceMode(PersistenceMode.InnerProperty)]   [TemplateContainer(typeof(LoginControlContent))]   public ITemplate LoggedInTemplate
  {
    get
    {
      return m_loggedInTemplate;
    }
    set
    {
      m_loggedInTemplate = value;
    }
  }
  [Browsable(false)]   [DefaultValue(null)]   [PersistenceMode(PersistenceMode.InnerProperty)]   [TemplateContainer(typeof(LoginControlContent))]   public ITemplate AnonymousTemplate
  {
    get
    {
      return m_anonymousTemplate;
    }
    set
    {
    m_anonymousTemplate = value;
    }
  }
  [Category(„Behavior“)]   [Bindable(true)]   [DefaultValue(„“)]   [Description(„Informace zobrazená nepřihlášenému uživateli“)]   public string AnonymousUserMessage
  {
    get
    {
      string message = (string) ViewState[„AnonymousUserMessage“];
      return (message == null ? String.Empty : message);
    }
    set
    {
      ViewState[„AnonymousUserMessage“] = value;
    }
  }
  [Category(„Behavior“)]   [Bindable(true)]   [DefaultValue(„“)]   [Description(„Informace zobrazená přihlášenému uživateli“)]   public string LoggedInMessage
  {
    get
    {
      string message = (string) ViewState[„LoggedInMessage“];
      return (message == null ? String.Empty : message);
    }
    set
    {
      ViewState[„LoggedInMessage“] = value;
    }
  }
  protected override HtmlTextWriterTag TagKey
  {
    get
    {
      return HtmlTextWriterTag.Div;
    }
  }
  public override void DataBind()
  {
    EnsureChildControls();
    base.DataBind ();
  }
  protected override void CreateChildControls()
  {
    Controls.Clear();
    if (Page.User.Identity.IsAuthenticated)
    {
      m_content = new LoginControlContent(Page.User.Identity.Name, this.AnonymousUserMessage, this.LoggedInMessage);
      ITemplate template = null;
      if (m_loggedInTemplate != null)
        template = m_loggedInTemplate;
      else
        template = new DefaultLoggedInTemplate();
      template.InstantiateIn(m_content);
    }
    else
    {
      m_content = new LoginControlContent(String.Empty, this.AnonymousUserMessage, this.LoggedInMessage);
      ITemplate template = null;
      if (m_anonymousTemplate != null)
        template = m_anonymousTemplate;
      else
        template = new DefaultAnonymousTemplate();
      template.InstantiateIn(m_content);
    }
    this.Controls.Add(m_content);
  }
  protected override bool OnBubbleEvent(object source, EventArgs e)
  {
    CommandEventArgs ex = e as CommandEventArgs;
    if (ex != null)
    {
      if (ex.CommandName.ToLower() == LOGIN_BUTTON_NAME.ToLower())
        OnLoginRequest(new EventArgs());
      else if (ex.CommandName.ToLower() == LOGOUT_BUTTON_NAME.ToLower())
        OnLogoutRequest(new EventArgs());
      else
        OnItemCommand(ex);
      return true;
    }
    return false;
  }
  protected virtual void OnLoginRequest(EventArgs e)
  {
    EventHandler eh = (EventHandler) Events[LoginRequestKey];
    if (eh != null)
      eh(this, e);
  }
  protected virtual void OnLogoutRequest(EventArgs e)
  {
    EventHandler eh = (EventHandler) Events[LogoutRequestKey];
    if (eh != null)
      eh(this, e);
  }
  protected virtual void OnItemCommand(CommandEventArgs e)
  {
    LoginItemCommandEventHandler eh = (LoginItemCommandEventHandler) Events[ItemCommandKey];
    if (eh != null)
      eh(this, e);
  }
}

Prvek LoginControl je dekorován několika metaatributy. Metaatribut DefaultProperty říká, že výchozí vlastností je vlastnost AnonymousUserMessage. Metaatribut DefaultEvent deklaruje jako výchozí událost událost LoginRequest. Atribut ToolBoxData zajištuje, že po přetažení prvku z panelu ovládacích prvků na stránku je vytvořena značka LoginControl. Atribut Designer RAD nástroje upozorňuje, že designérem pro třídu LoginControl je třída LoginControlDesigner. Jedná se o jednoduchého designéra, který pouze vykreslí název prvku. V příštím článku si ukážeme vytvoření designéra, který poskytne vývojářům stejnou podporu v design módu jakou znají například z Repeateru.

LoginControl dědí z třídy WebControl a implementuje značkovací rozhraní INamingContainer. Rozhraní INamingContainer musí implementovat každý kompozitní ovládací prvek, aby bylo zajištěno, že všechny ovládací prvky v šablonách budou mít na stránce unikátní hierarchický identifikátor. Kdybychom rozhraní INamingContainer neimplementovali, tak v případě, že na jedné stránce budeme mít dva prvky LoginControl, jejichž šablony obsahují prvky se stejným identifikátorem, tak nebude možné poznat, na jaký prvek se právě odkazujeme.

LoginControl deklaruje delegáta LoginItemCommandEventHandler pro událost ItemCommand. Dále jsou deklarovány konstanty LOGIN_BUTTON_NAME a LOGOUT_BUTTON_NAME. Když má tlačítko v šabloně ve vlastnosti CommandName hodnotu ‘Login‘ (konstanta LOGIN_BUTTON_NAME ), je po jeho stisknutí vyvolána událost LoginRequest. Analogicky, když má tlačítko v šabloně ve vlastnosti CommandName hodnotu ‘Logout‘ (konstanta LOGOUT_BUTTON_NAME ), je po jeho stisknutí vyvolána událost LogoutRequest. Pro tlačítka s jinými hodnotami ve vlastnosti CommandName je vyvolána událost ItemCommand. Statičtí členové pouze pro čtení s názvy LoginRequestKey, LogoutRequestKey a ItemCommandKey představují klíče událostí LoginRequest, LoginRequest a ItemCommand.

Každý kompozitní ovládací prvek by měl přepsat vlastnost Controls, aby bylo zajištěno, že jsou do kolekce jako první přidány prvky vytvářené v metodě CreateChildControls. To je zvláště důležité u prvků, které ukládají hodnoty do ViewState, protože úspěšné obnovení hodnot z ViewState závisí na tom, zda prvek má vždy v kolekci Controls stejný index.

Ve vlastnosti Content je uložen kontejner, ve kterém proběhla instanciace šablony AnonymousTemplate nebo LoggedInTemplate. Vlastnost Content je instance třídy LoginControlContent. Pokud budete chtít vyhledávat prvky v šabloně, musíte použít metodu FindControl, které předáte Id hledaného prvku. Vlastnost je dekorována metaatributem Browsable s argumentem false, který zajistí, že vlastnost nebude zobrazována na stránce vlastností. Atribut DesignerSerializationVisibility s argumentem DesignerSerializationVisibility.Hidden dává na vědomí RAD serializerům, že vlastnost není možné serializovat.

Vlastnosti LoggedInTemplate a AnonymousTemplate reprezentují šablony pro přihlášeného a anonymního uživatele. Každá šablona musí být typu ITemplate, což je speciální rozhraní s jedinou metodou InstantiateIn. Více se o rozhraní ITemplate dočtete v článku o vytváření typového sloupce se šablonou pro Datagrid. Parser ASPX stránek zajišťuje přenesení obsahu šablony ze stránky do vlastností LoggedInTemplate a AnonymousTemplate. Obě vlastnosti nemají být viditelné na stránce vlastnost (metaatribut Browsable má hodnotu false), jejich výchozí hodnotou je hodnota null (metaatribut DefaultValue) a jejich definice je na ASPX stránce uložena v samostatných značkách LoggedInTemplate a AnonymousTemplate situovaných pod značkou LoginControl (metatribut PresistenceMode s argumentem PersistenceMode.InnerProperty). Důležitý je atribut TemplateContainer, který informuje běhové prostředí ASP.NET o třídě použitého kontejneru. U obou vlastností je kontejnerem třída LoginControlContent. Běhové prostředí tuto informaci používá k vyhodnocování výrazů pro získání dat z datového zdroje. Například výraz <%#Container.UserName%> v šabloně LoggedInTemplate bude vyhodnocen jako odkaz na vlastnost UserName třídy LoginControlContent. Obě vlastnosti jsou dekorovány metaatributem Category, který RAD designér používá pro zjištění kategorie, do které má vlastnost umístit na stránce vlastností. Obě vlastnosti budou umístěny do kategorie “Behavior“.

Ve vlastnosti AnonymousUserMessage je uložena zpráva, která se má zobrazovat nepřihlášenému uživateli. Ve vlastnosti LoggedInMessage je asi nepřekvapivě uložena zpráva pro přihlášeného uživatele. Obě vlastnosti jsou na stránce vlastností zobrazovány v kategorii Behavior (metaatribut Category), je vhodné je svázat s datovým zdrojem (metaatribut Bindable), ve výchozím nastavení obsahují prázdný řetězec (metaatribut DefaultValue) a mají popisek na stránce vlastností (metaatribut Description). Hodnoty vlastností jsou perzistovány ve ViewState.

Přepsání chráněné vlastnosti TagKey způsobí, že prvek LoginControl je na HTML stránku renderován ve značce DIV.

Přepíšeme veřejnou metodu DataBind, aby bylo zajištěno, že před vyhodnocením datově orientovaných výrazů jsou vytvořeny všechny vnořené ovládací prvky.

Metoda CreateChildControls vytváří v kompozitních ovládacích prvcích vnořené prvky. Tato metoda je volána vždy, když metoda EnsureChildControls zjistí, že vnořené prvky ještě nebyly vytvářeny.Metoda CreateChildControls nejdříve odstraní všechny existující prvky v kolekci Controls. Poté zjistí, zda uživatel je nebo není přihlášen.

Když je uživatel přihlášen, tak je vytvořena nová instance kontejneru LoginControlContent, které je do konstruktoru předáno jméno uživatele a také jsou jí předány zprávy pro anonymního a přihlášeného uživatele. Dále metoda zkontroluje, zda byla vývojářem poskytnuta šablona LoggedInTemplate. Když šablona neexistuje, což poznáme tak, že šablona má výchozí hodnotu null, je vytvořena nová instance standardní šablony DefaultLoggedInTemplate a její metodě InstantiateIn je předán odkaz na kontejner LoginControContent. V případě, že existuje vývojářem definovaná šablona LoggedInTemplate, je volána metoda InstantiateIn šablony LoggedInTemplate. Metoda InstatiateIn zkopíruje všechny ovládací prvky v šabloně do kolekce Controls kontejneru.

Když uživatel není přihlášen, je postup takřka identický. Je také vytvořena instance kontejneru LoginControlContent, která ale neobsahuje uživatelovo jméno, jen zprávu pro přihlášeného a anonymního uživatele. Metoda zkontroluje existenci šablony AnonymousTemplate. Pokud šablona neexistuje, tak je vytvořena instance standardní šablony DefaultAnonymousTemplate a zavolána její metoda InstantiateIn, které je předán odkaz na kontejner LoginControlContent. Když šablona AnonymousTemplate existuje, je volána její metoda InstantiateIn.

LoginControl zachycuje v chráněné metodě OnBubbleEvent bublané události a podle hodnoty vlastnosti CommandName argumentu události rozhoduje, jaká událost má být na nejvyšší úrovni vyvolána.

Chráněné metody OnLoginRequest, OnLogout, OnItemCommand jsou odpovědné za vyvolání příslušných událostí.

Jak jste si jistě všimli, prvek LoginControl neobsahuje žádnou metodu, která by renderovala html značky. Metoda RenderContents ve třídě WebControl volá metodu Render z třídy Control, jež zavolá rekurzívně metodu RenderControl všech vnořených ovládacích prvků. Žádné dodatečné renderování značek nepožadujeme, a proto nám stačí výchozí implementace, která vykreslí všechny vnořené ovládací prvky.

Součástí zdrojových kódů je jednoduchá stránka, která ukazuje použití LoginControlu.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Předchozí článek Interval.cz v zajetí čísel
Další článek J2ME pro pokročilé - XML
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *