Cuidados ao Escrever Server Controls no ASP.Net

Objetivo

O objetivo deste artigo é descrever os principais pontos que devem ser observados ao se escrever server controls. Parece muito simples, mas na prática são muitos detalhes e é muito fácil se perder neles, por causa da velha história do ciclo de vida da página, ViewState e muitos outros detalhes.

De onde herdar?

A classe “base” para um server control não-visual (não consigo pensar num desses, mas é isso mesmo) é um System.Web.UI.Control. Para componentes visuais, System.Web.UI.WebControl, ou alguém mais especializado (System.Web.UI.TextBox, System.Web.UI.Panel, etc).

Quando existem “itens” no componente, Ex. Coluna de um grid, subcontroles de um panel ou qualquer outra situação semelhante, deve-se pensar no seguinte:

  • O componente vai ter propriedades suas mantidas em ViewState? Se a resposta for afirmativa, é interessante derivar eles também de WebControl. Se ele não for derivado de WebControl e fazer suas próprias propriedades guardarem estado, será necessário escrever todo o código para que o controle filho guarde e leia o ViewState (overriding LoadViewState e SaveViewState).
  • Se o item for bobão, sem muitas propriedades e com a lógica de renderização todo no componente principal (Ex.: Form, Filtro) estes podem ser uma classe sem herança alguma. Geralmente, eu prefiro a primeira opção.

Como as tags serão escritas no aspx?

Aqui começa uma confusãozinha razoável.

Supomos o seguinte componente, um componente que seja um Panel com título. Vamos chamá-lo de MeuPanel. Como queremos que o “usuário” do nosso componente escreva as tags no aspx?

Pode ser de duas formas:

<ME:MeuPanel titulo="Título do panel">
  <ME:MeuSubComponente />
  <ME:MeuSubComponente />
  <ME:MeuSubComponente />
</ME:MeuPanel>

No exemplo acima, estamos considerando que o título do panel é uma propriedade que será escrita como “Atributo” e que os subcomponentes serão armazenados numa propriedade chamada “MeusControles”, esta será a propriedade interna default.

Para esse exemplo, a declaração da classe deve ser a seguinte:

[ParseChildren(false, "MeusControles")]
[PersistChildren(true)]
public class MeuPanel{
  //...
}

ParseChildren = false, indica que os filhos não serão tratados como propriedades, ou seja, os nós filhos do ME:MeuPanel no aspx não tentarão ser colocados dentro de uma propriedade chamada “MeuSubComponente” da classe MeuPanel.

A propriedade default “MeusControles” do atributo ParseChildren, indica que os nós filhos declarados serão mapeados para dentro de uma propriedade chamada “MeusControles” por default, ou seja, não precisa colocar o nós <MeusControles> e </MeusControles> no aspx.

O atributo PersistChildren=true indica que o que for informado como filho do nó do aspx são itens de uma collection (filhos) e não propriedades. Se ele não for informado, ele vai tentar colocar MeuSubComponente como uma propriedade de Controls, e não como um item na collection.

A declaração das propriedades na classe, deve ser a seguinte:

[Description("Título do Panel")]
[PersistenceMode(PersistenceMode.Attribute)]
public string titulo{
  ...
}

[Description("Controles filhos")]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<Control> MeusControles{
  ...
}

Uma outra forma de se informar as propriedades no aspx poderia ser:

<ME:MeuPanel titulo="Título do panel">
  <titulo>Título do Painel</titulo>
  <MeusControles>
    <ME:MeuSubComponente />
    <ME:MeuSubComponente />
    <ME:MeuSubComponente />
  </MeusControles>
</ME:MeuPanel>

Para esse exemplo, as declarações na classe devem ser as seguintes:

[ParseChildren(true)]
public class MeuPanel{
  ...
}

[Description("Título do Panel")]
[PersistenceMode(PersistenceMode.Attribute)]
public string titulo{
  ...
}

[Description("Controles filhos")]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<Control> MeusControles{
  ...
}

Composite Controls e componentes com collections de filhos (child controls)

Composite controls é a mesma coisa que “Controle composto”, ou seja, um controle que agrega outros subcomponentes. Um bom exemplo disso poderia ser um “dual list box” por exemplo. Desde que ele fosse implementado agregando dois ListBox (Ele também poderia ser implementado inteirinho do zero, baseado em WebControl, com todo o controle em javascript).

Componentes com collections seguem a mesma idéia.

A única observação principal que devemos ter sobre essas “variações” é a “árvore” de Controles dentro do aspx. Ela é importante, pq é quem determina a relação “pai/filho” dos componentes, e quem força a chamada de uma série de métodos virtuais nos componentes. Ex.: Sempre que um grid é renderizado, ele precisa em algum lugar sofrer um “DataBind”, precisa ser chamado “CreateChildControls” e uma série de coisas.

Quem controla essas chamadas é a Página. E não existe nenhuma relação entre a página e o controle se o mesmo não existir na collection de “Controls” na hora certa. A “hora certa” pra fazer isso é “OnInit”. Depois disso a página sai chamando tudo o que precisa nos componentes filhos.

Se o componente q tem child controls mantiver os mesmos numa collection que não seja a de “Controls” é necessário no OnInit jogar os mesmos na collection de controls, senão eles sofrerão os mesmos problemas de “ciclo de vida”.

No caso de “Composite Controls” a melhor hora para adicionar os filhos na collection de controls é no método virtual CreateChildControls.

Muitos problemas de componentes que perdem o estado no postback, estão relacionados à perda da relação com a página na hora certa. Devo escrever um artigo mais detalhado sobre isso em breve.

Ciclo de Vida da Página

Esse é um assunto bastante complexo. A principal questão é que os controles devem fazer o trabalho de renderização no método virtual “Render”. Parece óbvio, mas não é tão simples assim.

No momento da renderização as vezes usamos outros componentes para ajudar no processo. Ex.: Table, TableRow, TableCell e amigos. Quando criamos um componente desse e adicionamos um dos nossos dentro deles (collection Controls), para q estes por sua vez sejam renderizados, automaticamente trocamos o “Pai” do nosso subcomponente. Isso novamente vai fazer com q outros métodos do ciclo de vida da página não sejam chamados no componente.

Se em tempo de render forem criados componentes, Ex.: Table, a table também precisa ser adicionada na collection de Controls, assim qdo outro componente é adicionado na table, ele não perde a relação com a página.

O ciclo de vida em si, é um pouco mais complicado que isso, se considerarmos todos os eventos dele. O artigo a seguir é bom para entender um pouquinho sobre ele, antes de se aventurar a sair escrevendo: http://www.15seconds.com/issue/020102.htm

Tem alguns outros detalhes sobre criação “dinâmica” de componentes, que merecem um artigo à parte.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s