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.

terça-feira, 11 de janeiro de 2011

Monitorando Arquivos com C#

Para você que deseja monitorar alterações em arquivos ou pastas em seu computador, o .NET FrameWork possui a classe FileSystemWatcher no namespace System.IO.
A implementação é simples e possui inúmeras propriedades, então mão na massa.

Crie um projeto Console e Adicione o Namespace System.IO.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace FileSystemWatcherChp2
{
    class Program
    {
        static void Main(string[] args)
        {
 }
    }
}



Feito isso vamos instanciar a classe FileSystemWatcher, seu construtor possui 3 overloads, neste passaremos o caminho a ser monitorado, neste exemplo iremos utilizar a variavel de ambiente para resgatar os diretórios da "USERPROFILE".

static void Main(string[] args)
  {
    FileSystemWatcher fsw = 
                     new FileSystemWatcher(Environment.GetEnvironmentVariable("USERPROFILE"));     
  }


Agora vamos setar algumas propriedades importantes para nosso programa, por exemplo o tipo de alteração no arquivo, para maior referência das propriedades acesse http://bit.ly/bNVlCi.

static void Main(string[] args)
  {
    FileSystemWatcher fsw = 
                     new FileSystemWatcher(Environment.GetEnvironmentVariable("USERPROFILE"));     

     //Propriedade para informar se deve monitorar subdiretorios ou somente o principal
     fsw.IncludeSubdirectories = true;
     //Tipo de Notificação
     //(FileName,LastWrite,DirectoryName,Attributes,Size,LastAccess,CreationTime,Security)
     fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
  }


Um filtro interessante é a propriedade Filter, que lhe possibilita monitorar inclusive um arquivo em especifico. 
Para monitorar as alterações precisamos de eventos, para indicar quando algo acontece, dos eventos implementados para esta classe cabe destaque para 4:
Em nosso exemplo utilizaremos somente o changed e o Renamed.

static void Main(string[] args)
  {
    FileSystemWatcher fsw = 
                     new FileSystemWatcher(Environment.GetEnvironmentVariable("USERPROFILE"));     

     //Propriedade para informar se deve monitorar subdiretorios ou somente o principal
     fsw.IncludeSubdirectories = true;
     //Tipo de Notificação
     //(FileName,LastWrite,DirectoryName,Attributes,Size,LastAccess,CreationTime,Security)
     fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
     //Evento changed, para alterações gerais no arquivo ou pasta
     fsw.Changed += new FileSystemEventHandler(fsw_Changed);
     //Evento Renamed, específico para alteração do nome do arquivo ou pasta
     fsw.Renamed += new RenamedEventHandler(fsw_Renamed);
     //Inicia o Monitoramento
     fsw.EnableRaisingEvents = true;

     //console.read para impedir que a aplicação finalize sua execução
     Console.Read();
  }

E por fim, os métodos que recebem as chamadas dos eventos.

//Evento Changed
   static void fsw_Changed(object sender, FileSystemEventArgs e)
   {
       //Escreve o tipo de alteração e o endereço do arquivo ou pasta alterado
        Console.WriteLine(e.ChangeType + ": " + e.FullPath);
   }
    //Evento Renamed
    static void fsw_Renamed(object sender, RenamedEventArgs e)
    {
       //Escreve na tela o nome de origem e seu novo nome
       Console.WriteLine("from: " + e.OldFullPath + " to: " + e.FullPath);
    }


Veja o resultado: