Nos últimos anos as tecnologias voltadas para a Internet experimentaram um grande avanço, incluindo a própria, tornando-se mais rápida e capaz de fornecer recursos antes não possíveis através da rede. Além disso, a crescente descentralização dos serviços de computação e o desenvolvimento de sistemas de gestão e outras aplicações empresariais capazes de serem executadas através da rede mundial de computadores cresceram. Daí surgiu também um problema: como fazer com que estas aplicações forneçam a seus usuários tanta facilidade, desempenho e usabilidade quanto as aplicações desktop? A solução surgiu tão rápido quanto a própria necessidade.

Permitir que os usuários de sistemas web tenham as mesmas facilidades encontradas em sistemas desktop, principalmente no que se refere à navegação, é garantir que suas aplicações alcancem patamares mais elevados de qualidade e aceitação. Um dos principais gargalos do desenvolvimento de aplicações web é justamente a performance, o que será melhorado aplicando-se as técnicas descritas neste artigo.

Neste artigo discutiremos uma dessas soluções, que é a capacidade de paginação através do componente DataList. Basicamente, busca-se através deste componente apresentar ao usuário de um sistema web somente uma quantidade limitada de informações por página, permitindo a construção automática de uma sequência numérica que o permita acessar os demais dados, de acordo com algumas restrições definidas pelo desenvolvedor. Assim, garantimos que uma quantidade viável de dados será apresentada ao usuário, otimizando o tráfego de dados e melhorando a navegabilidade pelos dados requisitados através da aplicação web.

Paginação com DataList

Um Datalist é um componente de software (não exclusivo das tecnologias Microsoft), que exibe uma série de dados em um formato no qual o desenvolvedor pode definir como será seu uso, modelos e estilos. Os dados desse componente não obrigatoriamente devem advir de uma base de dados, podendo ser originados de qualquer tipo de arquivo ou inseridos de forma “manual”. Este componente oferece uma série de recursos às aplicações que dele fazem uso como, por exemplo, diferentes layouts para apresentação dos dados nele contidos através da apresentação em linhas, ordenação por linhas ou colunas.

Quando utilizamos um Datalist organizamos os dados de forma que, ao serem requisitados, somente parte destes dados sejam efetivamente apresentados ao usuário em uma única página, sendo que, havendo necessidade de acessar os demais dados, este usuário contará com um índice (geralmente numérico), que permite acesso aos demais dados de forma gradativa, sempre respeitando um limite máximo por apresentação. A Figura 1 apresenta um esquema que permite melhor entendimento sobre o que foi dito.

Exemplo de Paginação com Datalist
Figura 1. Exemplo de Paginação com Datalist

Como o componente Datalist não possui suporte nativo ao recurso de paginação, logo se torna necessário escrever os procedimentos de armazenamento de dados, bem como desenvolver um controle personalizado de paginação.

Mas como implementar uma paginação em ASP.NET utilizando o Datalist, uma vez que este componente não apresenta recurso nativo à paginação e também não herda a interface IPageableItemContainer, como o ListView, com o qual pode-se utilizar o controle DataPager?

Uma das opções é personalizando o banco de dados, armazenando no mesmo uma stored procedure que, a partir dos registros disponíveis, retorna para a aplicação somente uma quantidade específica de dados. Assim sendo, caso a tabela em questão tenha um total de 1000 registros, e para apresentação o desenvolvedor queira apresentar somente 50 registros por página, logo será necessário buscar esses tendo como base o índice da página, aumentando o desempenho da aplicação.

Recursos

Para os exemplos deste artigo foram utilizados os seguintes recursos: Microsoft Visual Studio Express, Microsoft SQL Server Express e uma base de dados exemplo fornecida pela própria Microsoft (Northwind), que está presente no código fonte desse artigo.

Criando a Stored Procedure de Paginação

Antes de aplicar a paginação às aplicações WEB, recomenda-se a adaptação da base de dados. É importante ressaltar que, a partir de sua versão 2005, o SQL Server trás consigo a palavra-chave ROW_NUMBER(), um recurso que permite recuperar o número da linha do registro selecionado, facilitando a implementação da lógica de paginação personalizada através da utilização de Stored Procedures no SQL Server.

No banco de dados que estamos utilizando existe uma tabela custumers (ou clientes). Nela vamos criar a nossa stored procedure usando o código da Listagem 1.


CREATE PROCEDURE [dbo].[RecuperaClientes]
@PageIndex INT = 1,
@PageSize INT = 10,
@RecordCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER 
(
ORDER BY [CustumerID] ASC
AS RowNumber,
[CustumerID],
[CompanyName],
[ContactName],
[City],
[Country],
[Phone],
[Fax],
[PostalCode]
INTO # Resultados
FROM [Custumers]
SELECT @RecordCount = COUNT(*)
FROM # Resultados

SELECT * FROM # Resultados
WHERE RowNumber BETWEEN(@PageIndex – 1) * @PageSize 
AND (((@PageIndex – 1 * @PageSize) + @PageSize) – 1
DROP TABLE # Resultados
END
Listagem 1. Código da Stored Procedure da Paginação

Começamos o código com a assinatura da Stored Procedure usando o nome RecuperaClientes e nela criamos os parâmetros de entrada. Por padrão, caso não sejam informados, os valores dos parâmetros PageIndex e PageSize são 1 e 10, respectivamente. Na linha 4 tem-se o parâmetro de saída que indica a quantidade de registros que serão apresentados na página.

Já na linha 7 temos o comando set nocount on, que desativa a contagem de registros feita sempre que é executada uma instrução SQL dentro de uma Stored Procedure, desta forma evita-se o excesso de tráfego durante a sua execução. Em seguida retornamos o total de linhas do select realizado sobre a tabela Custumers. Repare que na linha 20 indicamos que o resultado desta seleção será armazenado na tabela temporária Resultados.

A linha 22 realiza uma nova seleção procurando recupera o número de registros da tabela Resultados e em seguida armazena no parâmetro de saída RecordCount. Na linha 25 realizamos outra seleção na tabela Resultados, só que dessa com uma restrição do retorno. E ao final excluímos a tabela Resultados.

Esquema de funcionamento da Listagem 1
Figura 2. Esquema de funcionamento da Listagem 1

Preparando a página do cliente

Uma iniciativa da Microsoft que deve-se ressaltar é sua visão democrática quanto ao desenvolvimento, o que significa que, para desenvolver aplicações web utilizando seus recursos mais modernos pode-se escolher entre as linguagens C# e VB.Net: todas são aceitas para o desenvolvimento de aplicações ASP.NET. Por este motivo, neste artigo apresentaremos as duas formas de implementação.

Para iniciar devemos importar algumas bibliotecas ou namespaces na página onde serão apresentados os dados. As Listagens 2 e 3 apresentam o código necessário nas linguagens C# e VB.net, respectivamente.


using System.Data;
using System.Data.SqlClient;
using System.Configuration;
Listagem 2. Namespaces no C#

imports System.Data;
imports System.Data.SqlClient;
imports System.Configuration;
Listagem 3. Namespaces no VB.Net

Em ambas as listagens importamos as bibliotecas necessárias para conexão, manipulação e configuração de dados.

Seguindo o propósito deste artigo, é necessário algum código HTML simples usando um DataList para preenchimento dos registros de clientes e um controle Repeater, como mostra a Listagem 4.


<asp:DataList ID="dlCustomers" runat="server" 
RepeatDirection="Horizontal" RepeatColumns="3">
<ItemTemplate>
<table cellpadding="2" cellspacing="0" class="Item">
  <tr>
      <td class="header">
          <b><u>
              <%# Eval("ContactName") %></u></b>
      </td>
  </tr>
  <tr>
     <td class="body">
          <b>City: </b>
          <%# Eval("City") %><br />
          <b>Postal Code: </b>
          <%# Eval("PostalCode") %><br />
          <b>Country: </b>
          <%# Eval("Country") %><br />
          <b>Phone: </b>
          <%# Eval("Phone") %><br />
          <b>Fax: </b>
          <%# Eval("Fax") %>
      </td>
  </tr>
</table>
</ItemTemplate>
</asp:DataList>
Listagem 4. HTML para apresentação dos dados

Começamos com alguns comandos básicos para implementação e integração dos recursos ASP.NET em um código HTML, no caso os componentes asp (com o comando asp) seguido da definição de um objeto DataList. Lembrando que todo script referente ao .NET Framework será executado no servidor, por isso definimos o modo de repetição como horizontal e limitado a um máximo de três colunas por linha.

Na linha 7 inserimos o comando que retorna o valor do campo ContactName, que é recuperado diretamente do objeto DataList. Nas demais linhas temos os códigos HTML que regem o formato de apresentação dos dados.

Devido as diferenças entre as sintaxes da linguagem C# e VB.Net, a segunda parte do código foi dividida em duas listagens, respectivamente as Listagens 5 e 6.


<asp:Repeater ID="rptPager" runat="server">
  <ItemTemplate>
      <asp:LinkButton ID="lnkPage" runat="server" 
      Text='<%#Eval("Text") %>’ CommandArgument='<%# 
      Eval("Value") %>'
          CssClass=’<%# Convert.ToBoolean(Eval("Enabled")) ? 
          "page_enabled" : "page_disabled" %>'
          OnClick="Page_Changed" OnClientClick='<%# 
          !Convert.ToBoolean(Eval("Enabled")) ? "return false;" : 
          "" %>'></asp:LinkButton>
 </ItemTemplate>
</asp:Repeater>
Listagem 5. Repeater em C#

<asp:Repeater ID="rptPager" runat="server">
  <ItemTemplate>
      <asp:LinkButton ID="lnkPage" runat="server" 
      Text='<%#Eval("Text") %>' 
      CommandArgument='<%# Eval("Value") %>'
          CssClass='<%# If(Convert.ToBoolean(Eval("Enabled")), 
          "page_enabled", "page_disabled")%>'
          OnClick="Page_Changed" OnClientClick='<%# 
          If(Not Convert.ToBoolean(Eval("Enabled")), 
          "return false;", "") %>'></asp:LinkButton>
  </ItemTemplate>
</asp:Repeater>
Listagem 6. Repeater em VB.Net

Apesar de sintaxes diferentes, ambas executam a mesma operação com a mesma quantidade de linhas de código escritas. Sendo assim, na linha 1 é criado o objeto Repeater a ser executado no servidor e na linha 3 criamos um botão de link, onde o texto do botão e seu valor serão recuperados do código armazenado no servidor.

A linha 4 indica qual será a classe CSS utilizada, sendo o seu valor também recuperado do código armazenado no servidor. Para definir se a classe será “page_enabled” ou “page_disabled” aplica-se um operador ternário que verifica se o valor da variável Enabled é verdadeiro.

Por fim, na linha 5 adicionamos uma ação ao evento click realizado no lado do cliente.

Preparando o lado do servidor

Realizadas as operações de preparação da página do cliente, seguimos então para a preparação do servidor. Neste ponto uma atividade muito importante é realizar a vinculação do DataList com a página de dados personalizada. Como a carga de dados é realizada página a página, serão realizadas chamadas à Stored Procedure RecuperaClientes, sendo informado a esta os devidos parâmetros para que sua execução ocorra de forma correta. Os registros retornados pela Stored Procedure serão armazenados no DataList.

Novamente foram realizadas implementações em C# e VB.Net, respectivamente nas Listagens 7 e 8.


private int PageSize = 6;
protected void Page_Load(object sender, EventArgs e) {
 if (!IsPostBack){
    this.GetCustomersPageWise(1);
 }
}

private void GetCustomersPageWise(int pageIndex) {
 string constring = ConfigurationManager.ConnectionStrings["constring"]
 .ConnectionString;
 using (SqlConnection con = new SqlConnection(constring)){
   using (SqlCommand cmd = new SqlCommand("GetCustomersPageWise", con)){
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.AddWithValue("@PageIndex", pageIndex);
     cmd.Parameters.AddWithValue("@PageSize", PageSize);
     cmd.Parameters.Add("@RecordCount", SqlDbType.Int, 4);
     cmd.Parameters["@RecordCount"].Direction = ParameterDirection.Output;
     con.Open();
     IDataReader idr = cmd.ExecuteReader();
     rptCustomers.DataSource = idr;
     rptCustomers.DataBind();
     idr.Close();
     con.Close();
     int recordCount = Convert.ToInt32(cmd.Parameters["@RecordCount"].Value);
     this.PopulatePager(recordCount, pageIndex);
   }
 }
}
Listagem 7. Recuperando dados com C#

Private PageSize As Integer = 6
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
   If Not IsPostBack Then
       Me.GetCustomersPageWise(1)
   End If
End Sub

Private Sub GetCustomersPageWise(pageIndex As Integer)
   Dim constring As String = ConfigurationManager.ConnectionStrings("constring").ConnectionString
 Using con As New SqlConnection(constring)
     Using cmd As New SqlCommand("GetCustomersPageWise", con)
          cmd.CommandType = CommandType.StoredProcedure
          cmd.Parameters.AddWithValue("@PageIndex", pageIndex)
          cmd.Parameters.AddWithValue("@PageSize", PageSize)
          cmd.Parameters.Add("@RecordCount", SqlDbType.Int, 4)
          cmd.Parameters("@RecordCount").Direction = ParameterDirection.Output
          con.Open()
          Dim idr As IDataReader = cmd.ExecuteReader()
          rptCustomers.DataSource = idr
          rptCustomers.DataBind()
          idr.Close()
          con.Close()
          Dim recordCount As Integer = Convert.ToInt32(cmd.Parameters("@RecordCount").Value)
          Me.PopulatePager(recordCount, pageIndex)
      End Using
  End Using
End Sub
Listagem 8. Recuperando dados com VB.Net

Começamos os códigos criando uma variável PageSize, onde o valor padrão definido foi 6, mas podemos alterá-lo sem qualquer prejuízo ao código.

Na linha 2 é criado um evento Page_Load, que será disparado no momento em que a página do cliente for carregada. Em seguida verifica-se se a página está sendo carregada pela primeira vez ou não (IsPosBack): caso esteja, então o método GetCustomersPageWise descrito a partir da linha 8 é chamado com parâmetro de entrada configurado como 1, indicando que a contagem deve iniciar do primeiro registro. Nas linhas 9 e 10 é criada uma string de conexão com o banco de dados e um objeto de conexão e a seguir indica-se que a conexão será para uma Stored Procedure.

As linhas 13 a 15 enviam os valores para os parâmetros de entrada da StoredProcedure e na linha 16 indicamos o parâmetro de saída.

Após abrir a conexão com o banco de dados na linha 17 cria-se um objeto IDataReader para percorrer pelos dados retornados pela conexão.

Na linha 24 fazemos uma chamada a um método muito importante, pois este recebe o total de registros presente na tabela do banco de dados e o índice da página atual, sendo então realizados alguns cálculos para que o sistema identifique a primeira e última página da listagem.

Ao final temos a vinculação com o controlador Repeater, de acordo com as Listagens 9 e 10.


private void PopulatePager(int recordCount, int currentPage) {
  List<ListItem> pages = new List<ListItem>();
  int startIndex, endIndex;
  int pagerSpan = 5;
   //Cálculo do início e fim da contagem de páginas
  double dblPageCount = (double)((decimal)recordCount / 
  Convert.ToDecimal(PageSize));
  int pageCount = (int)Math.Ceiling(dblPageCount);
  startIndex = currentPage > 1 && currentPage 
  + pagerSpan - 1 < pagerSpan ? currentPage : 1;
  endIndex = pageCount > pagerSpan ? pagerSpan : pageCount;
  if (currentPage > pagerSpan % 2) {
      if (currentPage == 2){
          endIndex = 5;
      }else{
          endIndex = currentPage + 2;
      }
  }else{
      endIndex = (pagerSpan - currentPage) + 1;
 }
  if (endIndex - (pagerSpan - 1) > startIndex){
     startIndex = endIndex - (pagerSpan - 1);
 }
 if (endIndex > pageCount){
   endIndex = pageCount;
     startIndex = ((endIndex - pagerSpan) + 1) > 0 ? 
     (endIndex - pagerSpan) + 1 : 1;
  }
  //Adiciona botão da primeira página.
  if (currentPage > 1){
      pages.Add(new ListItem("First", "1"));
  }
  //Adiciona botão anterior.
  if (currentPage > 1){
     pages.Add(new ListItem("<<", (currentPage - 1)
     .ToString()));
  }
  for (int i = startIndex; i <= endIndex; i++){
      pages.Add(new ListItem(i.ToString(), i.ToString(), 
      i != currentPage));
  }
  //Adiciona botão próximo.
  if (currentPage < pageCount){
      pages.Add(new ListItem(">>", (currentPage + 1)
      .ToString()));
 }
  //Adiciona botão para última página.
  if (currentPage != pageCount){
      pages.Add(new ListItem("Last", pageCount.ToString()));
  }
  rptPager.DataSource = pages;
 rptPager.DataBind();
}
Listagem 9. Preenchendo o controle pager com C#

Private Sub PopulatePager(recordCount As Integer, 
currentPage As Integer)
  Dim pages As New List(Of ListItem)()
  Dim startIndex As Integer, endIndex As Integer
  Dim pagerSpan As Integer = 5
  ‘Cálculo do início e fim da contagem de páginas
  Dim dblPageCount As Double = CDbl(CDec(recordCount) 
  / Convert.ToDecimal(PageSize))
  Dim pageCount As Integer = CInt(Math.Ceiling(dblPageCount))
  startIndex = If(currentPage > 1 AndAlso currentPage 
  + pagerSpan - 1 < pagerSpan, currentPage, 1)
    endIndex = If(pageCount > pagerSpan, pagerSpan, pageCount)
    If currentPage > pagerSpan Mod 2 Then
        If currentPage = 2 Then
            endIndex = 5
        Else
            endIndex = currentPage + 2
        End If
    Else
      endIndex = (pagerSpan - currentPage) + 1
End If
  If endIndex - (pagerSpan - 1) > startIndex Then
      startIndex = endIndex - (pagerSpan - 1)
  End If
  If endIndex > pageCount Then
      endIndex = pageCount
      startIndex = If(((endIndex - pagerSpan) + 1) > 0, 
      (endIndex - pagerSpan) + 1, 1)
  End If
  ‘Botão para primeira página
  If currentPage > 1 Then
      pages.Add(New ListItem("First", "1"))
  End If
  ‘Botão para página anterior
  If currentPage > 1 Then
      pages.Add(New ListItem("<<", 
      (currentPage - 1).ToString()))
  End If
  For i As Integer = startIndex To endIndex
      pages.Add(New ListItem(i.ToString(), i.ToString(), 
      i <> currentPage))
  Next
  ‘Botão para a próxima página.
  If currentPage < pageCount Then
      pages.Add(New ListItem(">>", 
      (currentPage + 1).ToString()))
  End If
  ‘Botão para a última página
  If currentPage <> pageCount Then
      pages.Add(New ListItem("Last", pageCount.ToString()))
  End If
  rptPager.DataSource = pages
  rptPager.DataBind()
End Sub
Listagem 10. Preenchendo o controle pager com VB.Net

O código que realiza o cálculo da quantidade de páginas a serem exibidas começa na linha 6, dividindo o total de páginas pela quantidade de registro por páginas e garante-se que o resultado será um valor inteiro. A linha 8 e 9 utilizam operadores ternários para definir a página de início e fim: verifica-se se a página aberta é maior do que 1, e se esta, somada ao valor (PageSpan = 5) menos 1 é menor do que o valor de PageSpan. Caso verdadeiro, então a página inicial será a currentPage, caso contrário a página inicial será 1. Na linha 9 verifica-se somente se o número de páginas é maior do que o valor de PageSpan: se for, então a última página será o PageSpan, senão a última página será o PageCount.

Finalmente cria-se um método que será executado quando o LinkButton de uma página for clicado. Este evento realiza um comando que indica ao banco de dados que este deve obter um novo conjunto de registros com base nos argumentos repassados a ele. O código pode ser observado nas Listagens 11 e 12.


protected void Page_Changed(object sender, EventArgs e)
{
   int pageIndex = int.Parse((sender as LinkButton).CommandArgument);
  this.GetCustomersPageWise(pageIndex);
}
Listagem 11. Método Page_Chanced em C#

Protected Sub Page_Changed(sender As Object, e As EventArgs)
  Dim pageIndex As Integer = Integer.Parse(TryCast(sender, LinkButton).CommandArgument)
  Me.GetCustomersPageWise(pageIndex)
End Sub
Listagem 12. Método Page_Chanced em VB.Net

Este evento, depois de inserido nos botões da página, garantirão a chamada do método GetCustumersPageWise, que disparará o processo para cada clique realizado.

Veja que com pouco código podemos criar uma página web capaz de realizar paginação de dados, evitando assim o envio de uma quantidade exagerada de informações para o usuário, informações que em muitos casos são desnecessárias até, levando a uma sobrecarga no processamento, desestimulando a permanência do usuário na mesma.

Utilizar recursos como o DataList, disponível não somente na plataforma .NET, mas também em muitas outras plataformas de desenvolvimento, traz ao desenvolvedor a facilidade de interagir com seus registros armazenados em banco de dados através de um componente robusto e seguro, capaz de oferecer ao mesmo uma série de recursos e templates que reduzem a complexidade do trabalho, diminuindo o tempo gasto na preparação dos dados para exibição.

Sempre que pretendemos apresentar uma coleção de dados para os usuários, o uso do DataList é indicado, mas existem outras formas de se realizar o processo, porém as facilidades, a robustez, a agilidade e performance oferecidas pelo componente não devem ser ignoradas.

Espero que tenham gostado do artigo e até a próxima.