Um curso intensivo sobre o desenvolvimento de controles ASP.NET: criando controles ligados a dados

Por Dino Esposito - Solid Quality Learning

Novembro de 2005

Aplica-se a:

  • Microsoft ASP.NET 2.0

Resumo: Com freqüência, você precisa usar controles ASP.NET para exibir dados. Isso é feito, geralmente, usando-se controles ligados a dados. O processo para criar seus próprios controles ligados a dados é um pouco mais trabalhoso do que o exigido para criar os controles "normais", mas você terá mais facilidade de conectá-los a bancos e outras fontes de dados. (21 páginas impressas).

C#: Clique aqui para baixar o código de exemplo deste artigo.

Visual Basic: Clique aqui para baixar o código de exemplo deste artigo.

Nesta página

Introdução
Classes base para controles ligados a dados
Controle Headline ligado a dados
Executando a ligação de dados
Processamento
Controles de lista
Controle HyperLinkList
Interface IRepeatInfoUser
Conclusão

Introdução

Um controle ligado a dados é semelhante a qualquer outro controle de servidor, exceto por apresentar algumas propriedades adicionais para conectar-se a uma fonte de dados externa. Quando ligado a uma fonte de dados, a própria interface de usuário do controle é modificada e adaptada para o conteúdo da fonte. Por exemplo, um controle de lista suspensa ligada a dados acaba listando tantos itens quantos forem os registros nos resultados de uma consulta.

As propriedades específicas de todos os controles ligados a dados são DataSource e DataSourceID. Elas são propriedades mutuamente excludentes que visam ligar o controle a uma fonte de dados. Talvez sejam necessárias propriedades adicionais com base no comportamento esperado do controle. Além disso, um controle ligado a dados precisa de um mecanismo que retire dados da fonte de dados e os utilize para criar a interface do usuário.

No ASP.NET 2.0, a maior parte do código necessário foi integrada a duas classes base: BaseDataBoundControl e DataBoundControl. A primeira etapa para os desenvolvedores que desejam criar controles ligados a dados personalizados deriva de uma dessas classes.

Classes base para controles ligados a dados

A classe BaseDataBoundControl é derivada da classe WebControl e a estende com as duas propriedades mencionadas anteriormente, DataSource e DataSourceID. Além disso, a classe substitui o método DataBind. O método DataBind é comum a todos os controles e representa o ponto de entrada no pipeline de ligação de dados em qualquer controle.

public override void DataBind()
{
   this.PerformSelect();
}

PerformSelect é um dos dois métodos abstratos na classe BaseDataBoundControl. O outro método é ValidateDataSource.

protected abstract void PerformSelect();
protected abstract void ValidateDataSource(object dataSource);

O primeiro método busca os dados na fonte de dados especificada; o segundo valida o objeto de fonte de dados ligada para garantir que ele tenha um dos formatos com suporte. Veja uma implementação típica de ValidateDataSource:

protected override void ValidateDataSource(object dataSource)
{
   if (((dataSource != null) && !(dataSource is IListSource)) && 
      (!(dataSource is IEnumerable) && !(dataSource is IDataSource)))
   {
        throw new InvalidOperationException();
   }
}

Como você pode perceber, o método verifica se a fonte de dados não é nula e implementa uma destas interfaces: IListSource, IEnumerable, IDataSource.

A classe DataBoundControl é mais rica em código e funcionalidade: uma filha direta da classe BaseDataBoundControl. A classe adiciona uma ou mais propriedades públicas DataMember e substitui os dois métodos abstratos da classe pai. E o mais importante, a classe adiciona mais detalhes ao código de BaseDataBoundControl, tornando o mecanismo de ligação de dados totalmente funcional.

Se você desejar criar um controle ligado a dados personalizado, a classe DataBoundControl é um bom ponto de partida. Como um primeiro exemplo, vamos considerar o controle Headline criado no artigo anterior. Para resumir, o controle Headline é uma tabela de duas linhas que exibe o título e o texto de uma manchete de notícia com um ícone opcional e um botão para ativar e desativar o texto. A Figura 1 oferece uma visualização do controle.

Aa479308.ccc3_fig01(pt-br,MSDN.10).gif
Figura 1. Controle Headline em ação

Não seria ótimo se você pudesse ligar o título e o texto aos campos na sua fonte de dados? Vamos ajustar o controle para fazer exatamente isso.

Controle Headline ligado a dados

O controle é herdeiro da classe DataBoundControl e implementa a interface de marcador INamingContainer. Consulte o artigo anterior para obter uma explicação detalhada da razão porque é necessário ter INamingContainer.

public class Headline : DataBoundControl, INamingContainer
{
   :
}

No ASP.NET 2.0, cada controle ligado a dados tem dois tipos de fonte de dados possíveis: coleções enumeráveis e controles da fonte de dados. As coleções enumeráveis podem ser qualquer classe que implemente IListSource ou IEnumerable. Os controles da fonte de dados são componentes novos no ASP.NET 2.0 como SqlDataSource ou ObjectDataSource que implementam a interface IDataSource. Você usa DataSource para ligar o controle a uma coleção enumerável e recorre a DataSourceID para associá-lo a um controle da fonte de dados na mesma página.

public object DataSource { ... }
public string DataSourceID { ... }

Um controle ligado a dados deve dar suporte aos dois cenários. A infra-estrutura fornecida por DataBoundControl torna obscura qualquer distinção entre coleções enumeráveis e controles da fonte de dados. Tudo o que você precisa fazer é substituir o método PerformDataBinding.

protected override void PerformDataBinding(IEnumerable data)

O parâmetro transmitido para esse método é a lista de dados a ser ligada ao controle: não importa de onde vêm os dados. PerformDataBinding é chamado da implementação de PerformSelect de DataBoundControl. É esse código interno que executará diferentes ações se DataSource ou DataSourceID for definido. Do ponto de vista do controle ligado a dados, não existe diferença e tudo que é recebido é considerado uma coleção de itens de dados a partir dos quais a interface do usuário deverá ser criada.

Na primeira vez que o controle exibe seu resultado, ele passa pelo mecanismo de ligação. Então, os dados são lidos na fonte, a árvore de controle é criada e a marcação é gerada. O que acontece, contudo, quando a página com o controle é postada? Um controle bem projetado tem a capacidade de recuperar seus dados de exibição no viewstate, contanto que o viewstate esteja habilitado para a página e o controle.

Examine novamente a Figura 1. Na primeira vez que o controle exibe o título e o texto, eles lidos da fonte de dados. Quando o usuário clica para expandir ou recolher a exibição, os dados necessários não precisam ser lidos novamente na fonte de dados. Você pode colocar o valor no cache ou, como fazem os controles incorporados, colocá-lo no viewstate. Quais valores exatamente? Todos os valores ligados a dados necessários para que a interface de usuário do controle seja recriada.

A abordagem típica usada para gerenciar com facilidade os dados ligados a postagens de página consiste em criar uma classe que represente o item de dados. Neste caso, você pode criar uma classe HeadlineItem com tantas propriedades quantos forem os valores ligados. HeadlineItem terá somente duas propriedades: Title e Text:

public class HeadlineItem 
{
   private string _text;
   private string _title;
   public HeadlineItem()
   {
   }
   public HeadlineItem(string title, string text)
   {
      _text = text;
      _title = title;
   }
   public string Text {
      get {return _text;}
      set {_text = value;}
   }
   public string Title {
      get { return _title; }
      set { _title = value; }
   }
}

O conceito é claro e simples. Você cria uma instância dessa classe para cada solicitação. A classe será preenchida pela primeira vez com os dados lidos na fonte de dados. Ao final da solicitação, o estado do objeto é mantido no viewstate. Quando a página for postada, uma nova instância da classe será criada e restaurada com os valores contidos no viewstate. Caso o evento de postagem contenha código que exija uma nova ligação a um outro registro, os novos valores substituirão os valores antigos. Esse padrão é essencial para os controles ligados a dados, no ASP.NET 2.0, bem como no ASP.NET 1.x. Veja como implementá-lo.

O controle Headline tem uma nova propriedade DataItem definida da seguinte forma:

private HeadlineItem _dataItem;
private HeadlineItem DataItem
{
   get
   {
      if (_dataItem == null)
      {
         _dataItem = new HeadlineItem();
         if (base.IsTrackingViewState)
            _dataItem.TrackViewState();
      }
      return _dataItem;
   }
}

A propriedade é inicializada por solicitação e é imediatamente acompanhada para alterações de viewstate. O acompanhamento do viewstate significa que o controle registra as alterações nas propriedades do viewstate para que todas elas sejam salvas corretamente quando o viewstate do controle for serializado ao final da solicitação. Essa é uma medida de otimização que visa minimizar a quantidade de dados transmitida para o viewstate.

As propriedades de controle típicas que utilizam o viewstate como meio de armazenamento têm uma implementação diferente. Veja um exemplo:

public virtual string DataTitleField
{
   get
   {
      object o = ViewState["DataTitleField"];
      if (o == null)
         return String.Empty;
      return (string) o;
   }
   set { ViewState["DataTitleField"] = value; }
}

Por que DataItem é diferente? E mais importante, onde está o código que mantém DataItem para o viewstate?

As propriedades podem implementar o gerenciamento de viewstate de duas maneiras. A abordagem mais comum é a mostrada para a propriedade DataTitleField. O valor da propriedade é lido explicitamente no ViewState, e salvo no mesmo, por meio da coleção ViewState do controle. Essa abordagem funciona muito bem para tipos primitivos como seqüências de caracteres, números, booleanos e datas.

As classes personalizadas como HeadlineItem são superiores para implementar elas mesmas persistência de viewstate. Funcionalmente falando, o código abaixo também deve funcionar:

public virtual HeadlineItem DataItem
{
   get
   {
      object o = ViewState["DataItem "];
      if (o == null)
         return new HeadlineItem();
      return (HeadlineItem) o;
   }
   set { ViewState["DataItem"] = value; }
}

É essencial que a classe HeadlineItem seja marcada como serializável. Na verdade, ela será serializada através do formatador binário e os bytes resultantes serão armazenados no viewstate. Isso normalmente é menos eficiente, de uma perspectiva de espaço e tempo, do que implementar uma camada de gerenciamento de estado personalizada dentro da classe. Para responder pelo gerenciamento do viewstate, a implementação de HeadlineItem deve ser estendida para implementar uma interface adicional: IStateManager.

public class HeadlineItem : IStateManager
{
    private bool _marked;
    :
} 

Uma possível implementação de IStateManager é mostrada abaixo:

public bool IsTrackingViewState
{
   get { return _marked; }
}
public void LoadViewState(object state)
{
   if (state != null)
   {
      Pair p = (Pair) state;
      _title = (string) p.First;
      _text = (string) p.Second;
   }
}
public object SaveViewState()
{
   return new Pair(_title, _text);
}
public void TrackViewState()
{
   _marked = true;
}

Ela consiste em três métodos: SaveViewState, LoadViewState, TrackViewState, e uma propriedade booleana. Os dois primeiros salvam e restauram o conteúdo do viewstate. O último método ativa o controle do viewstate. Vamos nos concentrar em SaveViewState.

Nesse método, você decide como manter as informações importantes contidas na classe. Espera-se que o método retorne um objeto que será salvo na coleção ViewState. A criação e o retorno desse objeto é normalmente mais rápido do que usar um formatador binário. O que você faz no corpo de SaveViewState é, até certo ponto, arbitrário; o importante é que você retorne um objeto contendo o estado do objeto. Neste caso, existem apenas duas propriedades a serem mantidas: Title e Text. Você geralmente usa matrizes ou talvez coleções. Neste caso, eu uso um objeto Pair, que é uma matriz especial superotimizada de dois elementos Object.

O objeto retornado por SaveViewState se torna o argumento de entrada de LoadViewState. Nesse método, você extrai informações do objeto de estado e restaura os valores.

Nesse momento, você tem uma classe HeadlineItem serializável para viewstate que representa totalmente quaisquer dados ligados ao controle Headline. Observe, contudo, que a classe HeadlineItem não é o objeto da fonte de dados. Em vez disso, é um buffer intermediário criado para conter as informações provenientes da fonte de dados. A classe ajuda a criar a saída do controle armazenando as informações significativas da fonte de dados em um formato pronto para utilização. Por exemplo, geralmente você liga um registro de tabela ao controle. De todas as colunas possivelmente disponíveis nos registros, apenas duas são significativas para o controle Headline: uma para o título e outra para o texto. A classe auxiliar HeadlineItem armazena exatamente essas informações de um modo persistente para o viewstate.

Como você selecionaria os campos da fonte de dados a fim de criar a interface de usuário do controle? Neste caso, você precisa de um título e o texto da manchete. DataTitleField e DataTextField são propriedades de seqüência de caracteres que indicam os nomes das colunas da fonte de dados nas quais o título e o texto para o controle Headline devem ser lidos.

Executando a ligação de dados

Onde você preenche a propriedade DataItem do controle Headline? Em outras palavras, de onde você copia as informações na fonte de dados ligada para uma instância da classe auxiliar HeadlineItem? Você faz isso na substituição do método PerformDataBinding:

protected override void PerformDataBinding(IEnumerable data)
{
   IEnumerator e = data.GetEnumerator();
   e.MoveNext();

   string displayTitle = Title;
   string displayText = Text;
   if (!String.IsNullOrEmpty(DataTitleField))
    displayTitle = (string) DataBinder.GetPropertyValue(
            e.Current, DataTitleField);
   if (!String.IsNullOrEmpty(DataTextField))
   displayText = (string) DataBinder.GetPropertyValue(
            e.Current, DataTextField);
   
   DataItem.Title = displayTitle;
   DataItem.Text = displayText;
}

O controle Headline não é um controle de lista, pois só exibe um único registro de cada vez. O argumento de dados transmitido para PerformDataBinding é uma coleção e contém tantos itens quantos forem os registros na fonte de dados ligada. Se houver mais de um registro ligado, somente o primeiro será processado. Para um controle de lista, você configura um loop for-each e preenche uma coleção de objetos HeadlineItem (conforme veremos no próximo exemplo). Para um controle de registro único, você obtém um enumerador e vai para o primeiro registro.

IEnumerator e = data.GetEnumerator();
e.MoveNext();

O item da fonte de dados ligada é e.Current. O tipo desse objeto depende da coleção de dados ligada. Se você ligar o controle a uma DataTable ou DataView, e.Current será um objeto DataRowView. Se ligar o controle a, digamos, uma coleção de seqüências de caracteres, ele será uma seqüência de caracteres. Se tiver usado uma coleção personalizada, e.Current será uma instância do elemento da coleção personalizada. Cada tipo pode exigir uma abordagem diferente para ler os dados.

A classe auxiliar DataBinder oferece alguns métodos para ler informações de propriedade de praticamente qualquer objeto de dados. Ela oferece uma API comum que funciona com quaisquer dados ligados. Como isso é possível? Os métodos DataBinder fazem uso extenso de .NET Reflection e consultam o tipo com relação a uma propriedade com o nome especificado. O método GetPropertyValue usa o objeto e.Current e o nome da propriedade (DataTitleField e DataTextField), e retorna o valor correspondente no registro da fonte de dados. Os valores da leitura são finalmente armazenados na propriedade DataItem para uso posterior. Se a DataItem for nula, uma nova instância será criada durante o processamento, conforme o acessador get da propriedade mostrada anteriormente demonstrou.

Processamento

No mecanismo de processamento do controle, você usa simplesmente as propriedades DataItem sempre que precisa exibir o título e o texto do controle. É importante observar que no tempo de processamento a DataItem é, com certeza, não-nula e corretamente preenchida com dados. Se a lógica da página host ligar o controle aos dados, o método PerformDataBinding será chamado e a DataItem será inicializada e preenchida conforme esperado. Se houver uma postagem da página, não ocorrerá nenhuma operação explícita de ligação de dados. Entretanto, um evento de postagem implica em um viewstate não-nulo. Isso significa que o conteúdo existente na DataItem na última vez que a página foi apresentada estará armazenado no viewstate. Graças aos métodos IStateManager implementados na DataItem, o conteúdo do viewstate é restaurado e DataItem estará pronta no tempo de processamento. O importante é que você não precisa distinguir entre ligação de dados explícita e restauração de viewstate. Um guia passo a passo pode ser descrito da seguinte forma:

  1. Escreva um auxiliar, a classe dos itens de dados que representa como o mecanismo de processamento do controle deve ligar os dados.

  2. Torne essa classe auto-serializável no viewstate através dos métodos da interface IStateManager.

  3. Defina uma propriedade no controle que faça referência à classe dos itens de dados.

  4. Implemente o método PerformDataBinding, acesse os dados ligados e armazene os valores na propriedade que faz referência à classe dos itens de dados.

  5. No tempo de processamento, em Render ou em CreateChildControls, acesse a classe dos itens de dados e crie a marcação. As etapas 2 e 4 garantem intrinsecamente que o item de dados será funcional e corretamente inicializado neste momento, independentemente de quais eventos de postagens possam ocorrer.

Para usar a versão de ligação de dados do controle Headline em uma página de exemplo, você pode implementar algo semelhante a este código de exemplo:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ConnectionString='<%$ConnectionStrings:LocalNWind %>'
        SelectCommand="SELECT lastname, notes FROM employees" />
<cc1:Headline ID="Headline1" runat="server" DataSourceID="SqlDataSource1" 
        DataTextField="Notes" DataTitleField="Lastname" />

Os resultados da consulta na tabela Employees são ligados ao controle. O campo Lastname é usado para ligar o título do controle Headline. O campo Notes preenche o corpo, conforme mostrado na Figura 2.

Aa479308.ccc3_fig02(pt-br,MSDN.10).gif
Figura 2. Controle Headline ligado a dados

Controles de lista

Na estrutura do ASP.NET, existem diversos controles ligados a dados para exibir uma lista de dados: DropDownList, ListBox, CheckBoxList, para mencionar apenas alguns. As atribuições dos controles de lista são geralmente ligados a uma fonte de dados e atribuem uma parte da interface de usuário fixa e imutável a cada item de dados ligado. Por exemplo, um controle CheckBoxList cria um controle de caixa de seleção para cada elemento ligado. E se você precisar criar um controle de lista personalizado, isto é, um controle que atribua um parte personalizada de uma árvore de controles a cada item ligado? Crie um novo controle ligado a dados e torne-o herdeiro de ListControl.

A maioria dos controles de lista ASP.NET é herdeira de ListControl, incluindo os controles CheckBoxList e o mais novo BulletedList. Se você for criar um controle de lista totalmente personalizado, digamos, um controle HeadlineList, ListControl não será necessariamente a melhor classe com a qual começar. Para entender o porquê, vamos considerar um exemplo mais simples: uma lista de hiperlinks.

public class HyperLinkSimpleList : ListControl
{
   :
}

Depois de herdar de ListControl o processo está quase terminado. ListControl funciona repetindo uma determinada árvore de controles para cada item de dados ligado. A propriedade interna ControlToRepeat especifica o bloco de interface do usuário repetido: neste caso, um HyperLink.

private HyperLink _controlToRepeat;
private HyperLink ControlToRepeat
{
   get
   {
      if (_controlToRepeat == null)
      {
         _controlToRepeat = new HyperLink();
         Controls.Add(_controlToRepeat);
      }
      return _controlToRepeat;
   }
}

Em seguida, você substitui o método Render para fazer iteração na lista de itens ligados e repetir o controle. Veja um exemplo rápido:

protected override void Render(HtmlTextWriter writer)
{
    foreach(ListItem item in Items)
    {
   HyperLink ctl = ControlToRepeat;
   ctl.Text = item.Text;
   ctl.NavigateUrl = item.Value;
      ctl.RenderControl(writer);
    }
}

Nenhum outro código é necessário. Existem desvantagens? Apenas uma, mas geralmente você pode escolher uma rota alternativa. O principal problema é a representação dos itens ligados. ListControl define uma propriedade de coleção chamada Items. A propriedade é do tipo ListItemCollection. Os membros dessa coleção são objetos do tipo ListItem e têm duas propriedades de seqüência de caracteres: Text e Value. Quanto mais complexa for a árvore de controles usada para criar a lista, menos ListItem atenderá às suas necessidades de programação. Para representar um hiperlink, geralmente você precisa destes itens:

  • Um objeto String para o texto do hiperlink

  • Um objeto String da URL para navegação

  • Um objeto String para a dica de ferramenta

  • Um objeto String para o quadro de destino

Não é possível um ListItem para representar um item de dados com mais de duas propriedades. Sem falar nos problemas de nomenclatura que você pode encontrar mesmo que restrinja o item de dados a duas propriedades. (Para um hiperlink, poderia ser texto e URL somente.) Mas por que usar o termo Value para representar a URL? Essa é a razão porque ListControl é ótimo, contanto que você tenha árvores de controles relativamente simples para repetir os itens de dados com não mais de duas propriedades. Para os cenários mais complexos (ou simplesmente mais realistas), é preciso herdar de DataBaseControl e generalizar ligeiramente o código de Headline.

Controle HyperLinkList

Para criar esse controle ligado a dados personalizado, vamos usar o guia com cinco etapas descrito anteriormente no artigo. As etapas 1 e 2 determinam que você crie uma classe auxiliar de itens de dados para representar o item ligado. Como o controle HyperLinkList está ligado a diversos itens de dados, a classe precisará ser uma coleção de itens de dados individuais. Veja a classe HyperLinkItem que representa o hiperlink individual.

public class HyperLinkItem  
{
   private string _text;
   private string _url;
   private string _tooltip;
   public HyperLinkItem()
   {
   }
   public HyperLinkItem(string url, string text, string tooltip)
   {
      _text = text;
      _url = url;
      _tooltip = tooltip;
   }
   public string Text
   {
      get { return _text; }
      set { _text = value; }
   }
   public string Tooltip
   {
      get { return _tooltip; }
      set { _tooltip = value; }
   }
   public string Url
   {
      get { return _url; }
      set { _url = value; }
   }
}

A classe inclui três propriedades que podem ser ligadas para descrever o hiperlink: texto, URL e dica de ferramenta. A próxima etapa consiste em definir uma classe de coleção de HyperLinkItem. Usando os genéricos, a criação de uma classe como essa é simples.

public class HyperLinkItemCollection : List<HyperLinkItem>
{
}

Quem deve implementar os recursos de viewstate? Um controle de lista apresenta uma propriedade para agrupar todos os itens de dados que estejam atualmente ligados. Essa propriedade é geralmente chamada Items (nome arbitrário se não for herdeira de ListControl) e é implementada como uma coleção.

private HyperLinkItemCollection _items;
public virtual HyperLinkItemCollection Items
{
   get
   {
      if (_items == null)
      {
         _items = new HyperLinkItemCollection();
         if (base.IsTrackingViewState)
            _items.TrackViewState();
      }
      return _items;
   }
}

Conforme o código já mostra, a classe da coleção deve implementar a interface IStateManager. Fazer HyperLinkItem implementar também IStateManager certamente não é errado, mas esse código jamais será usado. Veja uma implementação possível de IStateManager na classe da coleção.

public class HyperLinkItemCollection : List<HyperLinkItem>, IStateManager
{
   private bool _marked;
   public HyperLinkItemCollection()
   {
      _marked = false;
   }
   public bool IsTrackingViewState
   {
      get { return _marked; }
   }
   public void LoadViewState(object state)
   {
      if (state != null)
      {
         Triplet t = (Triplet) state;
         Clear();

         string[] rgUrl = (string[])t.First;
         string[] rgText = (string[])t.Second;
         string[] rgTooltip = (string[])t.Third;

         for (int i = 0; i < rgUrl.Length; i++)
         {
           Add(new HyperLinkItem(rgUrl[i], 
                        rgText[i], rgTooltip[i]));
         }
      }
   }
   public object SaveViewState()
   {
      int numOfItems = Count;
      object[] rgTooltip = new string[numOfItems];
      object[] rgText = new string[numOfItems];
      object[] rgUrl = new string[numOfItems];

      for (int i = 0; i < numOfItems; i++)
      {
         rgTooltip[i] = this[i].Tooltip;
         rgText[i] = this[i].Text;
         rgUrl[i] = this[i].Url;
      }

      return new Triplet(rgUrl, rgText, rgTooltip);
   }
   public void TrackViewState()
   {
      _marked = true;
   }
}

O método SaveViewState precisa salvar as três propriedades de item para cada item ligado. Então, ele cria três matrizes de seqüências de caracteres com tantas entradas quantos forem os elementos na coleção. Cada matriz recebe a dica de ferramenta, o texto e a URL respectivamente. As três matrizes são agrupadas em um objeto Triplet e salvas no viewstate. E se você precisar salvar quatro matrizes? Você pode usar uma matriz com quatro elementos em vez de um objeto Triplet ou, talvez, um par de objetos Pair. O modo como os dados são incluídos é uma decisão exclusivamente sua.

Você também define as três propriedades de seqüência de caracteres para permitir que os usuários do controle especifiquem o campo da fonte de dados a ser usado para dicas de ferramentas, texto ou URLs. As propriedades são: DataTextField, DataUrlField e DataTooltipField. Veja uma implementação de exemplo:

public virtual string DataUrlField
{
   get
   {
      object o = ViewState["DataUrlField"];
      if (o == null)
         return "";
      return (string)o;
   }
   set { ViewState["DataUrlField"] = value; }
}

Outras propriedades de campo de dados têm uma implementação semelhante.

Na substituição de PerformDataBinding, você simplesmente preenche a coleção Items com as informações recebidas como um argumento.

protected override void PerformDataBinding(IEnumerable dataSource)
{
  base.PerformDataBinding(dataSource);

  string urlField = DataUrlField;
  string textField = DataTextField;
  string tooltipField = DataTooltipField;

  if (dataSource != null)
  {
   foreach (object o in dataSource)
   {
        HyperLinkItem item = new HyperLinkItem();
     item.Url = DataBinder.GetPropertyValue(o, urlField, null);
     item.Text = DataBinder.GetPropertyValue(o, textField, null);
     item.Tooltip = DataBinder.GetPropertyValue(o, tooltipField, null);
     Items.Add(item);
   } 
  }
}

Vamos recapitular rapidamente o que criamos até agora. Você tem um controle de lista ligado a dados com uma propriedade de coleção Items pública que aceita objetos HyperLinkItem. Você pode preencher essa coleção explicitamente usando métodos canônicos ou por meio de ligação de dados. Cada elemento na coleção Items dá origem a um hiperlink. Texto, URL e dica de ferramenta são atributos do hiperlink que podem ser associados a colunas da fonte de dados. A ligação é estabelecida através de uma série de propriedades DataXXXField. A coleção Items salva a si mesma no viewstate.

Duas perguntas ainda não foram respondidas. Primeiro, como Items é restaurado a partir do viewstate? Segundo, como o processamento é implementado?

Os recursos da propriedade Items de restaurar a partir do viewstate seriam inúteis se o controle não invocasse explicitamente LoadViewState e SaveViewState na coleção. Contudo, isso não acontece por padrão. Você também precisa substituir LoadViewState e SaveViewState no controle e ordenar chamadas semelhantes na coleção. Veja como isso é feito:

protected override void LoadViewState(object savedState)
{
   if (savedState != null)
   {
      Pair p = (Pair) savedState;
      base.LoadViewState(p.First);
      Items.LoadViewState(p.Second);
   }
   else
      base.LoadViewState(null);
}
protected override object SaveViewState()
{
   object baseState = base.SaveViewState();
   object itemState = Items.SaveViewState();
   if ((baseState == null) && (itemState == null))
      return null;
   return new Pair(baseState, itemState);
}

SaveViewState retorna um par de objetos: o resultado da mesma chamada de método na classe pai e o resultado de chamar SaveViewState em Items. O mesmo par é então desserializado em LoadViewState. Isso significa que, no caso de uma postagem, a coleção Items será restaurada corretamente a partir do último estado válido.

Interface IRepeatInfoUser

Muitos controles incorporados permitem determinar o layout final por meio da utilização de algumas propriedades como RepeatColumns, RepeatDirection e RepeatLayout. Usando essas propriedades, você pode instruir o controle para processar horizontal ou verticalmente por um determinado número de colunas, através de fluxo de saída ou da criação de uma tabela. Você também pode adicionar os mesmos recursos a seus próprios controles implementando a interface IRepeatInfoUser.

A interface é relativamente simples, composta de quatro propriedades e alguns métodos. Veja uma implementação de exemplo.

bool IRepeatInfoUser.HasFooter
{
   get { return false; }
}
bool IRepeatInfoUser.HasHeader
{
   get { return false; }
}
bool IRepeatInfoUser.HasSeparators
{
   get { return false; }
}
int IRepeatInfoUser.RepeatedItemCount
{
   get { return this.Items.Count; }
}
Style IRepeatInfoUser.GetItemStyle(ListItemType itemType, int repeatIndex)
{
   return null;
}

O segundo método é RenderItem e funciona em conjunto com o método Render do controle. O método Render deve dar suporte explícito ao processamento de layout variável, conforme a seguir:

protected override void Render(HtmlTextWriter writer)
{
   if (Items.Count > 0)
   {
      RepeatInfo ri = new RepeatInfo();
      ri.RepeatColumns = RepeatColumns;
      ri.RepeatDirection = RepeatDirection;
      ri.RepeatLayout = RepeatLayout;
      ri.RenderRepeater(writer, this, controlStyle, this);
   }
}

O método RenderRepeater da classe auxiliar RepeatInfo chama internamente os métodos do objeto IRepeatInfoUser especificado como seu segundo argumento: o próprio controle. No final, chama o seguinte código:

void IRepeatInfoUser.RenderItem(ListItemType itemType, int repeatIndex, 
            RepeatInfo repeatInfo, HtmlTextWriter writer)
{
   HyperLink ctl = ControlToRepeat;
   int i = repeatIndex;
   ctl.ID = i.ToString();
   ctl.Text = Items[i].Text;
   ctl.NavigateUrl = Items[i].Url;
   ctl.ToolTip = Items[i].Tooltip;
   ctl.RenderControl(writer);
}

O uso de uma propriedade interna chamada ControlToRepeat é uma forma de otimização. Em vez de criar um novo controle de hiperlink para cada iteração, você pode criá-lo apenas uma vez. Observe que esse truque não seria possível se em vez de processar explicitamente a marcação você estivesse criando uma árvore de controle. Na verdade neste caso, você precisaria adicionar à árvore de controle instâncias distintas de controles. A Figura 3 mostra o controle HyperLinkList durante o design.

Aa479308.ccc3_fig03(pt-br,MSDN.10).gif
Figura 3. Controle HyperLinkList no Visual Studio 2005

O código a seguir liga uma instância do controle a uma fonte de dados SQL para os resultados mostrados na Figura 4.

<cc1:HyperLinkList runat="server" ID="HyperLinkList1" 
     DataSourceID="SqlDataSource1" DataTooltipField="Notes" 
     DataTextField="Lastname" DataUrlField="Lastname" 
     RepeatColumns="3" RepeatDirection="Vertical"  />

Figura 4. Controle HyperLinkList em ação.

Conclusão

Os controles ligados a dados são bem mais fáceis de escrever no ASP.NET 2.0 do que em versões anteriores. Isso deve-se em grande parte a uma boa quantidade de classes intermediárias que proporcionam a você pontos de partida para a criação de novos controles. Neste artigo, examinamos as etapas necessárias para a criação de controles ligados a dados capazes de exibir dados de fontes habilitadas e mantê-los em postagens. Examinamos também os controles de lista e as interfaces avançadas necessárias à obtenção de layouts sofisticados.

Os controles de lista são talvez os controles ligados a dados mais simples. Os controles iterativos, compostos, parecidos com grade e em forma de modelo são o próximo passo, e isso requer um outro artigo.

Sobre o autor

Dino Esposito é um dos mentores da Solid Quality Learning (em inglês) e é o autor de "Programming Microsoft ASP.NET 2.0" (Microsoft Press, 2005), em inglês. Residente na Itália, Dino é um orador sempre presente em eventos da indústria no mundo inteiro. Entre em contato pelo endereço mailto:cutting@microsoft.com ou acesse o blog, em http://weblogs.asp.net/despos (em inglês).

© .

Page view tracker