sexta-feira, 11 de fevereiro de 2011

Paginação com ASP.NET MVC e Entity Framework


Para quem está habituado a trabalhar com WebForms em ASP.NET, enfrentar um projeto MVC pode trazer alguns empecilhos, tendo em vista facilidades que tinhamos no WebForms, como GridView. Utilizando um GridView bastava setar suas propriedades e pronto, tinhamos um grid de informações da base paginado, formatado e até com “sort” implementado.
No MVC já não dispomos destas facilidades e, quando buscamos por exemplos de Upload de Arquivos, paginação, dentre outras facilidades que o WebForms nos disponibiliza, não encontramos muitos exemplos.
Neste artivo iremos tratar de paginação, utilizando MVC 2.0 (também funciona para o 1.0) e acesso a dados com Entity FrameWork utilizando LINQ to Entities. Antes de falarmos da paginação propriamente dita, gostaria de falar sobre um recurso poderoso do LINQ (to SQL ou Entities), a interface IQueryable.
O IQueryable é uma interface detinada a provedores de consulta, sua implementação só é possível em provedores que também implementem a IQueryable<T>. Esta interface herda de IEnumerable, possibilitando, assim a enumeração de dados. O mais interessante em sua utilização é a possibilidade de implementação com recursos de filtragem, além de outros de agrupamentos e ordenação. Mas ai vem a pergunta: Um List ou mesmo um Enumerable também possuem este recurso, então por que utilizá-la?
Sua grande diferença é que a sua execução efetiva na base ocorre somente no momento em que utilizarmos algum recurso de chamada para apresentação e/ou utilização dos dados, portanto, se voce possui um método que teoricamente retorna todos os dados e, em seguida quer dar um count em seu resultado, o comando executado na base de dados será somente o Count. Diferente de outras implementações do IEnumerable (List, Enumerable...) o IQueryable não retorna tudo para a memória  e executa o Count no objeto.
Passada essa pequena explicação vamos ao projeto. Considerando que voce saiba criar e conhece a estrutura de um projeto ASP.NET MVC, vamos direto ao assunto, a paginação.
Neste projeto criamos um Edmx utilizando a base AdventureWorks da microsoft e iremos exibir os dados de empregados, nosso projeto está com a estrutura vista na Figura 1.



 Figura 1 Estrutura do projeto

Como pode ver temos uma Interface de Repositório e duas classes em nosso Model: EmployeeRepository, que é nosso repositório de empregados e PaginatedData, nossa classe de paginação. Em Views temos nossa Employee com apenas a View Index para listar os dados e nossa Controller chamada EmployeeController.
A estrutura da Controller, a principio, esta muito simples, temos apenas o método Index que retorna todos os empregados, sem paginação ou tratamento. O resultado disso pode ser visto na Figura 2, onde temos todos os empregados listados de uma só vez.

EmployeeRepository employeeRepository = new EmployeeRepository();
        public ActionResult Index()
        {
            //retorna todos os empregados
            IQueryable<Employee> employees = employeeRepository.GetAll();


            return View(employees);
        }


Figura 2 Listagem sem paginação

Agora vamos a implementação de nossa classe PaginatedData, que é onde tudo acontece, sua estrutura é simples, possui 6 propriedades e um construtor que recebe o número de registros por página e qual página exibir. Utilizamos o <T> para que a classe possa ser genérica, assim sua implementação é possível com IQueryable<> de qualquer tipo.

public class PaginatedData<T>:List<T>
    {
        public int PageIndex { get; private set; }
        public int PageSize { get; private set; }
        public int TotalCount { get; private set; }
        public int TotalPages { get; private set; }

        public PaginatedData(IQueryable<T> source, int pageIndex, int pageSize)
        {
            PageIndex = pageIndex;
            PageSize  = pageSize;
            TotalCount = source.Count();

            TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);

            this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
        }
        public bool HasPreviousPage
        {
            get
            {
                return (PageIndex > 0);
            }
        }
        public bool HasNextPage
        {
            get
            {
                return (PageIndex + 1 < TotalPages);
            }
        }
    }
Criada a classe de paginação, vamos implementá-la em nossa Controller, sua estrutura simples facilita também sua implementação.
public ActionResult Index(int? page)
{
   IQueryable<Employee> employees = employeeRepository.GetAll();

   var paginatedEmployee = new PaginatedData<Employee>(employees, page ?? 0, 10);


   return View(paginatedEmployee);
}

               Basicamente a alteração de nosso método Index foram a inclusão de um parametro do tipo int anulável, paga receber a proxima página e a chamada da classe PaginatedData. Na Figura 3 podemos ver o resultado listado, contendo apenas 10 registros, que foi o numero de registros por pagina que passamos ao construtor da classe.

Figura 3 Listagem de empregados filtrada


Então nossa paginação está pronta e funcionando 100%! Não, teremos que alterar a View, hoje nossa View é tipada para Employee e recebe um IEnumerable, porém teremos que alterar esta estrutura para que seja possível paginar as informações. Na primeira linha temos a situação atual da View, já na segunda observe que alteramos o IEnumerable para PaginacaoMvc.Models.PaginatedData, assim poderemos trabalhar a navegação das paginas.
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<PaginacaoMvc.Models.Employee>>" %>


<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<PaginacaoMvc.Models.PaginatedData<PaginacaoMvc.Models.Employee>>" %>

A estrutura do “Grid” da View também será um pouco alterada, antes da paginação encontrava-se conforme abaixo.
<table>
        <tr>
            <th>
                Nome
            </th>
            <th>
                E-mail
            </th>
            <th>
                Aniversário
            </th>
            <th>
                Estado Civil
            </th>
            <th>
                Sexo
            </th>
        </tr>

    <% foreach (var item in Model) { %>
   
        <tr>
            <td>
                <%: item.Contact.FirstName %>
            </td>
            <td>
                <%: item.Contact.EmailAddress %>
            </td>
            <td>
                <%: String.Format("{0:dd/MM/yyyy}", item.BirthDate) %>
            </td>
            <td>
                <%: item.MaritalStatus %>
            </td>
            <td>
                <%: item.Gender %>
            </td>
        </tr>
   
    <% } %>
    </table>

Para implementarmos a navegação das páginas iremos adicionar o trecho de codigo abaixo.
        <tr>
            <td>
                <% if (Model.HasPreviousPage) { %>
                   <%= Html.ActionLink("Anterior","Index",new { page = Model.PageIndex - 1 }) %>
                   
                <% } %>
            </td>
            <td colspan="3">
                <% if (Model.HasNextPage) { %>
                   <%= Html.ActionLink("Próxima", "Index", new { page = Model.PageIndex + 1 })%>
                   
                <% } %>
            </td>
        </tr>
    </table>

Agora sim temos um Grid paginado e funcional, conforme Figura 4.

Figura 4 Grid com paginação

            Apesar de não termos as facilidades do WebForms, ainda assim trabalhar com ASP.NET MVC é simples e rápido,  o Visual Studio possui muitas opções para agilizar o desenvolvimento e o .NET FrameWork não fica atrás com seus recursos. Se você é iniciante em ASP.NET MVC sugiro a leitura do livro NerdDinner, é um livro didático e prático.