Por que eu devo ler este artigo:Este artigo é útil para desenvolvedores que desejam aprender ou aprimorar seus conhecimentos na geração de relatórios em aplicações ASP.NET MVC, aproveitando todos os recursos de formatação e responsividade do Bootstrap. Este artigo traz uma abordagem baseada nos principais recursos dos plug-ins PagedList, para possibilitar a geração de relatórios no ASP.NET MVC, e Rotativa, que transforma páginas HTML em arquivos PDF.

O uso de software para gestão empresarial está presente nas mais variadas empresas que atuam no mercado, sejam elas de pequeno ou grande porte. Os gestores necessitam consultar e analisar dados gerados pelo sistema de gestão, que por sua vez pode integrar informações de vários setores da empresa, como vendas, financeiro, estoque, compras, fiscal, contábil, dentre outros. Com base neste cenário complexo, é necessário ter ferramentas que possam gerar relatórios para que o gestor de negócio possa analisar os dados e tomar decisões referentes aos mais variados processos de negócio, dentro e fora da empresa.

O desenvolvimento de relatórios em aplicações web, no entanto, nem sempre é uma tarefa fácil, visto que estamos na era da mobilidade e por isso precisamos oferecer aos usuários soluções que possam ser acessadas também por dispositivos móveis, como smartphones e tablets.

Para tornar possível o desenvolvimento de relatórios nesse cenário, iremos aprender neste artigo a trabalhar com os plug-ins PagedList e Rotativa em uma aplicação ASP.NET MVC. Além disso, utilizaremos os recursos do Bootstrap para oferecer uma interface elegante e responsiva, compatível com os principais browsers da atualidade.

Criando o projeto ASP.NET MVC

Para iniciar nossa aplicação de relatórios, começaremos criando um novo projeto do tipo ASP.NET Web Application no Visual Studio, que nomearemos aqui como “Relatorios-ASPNET-MVC”. Na tela seguinte devemos selecionar o template MVC e na opção Change Authentication marcar o item “No Authentication”, pois não iremos trabalhar com o sistema de autenticação padrão do ASP.NET. Clicamos então em OK para que o Visual Studio possa criar o projeto com todos os arquivos necessários para o projeto que segue o padrão MVC.

Após o projeto ter sido criado, o segundo passo é adicionar a versão mais recente da biblioteca Entity Framework, que fará o Mapeamento Objeto-Relacional das classes do domínio da nossa aplicação para gerar, a partir delas, o banco de dados SQL Server. Aqui utilizaremos a abordagem Code First, em que realizamos toda a modelagem das entidades a partir de classes POCO (Plain Old CLR Object), sem utilizar a ferramenta de modelagem visual comum na abordagem Model First, ou começando o desenvolvimento pela criação das tabelas no banco (Database First).

Para adicionar o Entity Framework, devemos clicar no menu Tools>Nuget Package Manager> Package Manager Console e assim baixar a biblioteca diretamente do repositório do NuGet, que é a ferramenta de gerenciamento de pacotes integrada ao Visual Studio. Digite o comando Install-Package EntityFramework no console e pressione Enter para que a biblioteca seja instalada, conforme mostra a Figura 1.

Como gerar Relatórios no ASP.NET MVC
Figura 1. Instalação do Entity Framework no Package Manager Console

Criando as classes POCO para entidades

O Entity Framework Code First trabalha baseado em classes POCO que representam as entidades do mundo real a serem mapeadas para o banco de dados, desta forma, precisaremos criar algumas classes que irão modelar o cenário do sistema de vendas.

A Figura 2 representa o DER (Diagrama Entidade Relacionamento) do sistema de vendas proposto para geração dos relatórios. Perceba que a base de dados será composta por quatro tabelas: Cliente, Produto, Pedido Cabeçalho e Item Pedido. Seguindo esse contexto precisaremos criar uma classe para cada tabela.

Como gerar Relatórios no ASP.NET MVC
Figura 2. Diagrama Entidade Relacionamento do sistema de vendas

O projeto criado anteriormente no Visual Studio é do tipo ASP.NET MVC, assim sendo iremos concentrar as classes modelo dentro da pasta Model presente no Solution Explorer.

Para adicionar a primeira classe clique com o botão direito nessa pasta e depois na opção Add > Class. Nomeie como Cliente e logo em seguida clique em Add para que o arquivo seja criado.

Com a classe cliente criada, adicione o código presente na Listagem 1.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class Cliente
  {
      public Cliente()
      {
          this.PedidoCabecalho = new List<PedidoCabecalho>();
      }
      public int ClienteId { get; set; }
      public string Nome { get; set; }
      public string CpfCnpj { get; set; }
      public string Endereco { get; set; }
      public string Bairro { get; set; }
      public string Cidade { get; set; }
      public string Cep { get; set; }
      public string Email { get; set; }
      public string Telefone { get; set; }
      public string StatusCliente { get; set; }
      public DateTime DataCadastro { get; set; }
      public virtual ICollection<PedidoCabecalho> PedidoCabecalho { get; set; }
  }
}
Listagem 1. Implementação da classe Cliente

A implementação traz algumas particularidades:

  • Linhas 10 a 13: Neste intervalo foi criado um construtor para a classe Cliente que instancia uma lista de objetos do tipo PedidoCabecalho (declarada na linha 25) que iremos criar mais à frente. Esta propriedade irá representar, em tempo de execução, a relação entre a classe Cliente e PedidoCabecalho para que possamos facilmente acessar os pedidos de um cliente com o LINQ (Language-Integrated Query);
  • Linha 14: Aqui o atributo referente ao id do cliente foi definido como ClienteId ao invés de IdCliente, por exemplo. Essa é uma convenção de nomenclatura que o Entity Framework usa para identificar uma chave primária em uma classe (<nome_entidade>Id).

As demais propriedades dispensam maiores comentários, uma vez que se tratam apenas de tipos nativos do framework e possuem nomes sugestivos.

Na sequência, crie outra classe chamada de Produto e implemente o código presente na Listagem 2.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class Produto
  {
      public Produto()
      {
          this.ItemPedido = new List<ItemPedido>();
      }
      public int ProdutoId { get; set; }
      public string Descricao { get; set; }
      public Nullable<decimal> PrecoCompra { get; set; }
      public Nullable<decimal> PrecoVenda { get; set; }
      public Nullable<int> QtdeEstoque { get; set; }
      public Nullable<System.DateTime> DataCadastro { get; set; }
      public string StatusProduto { get; set; }
      public virtual ICollection<ItemPedido> ItemPedido { get; set; }
  }
}
Listagem 2. Implementação da classe Produto

Essa implementação é bem similar à da classe Cliente, sendo instanciado um objeto do tipo List no construtor para representar o relacionamento desta classe com a de itens do pedido. Novamente foi seguida a convenção para a nomenclatura da chave primária (linha 12).

Siga os mesmos passos descritos anteriormente e insira uma nova classe chamada de PedidoCabecalho e, em seguida, implemente a mesma conforme a Listagem 3. Essa classe representa os principais dados a respeito do pedido, tais como valor total, cliente e data.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class PedidoCabecalho
  {
      public PedidoCabecalho()
      {
          this.ItemPedido = new List<ItemPedido>();
      }

      public int PedidoCabId { get; set; }
      public int ClienteId { get; set; }
      public Nullable<decimal> ValorTotalPedido { get; set; }
      public Nullable<int> QtdeItens { get; set; }
      public string StatusPedido { get; set; }
      public Nullable<System.DateTime> DataPedido { get; set; }
      public virtual Cliente Cliente { get; set; }
      public virtual ICollection<ItemPedido> ItemPedido { get; set; }
  }
}
Listagem 3. Implementação da classe PedidoCabecalho

Nessa classe temos, na linha 16, uma referência ao ID do cliente ao qual pertence esse pedido. Na linha 21 temos uma propriedade do tipo Cliente. Em tempo de execução, isso permitirá que o Entity Framework preencha o objeto Cliente com todos os dados deste, sem que precisemos realizar outra consulta a essa entidade para que tenhamos suas informações.

A última classe a ser criada é a ItemPedido, que se relaciona com o produto e com o cabeçalho do pedido. Crie a mesma e a implemente conforme a Listagem 4.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class ItemPedido
  {
      public int ItemPedidoId { get; set; }
      public int PedidoCabId { get; set; }
      public int ProdutoId { get; set; }
      public Nullable<int> Quantidade { get; set; }
      public Nullable<decimal> ValorUnitario { get; set; }
      public Nullable<decimal> Subtotal { get; set; }
      public string StatusItem { get; set; }
      public virtual PedidoCabecalho PedidoCabecalho { get; set; }
      public virtual Produto Produto { get; set; }
  }
}
Listagem 4. Implementação da classe ItemPedido

A única coisa a se destacar é a linha 17 que contém uma propriedade virtual do tipo PedidoCabecalho para realizar a referência entre as classes e obter o pedido ao qual o item se refere.

Definindo as classes de mapeamento com Fluent API

O próximo passo em nosso projeto será criar as classes que irão mapear as tabelas a serem geradas pelo Entity Framework Code First. Este processo é realizado com a Fluent API, uma biblioteca que faz parte do Entity Framework com função semelhante às Data Annotations, porém mais ampla em recursos.

A Fluent API é utilizada para mapear as entidades que irão compor o banco de dados da aplicação, com ela é possível definir as colunas de uma tabela, chave primária, tamanho máximo de uma coluna, nome, ordem das colunas, colunas anuláveis, propriedades da entidade que não farão parte da tabela, relacionamento entre tabelas, nome de uma tabela, dentre outros recursos. Além disso, o uso dessa API faz com que o domínio fique desacoplado de qualquer tecnologia de acesso a dados, o que não ocorreria se usássemos as Data Annotations nas classes e suas propriedades.

Para cada entidade teremos uma classe de mapeamento, e a primeira a ser criada será a ClienteMap. Adicione essa nova classe dentro do diretório Model e observe, na Listagem 5, como deve ficar seu código. Atente aos namespaces que precisam ser importados no topo da classe para que tenhamos acesso às classes e métodos do mapeamento.


using System;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class ClienteMap : EntityTypeConfiguration<Cliente>
  {
      public ClienteMap()
      {            
          this.HasKey(t => t.ClienteId);
          this.Property(t => t.Nome).HasMaxLength(100);
          this.Property(t => t.CpfCnpj).HasMaxLength(18);
          this.Property(t => t.Endereco).HasMaxLength(200);
          this.Property(t => t.Bairro).HasMaxLength(40);
          this.Property(t => t.Cidade).HasMaxLength(40);
          this.Property(t => t.Cep).HasMaxLength(10);
          this.Property(t => t.Email).HasMaxLength(50);
          this.Property(t => t.Telefone).HasMaxLength(15);
          this.Property(t => t.StatusCliente).HasMaxLength(20);

          this.ToTable("Cliente");
          this.Property(t => t.ClienteId).HasColumnName("ClienteId");
          this.Property(t => t.Nome).HasColumnName("Nome");
          this.Property(t => t.CpfCnpj).HasColumnName("CpfCnpj");
          this.Property(t => t.Endereco).HasColumnName("Endereco");
          this.Property(t => t.Bairro).HasColumnName("Bairro");
          this.Property(t => t.Cidade).HasColumnName("Cidade");
          this.Property(t => t.Cep).HasColumnName("Cep");
          this.Property(t => t.Email).HasColumnName("Email");
          this.Property(t => t.Telefone).HasColumnName("Telefone");
          this.Property(t => t.StatusCliente).HasColumnName("StatusCliente");
          this.Property(t => t.DataCadastro).HasColumnName("DataCadastro");
      }
  }
}
Listagem 5. Implementação da classe ClienteMap

Alguns detalhes com relação a essa classe merecem destaque:

  • Linha 9: Nesta linha consta o primeiro passo para se trabalhar com a Fluent API, ou seja, a classe de mapeamento ClienteMap deve herdar da classe EntityTypeConfiguration, recebendo como tipo genérico a classe que será mapeada. É importante destacar que as classes de mapeamento serão informadas ao criarmos o DbContext mais à frente neste artigo;
  • Linha 11: Aqui é criado o construtor da classe ClienteMap, que, após ser utilizado pelo Entity Framework Code First, no momento da criação da base de dados, irá construir o mapeamento da entidade Cliente;
  • Linha 13: Neste ponto é definida a chave primária da tabela Cliente através do método HasKey da classe EntityTypeConfiguration. Perceba que ele recebe uma expressão lambda “t => t.ClienteId” como parâmetro, onde t representa a classe Cliente e o atributo ClienteId será a chave primária da tabela;
  • Linhas 14 a 22: Neste intervalo é definido o tamanho máximo de cada coluna que irá compor a tabela cliente. Veja que é utilizado o método Property com uma expressão lambda para definir a coluna e em seguida é chamado o método HasMaxLength com o tamanho máximo;
  • Linha 24: Nesta linha é definido o nome da tabela a ser criada com uso do método ToTable. Aqui o nome da tabela não precisa ser o mesmo da classe;
  • Linhas 25 a 25: Neste intervalo é definido o nome das colunas da tabela Cliente através dos métodos Property e HasColumnName. Além disso, o nome da coluna não precisa ser necessariamente o mesmo nome da propriedade.

A próxima classe a ser mapeada será a Produto, assim sendo, crie uma nova classe chamada ProdutoMap e a implemente conforme consta na Listagem 6, que segue o mesmo formato da classe anterior.


using System;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class ProdutoMap : EntityTypeConfiguration<Produto>
  {
      public ProdutoMap()
      {
          this.HasKey(t => t.ProdutoId);

          this.Property(t => t.Descricao).HasMaxLength(100);
          this.Property(t => t.StatusProduto).HasMaxLength(20);

          this.ToTable("Produto");
          this.Property(t => t.ProdutoId).HasColumnName("ProdutoId");
          this.Property(t => t.Descricao).HasColumnName("Descricao");
          this.Property(t => t.PrecoCompra).HasColumnName("PrecoCompra");
          this.Property(t => t.PrecoVenda).HasColumnName("PrecoVenda");
          this.Property(t => t.QtdeEstoque).HasColumnName("QtdeEstoque");
          this.Property(t => t.DataCadastro).HasColumnName("DataCadastro");
          this.Property(t => t.StatusProduto).HasColumnName("StatusProduto");
      }
  }
}
Listagem 6. Implementação da classe ProdutoMap

Nessa listagem utilizamos as mesmas classes e métodos da anterior, alterando apenas os nomes da tabela e colunas. Podemos então criar a classe PedidoCabecalhoMap para fazer o mapeamento da tabela PedidoCabecalho, de acordo com a Listagem 7.


using System;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models

  public class PedidoCabecalhoMap : EntityTypeConfiguration<PedidoCabecalho>
  {
      public PedidoCabecalhoMap()
      {
          this.HasKey(t => t.PedidoCabId);
          this.Property(t => t.StatusPedido).HasMaxLength(20);

          this.ToTable(“PedidoCabecalho”);
          this.Property(t => t.PedidoCabId).HasColumnName(“PedidoCabId”);
          this.Property(t => t.ClienteId).HasColumnName(“ClienteId”);
          this.Property(t => t.ValorTotalPedido).HasColumnName(“ValorTotalPedido”);
          this.Property(t => t.QtdeItens).HasColumnName(“QtdeItens”);
          this.Property(t => t.StatusPedido).HasColumnName(“StatusPedido”);
          this.Property(t => t.DataPedido).HasColumnName(“DataPedido”);

          this.HasRequired(t => t.Cliente)
              .WithMany(t => t.PedidoCabecalho)
              .HasForeignKey(d => d.ClienteId);
      }
  }
}
Listagem 7. Implementação da classe PedidoCabecalhoMap

Essa classe novamente usa as mesmas classes e métodos que já vimos, porém ela possui alguns detalhes que merecem destaque:

  • Linhas 24 a 26: O primeiro ponto a se destacar é o método HasRequired, que informa que um cliente é necessário para o cadastro do pedido. Na sequência é chamado o método WithMany, que informa que há um relacionamento de Um-Para-Muitos entre o cliente e o cabeçalho do pedido. Por fim é chamado o método HasForeignKey que define a chave estrangeira como sendo a primária ClienteId da classe Cliente.

Para finalizar o mapeamento, a última classe a ser implementada é a ItemPedidoMap, cujo código encontra-se na Listagem 8.


using System;
02 using System.Collections.Generic;
03 using System.Data.Entity.ModelConfiguration;
04 using System.Linq;
05 using System.Web;
06
07 namespace Relatorios_ASPNET_MVC.Models
08 {
09    public class ItemPedidoMap : EntityTypeConfiguration<ItemPedido>
10    {
11        public ItemPedidoMap()
12        {
13            this.HasKey(t => t.ItemPedidoId);
14            this.Property(t => t.StatusItem).HasMaxLength(20);
15
16            this.ToTable("ItemPedido");
17            this.Property(t => t.ItemPedidoId).HasColumnName("ItemPedidoId");
18            this.Property(t => t.PedidoCabId).HasColumnName("PedidoCabId");
19            this.Property(t => t.ProdutoId).HasColumnName("ProdutoId");
20            this.Property(t => t.Quantidade).HasColumnName("Quantidade");
21            this.Property(t => t.ValorUnitario).HasColumnName("ValorUnitario");
22            this.Property(t => t.Subtotal).HasColumnName("Subtotal");
23            this.Property(t => t.StatusItem).HasColumnName("StatusItem");
24
25            this.HasRequired(t => t.PedidoCabecalho).WithMany(t => t.ItemPedido)
26                .HasForeignKey(d => d.PedidoCabId);
27
28            this.HasRequired(t => t.Produto).WithMany(t => t.ItemPedido)
29                .HasForeignKey(d => d.ProdutoId);
30        }
31    }
32 }
Listagem 8. Implementação da classe ItemPedidoMap

Observe que no intervalo das linhas 25 a 29 temos as definições dos relacionamentos Um-Par-Muitos através do uso do método WithMany entre as classes ItemPedido, PedidoCabecalho e Produto.

Criando o DbContext para o EF Code First

Para podermos trabalhar de fato com o Entity Framework Code First e gerar o banco de dados, será necessário criar uma classe que herde de DbContext e na qual será configurado o nome do banco de dados a ser gerado, as tabelas que irão compor a base, e por fim determinar que seja usado o mapeamento que realizamos anteriormente com a Fluent API.

Insira então uma nova classe chamada VendaRelatorioDbContext dentro da pasta Model e a implemente conforme a Listagem 9.


using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace Relatorios_ASPNET_MVC.Models
{
  public class VendaRelatorioDbContext: DbContext
  {
      static VendaRelatorioDbContext()
      {
          Database.SetInitializer<VendaRelatorioDbContext>(null);
      }

      public VendaRelatorioDbContext()
          : base("Name=VendaRelatorioDbContext")
      {
      }

      public DbSet<Cliente> Clientes { get; set; }
      public DbSet<ItemPedido> ItemPedidos { get; set; }
      public DbSet<PedidoCabecalho> PedidoCabecalhos { get; set; }
      public DbSet<Produto> Produtos { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
          modelBuilder.Configurations.Add(new ClienteMap());
          modelBuilder.Configurations.Add(new ItemPedidoMap());
          modelBuilder.Configurations.Add(new PedidoCabecalhoMap());
          modelBuilder.Configurations.Add(new ProdutoMap());
      }
  }
}
Listagem 9. Implementação da classe VendaRelatorioDbContext

Essa classe representa o DbContext, classe central utilizada pelo EF Code First para obtermos acesso ao banco de dados, armazenando as coleções de entidades que serão mapeadas. Alguns pontos sobre essa classe merecem destaque:

  • Linha 13: Aqui definimos que a própria classe deve ser utilizada para inicializar o banco de dados;
  • Linha 17: Nesta linha temos que informar o nome da conexão de acesso a dados, conforme consta na seção Connection Strings no arquivo WebConfig, que pode ser vista na Listagem 10. Perceba que o nome da Connection String é o mesmo nome de nosso contexto;
  • Linhas 21 a 24: Neste intervalo devemos informar todas as classes que farão parte do mapeamento objeto-relacional através de uma propriedade do tipo DbSet, que representa uma coleção de objetos com tipagem genérica que serão persistidos e recuperados do banco de dados automaticamente pelo Entity Framework;
  • Linhas 26 a 32: Neste intervalo informamos as classes de mapeamento da Fluent API através de uma instância do DbModelBuilder que é recebida por parâmetro no método sobrescrito OnModelCreating do DbContext. Com isso, quando o modelo for criado, essas classes serão utilizadas para realizar o mapeamento.

<connectionStrings>
    <add name="VendaRelatorioDbContext"
         connectionString="Data Source=.\SQLEXPRESS;Initial   
         Catalog=VendaRelatorioDbContext;Persist Security Info=True;User  
         ID=sa;Password=SuaSenha;MultipleActiveResultSets=True"
         providerName="System.Data.SqlClient" />
</connectionStrings>
Listagem 10. Connection Strings WebConfig

Na Connection String, é importante perceber que o nome do servidor pode variar. Aqui foi utilizada a instância padrão do SQLEXPRESS, mas caso seja necessário, basta indicar no parâmetro Data Source qual servidor deve ser utilizado. Os demais parâmetros, em geral, devem permanecer os mesmos.

Habilitando o Migrations para gerar o banco

Após toda a estrutura necessária para trabalharmos com o Entity Framework Code First ter sido criada, precisaremos agora gerar o banco de dados no SQL Server 2014 Express. Para isso utilizaremos o sistema de versionamento de banco de dados do próprio EF chamado Migrations. Esse recurso permite que façamos alterações nas classes modelo da aplicação e depois atualizemos a base de dados diretamente pelo Visual Studio, sem precisar escrever código SQL para refletir essas mudanças.

Para utilizamos o Migrations, primeiramente é necessário habilitar esse recurso no projeto, para isso digite o seguinte comando no Package Manager Console: Enable-Migrations.

Caso tenha corrido tudo bem na instalação, será apresentada uma mensagem informando que o Migrations foi habilitado no projeto. Além disso, será criada uma nova pasta no projeto chamada Migrations e uma classe chamada Configuration, onde podemos realizar algumas configurações sobre as alterações a serem realizadas no banco. Nessa classe devemos definir como true a propriedade AutomaticMigrationsEnabled, localizado dentro do construtor, para que o próprio Migrations fique responsável por atualizar a base de dados sempre que realizarmos uma alteração em nosso modelo.

Para finalizar e gerar o banco de dados, digite o seguinte comando no Package Manager Console: Update-Database. Isso fará com que, baseado no modelo de classes que construímos, o banco de dados seja gerado.

Agora podemos abrir o SQL Server Management Studio e verificar se o banco de dados foi criado, conforme mostra a Figura 3.

Como gerar Relatórios no ASP.NET MVC
Figura 3. Banco de dados gerado com o Migrations

Relatório de listagem de Clientes

Para de fato iniciarmos o desenvolvimento do primeiro relatório, precisaremos primeiro instalar dois plugins através do Nuget. O primeiro é o PagedList, que nos dará a possibilidade de paginação e totalização do relatório. O segundo plugin é o Rotativa, que converte HTML e CSS para PDF usando o motor WebKit.

Abra o Package Manager Console e instale os dois plug-ins utilizando os seguintes comandos:


Install-Package PagedList.MVC 

Install-Package Rotativa

Após a execução desses comandos teremos as referências adicionadas na pasta References. Agora vamos criar o controller que será responsável por conter todas as actions referentes aos relatórios de nosso projeto. Para isso, clique com botão direito na pasta Controller > Add > Controller e selecione a opção “MVC 5 Controller – Empty”. Clique em Add e nomeie o controller como Relatorios.

Por padrão esse controller será criado apenas com uma action Index, porém iremos realizar algumas modificações e implementar a action ListagemClientes, que tratará de gerar nosso primeiro relatório. Implemente o controller Relatorios conforme o código presente na Listagem 11.


using Relatorios_ASPNET_MVC.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PagedList;
using Rotativa;
using Rotativa.Options;

namespace Relatorios_ASPNET_MVC.Controllers
{
  public class RelatoriosController : Controller
  {
      private VendaRelatorioDbContext db = new VendaRelatorioDbContext();
      public ActionResult ListagemClientes(int? pagina, Boolean? gerarPDF)
      {
          var listagemClientes = db.Clientes.OrderBy(n => n.ClienteId)
          .ToList<Cliente>();

          if (gerarPDF != true)
          {
              //Definindo a paginação              
              int paginaQdteRegistros = 10;
              int paginaNumeroNavegacao = (pagina ?? 1);

              return View(listagemClientes.ToPagedList(paginaNumeroNavegacao, 
              paginaQdteRegistros));
          }
          else
          {
              int paginaNumero = 1;
              
              var pdf = new ViewAsPdf
              {
                  ViewName = "ListagemClientes",
                  PageSize = Size.A4,
                  IsGrayScale = true,
                  Model = listagemClientes.ToPagedList(paginaNumero, listagemClientes.Count)
              };
              return pdf;
          }
      }
  }
}
Listagem 11. Implementação do controller Relatorios para a Listagem de Clientes

Observe alguns detalhes importantes sobre esse controller:

  • Linhas 7 a 9: Adicionamos as referências necessárias para usar as bibliotecas do PagedList e Rotativa;
  • Linha 15: aqui declaramos um objeto do tipo VendaRelatorioDbContext, que nos permitirá acessar os dados da aplicação;
  • Linha 16: Aqui é definida a action ListagemClientes, que recebe como parâmetro o número da página a ser navegada para utilizarmos na paginação e outro parâmetro chamado gerarPDF, que informa através de um Boolean se o Rotativa deve gerar o relatório em PDF ou o mesmo deve apenas ser renderizado normalmente pelo browser e ser paginado com PagedList;
  • Linha 18: Realizamos uma consulta com o EF para obter a listagem de clientes e compor o relatório, armazenando o resultado em uma variável chamada listagemClientes;
  • Linhas 20 a 27: Neste primeiro bloco temos uma estrutura condicional que verifica se a variável gerarPDF é diferente de true, caso seja, então definimos a quantidade de registros a serem exibidos por página com PagedList, verificando qual página deve ser retornada. Por fim é retornada a listagem utilizando o método ToPagedList, passando o número da página de navegação e a quantidade a ser exibida por página;
  • Linhas 28 a 40: Este bloco será executado caso a variável gerarPDF seja true, assim definimos o valor da variável paginaNumero como 1 para o PagedList gerar apenas uma página HTML. Na sequência criamos uma variável chamada pdf, que recebe uma instância da classe ViewAsPdf referente ao Rotativa para gerarmos o relatório em PDF. Veja que no ViewAsPdf são definidos alguns atributos referentes ao nome da view que irá renderizar o relatório, o tamanho da página do PDF, escala e Model, que recebe a listagem de clientes através do PagedList.

Com essa action pronta para retornar a listagem de clientes, agora precisamos criar a view que de fato irá renderizar a página HTML com alguns recursos do Bootstrap. Para criar a view, clique com o botão direito sobre a action e depois em Add View. Em seguida será apresentada uma nova janela, conforme a Figura 4, onde você deve clicar em Add para finalizar e o Visual Studio criará o arquivo ListagemClientes.cshtml dentro da pasta Views/Relatorios.

Como gerar Relatórios no ASP.NET MVC
Figura 4. Criando a view ListagemClientes

Implemente então a view ListagemClientes.cshtml conforme o código da Listagem 12, onde usamos o Bootstrap para montar nossa interface.


@model PagedList.IPagedList<Relatorios_ASPNET_MVC.Models.Cliente>

@{ ViewBag.Title = "ListagemClientes";
Layout = "~/Views/Shared/_Layout.cshtml"; }

<div class="panel panel-default">
  <div class="panel-heading"><h5>Relatório Listagem de Clientes</h5></div>
  <div class="panel-body">
      <div class="row">
          <div class="col-md-12">
            <table class="table">
              <tr>
                  <th>ID </th><th>Nome </th><th>CPF/CNPJ 
                  </th><th>Endereço </th><th>Bairro </th>
                  <th>Cidade </th>
                  <th>CEP </th><th>Email </th><th>Telefone 
                  </th><th>Status </th><th>Data cadastro </th>
              </tr>
              @foreach (var item in Model)
              {
                  <tr>
                      <td>@Html.DisplayFor(modelItem => item.ClienteId)</td>
                      <td>@Html.DisplayFor(modelItem => item.Nome)</td>
                      <td>@Html.DisplayFor(modelItem => item.CpfCnpj)</td>
                      <td>@Html.DisplayFor(modelItem => item.Endereco)</td>
                      <td>@Html.DisplayFor(modelItem => item.Bairro)</td>
                      <td>@Html.DisplayFor(modelItem => item.Cidade)</td>
                      <td>@Html.DisplayFor(modelItem => item.Cep)</td>
                      <td>@Html.DisplayFor(modelItem => item.Email)</td>
                      <td>@Html.DisplayFor(modelItem => item.Telefone)</td>
                      <td>@Html.DisplayFor(modelItem => item.StatusCliente)</td>
                      <td>@Html.DisplayFor(modelItem => item.DataCadastro)</td>
                  </tr> }
              <tr>
                  <td><b>@Model.Count registos de @Model.TotalItemCount</b>
                  </td>
                    <td><a href="/Relatorios/ListagemClientes?gerarPDF=true">
                    <b>GERAR PDF</b></a></td>
              </tr>
          </table>
      </div>
  </div>
  @{
      if (Model.TotalItemCount != Model.Count)
      {
          <div class="row">
              <div class="col-md-12">
                  Página @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) de 
                  @Model.PageCount

                  @if (Model.HasPreviousPage)
                  {
                      @Html.ActionLink("<<", "ListagemClientes", new { pagina = 1, 
                      sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter })
                      @Html.Raw(" ");
                      @Html.ActionLink("< Anterior", "ListagemClientes", new { pagina = 
                      Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter = 
                      ViewBag.CurrentFilter })
                  }
                  else
                  {
                      @:<<
                      @Html.Raw(" ");
                      @:< Anterior
                     }

                  @if (Model.HasNextPage)
                  {
                      @Html.ActionLink("Próxima >", "ListagemClientes", new { pagina = 
                      Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter = 
                      ViewBag.CurrentFilter })
                      @Html.Raw(" ");
                      @Html.ActionLink(">>", "ListagemClientes", new { pagina = 
                      Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter = 
                      ViewBag.CurrentFilter })
                  }
                  else
                  {
                      @:Próxima >
                      @Html.Raw(" ")
                      @:>>
              }
              </div>
          </div>
      }
    }
  </div>
</div>
Listagem 12. Implementação da view ListagemClientes.cshtml

Com relação a essa view, temos de observar alguns pontos importantes:

  • Linha 1: Aqui é informado o model que irá representar a listagem de clientes na view, perceba que o mesmo é gerado a partir da interface IPagedList;
  • Linhas 6 a 10: Neste intervalo é definido um painel para agrupar os campos do relatório através da classe panel e sistema de grids do Bootstrap;
  • Linhas 11 a 15: Aqui é criada uma tabela utilizando a classe table do Bootstrap e em seguida é definido o cabeçalho da tabela;
  • Linhas 16 a 30: Neste bloco é utilizado o foreach para iterar sobre os itens do model e exibir a listagem de clientes, criando uma nova linha (tr) na tabela para cada registro na coleção;
  • Linhas 32 e 33: Primeiramente são utilizadas as propriedades Count e TotalItemCount que o PagedList fornece para obtermos a quantidade de registros da página e o total da listagem. Na sequência é criado um link para a action ListagemClientes, passando como parâmetro o gerarPDF como true para que o Rotativa possa gerar o relatório em PDF;
  • Linhas 39 a 69: Neste bloco verificamos se o relatório deve ser paginado com uso de alguns atributos do PagedList, ou se o mesmo será gerado com o Rotativa e não precisará da paginação. Em seguida, caso seja realmente necessário, será implementada a paginação do relatório na página web com recursos do PagedList.

Para testar o relatório, insira alguns dados na tabela Cliente no banco de dados e execute o projeto para visualizar os dados com paginação e, logo depois, clique no link em GERAR PDF para o Rotativa gerar o mesmo e possibilitar os recursos de impressão e gravação em disco. Veja as Figuras 5 e 6 que correspondem ao relatório em execução.

Como gerar Relatórios no ASP.NET MVC
Figura 5. Relatório com paginação do PagedList
Como gerar Relatórios no ASP.NET MVC
Figura 6. Relatório gerado em PDF com o Rotativa

Relatório com agrupamento, subtotais e passagem de parâmetro

Criaremos agora um relatório um pouco mais complexo, para exibir as vendas agrupando-as por pedido, informando o cliente ao qual a venda pertence e quais itens compõem o pedido. É importante frisar que o relatório deve ter subtotal nas quantidades vendidas e valores referentes aos itens de cada pedido de venda.

O primeiro passo para o desenvolvimento do relatório será criar um formulário para que o usuário possa informar o intervalo de datas referente ao período que deseja exibir no mesmo. Então, no controller Relatorios inclua mais uma action chamada FormVendasPorPedido, conforme mostra a Listagem 13.


public ActionResult FormVendasPorPedido()
{
  return View();
}
Listagem 13. Action FormVendasPorPedido

Repita o processo feito anteriormente para adicionar uma view referente a essa action e altere sua implementação de acordo com a Listagem 14.


@model Relatorios_ASPNET_MVC.Models.PedidoCabecalho

@{
  ViewBag.Title = "FormVendasPorPedido";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="panel panel-default">
  <div class="panel-heading">
      <h2>Relatório Venda por Pedido/Período</h2>
  </div>
  <div class="panel-body">
      <form method="post" action="/Relatorios/RelVendasPorPeriodo" 
      id="frmVenda" class="form-horizontal">

          <div class="form-group">
              <label for="dataInicial" class="col-sm-2 control-label">Data Inicial:
              </label>
              <div class="col-sm-10">
                  <input type="date" class="form-control" id="dataInicial" 
                  name="dataInicial" required>
              </div>
          </div>
          <div class="form-group">
              <label for="dataFinal" class="col-sm-2 control-label">Data Final
              </label>
                <div class="col-sm-10">
                    <input type="date" class="form-control" id="dataFinal" 
                    name="dataFinal" required>
              </div>
          </div>

          <div class="form-group">
              <div class="col-sm-offset-2 col-sm-10">
                  <button type="submit" class="btn btn-default"
                  >Gerar Relatório</button>
              </div>
          </div>
      </form>
  </div>
</div>
Listagem 14. Implementação da view FormVendasPorPedido.cshtml

Aqui novamente utilizamos os recursos do Bootstrap para otimizar a interface, mas vale ainda destacar alguns pontos.

  • Linha 13: Nesta linha é criado um form HTML que realiza uma requisição via método POST à action RelVendasPorPeriodo do controller Relatorios, que ainda será criada. Esta nova action será responsável por buscar os dados no banco de dados e retornar para a view que renderiza o relatório;
  • Linha 15 a 32: Neste bloco foram criados os inputs para o usuário informar a data inicial e final do período de vendas.

Se executarmos o projeto neste momento iremos visualizar a página que é ilustrada na Figura 7, porém não podemos ainda submeter o formulário, pois iremos criar a action e view que irão de fato gerar o relatório.

Como gerar Relatórios no ASP.NET MVC
Figura 7. Formulário para definição do período para geração do relatório

Após ter sido desenvolvida a página para que o usuário possa informar o intervalo de datas, agora precisamos criar a action que irá receber esses parâmetros e realizar a consulta no banco de dados. Assim, insira no controller Relatorios o código da Listagem 15, onde temos a nova action RelVendasPorPeriodo.


public ActionResult RelVendasPorPeriodo(DateTime dataInicial, 
DateTime dataFinal, int? pagina, Boolean? gerarPDF)
{
  var vendas = db.PedidoCabecalhos.Where(i => i.DataPedido >= dataInicial && 
  i.DataPedido <= dataFinal).OrderBy(p=>p.PedidoCabId).ToList<PedidoCabecalho>();

  ViewBag.dataInicial = dataInicial;
  ViewBag.dataFinal = dataFinal;

  if (gerarPDF != true)
  {
//Definindo a paginação      
int paginaQdteRegistros = 2;
int paginaNumeroNavegacao = (pagina ?? 1);

return View(vendas.ToPagedList(paginaNumeroNavegacao, paginaQdteRegistros));
  }
  else
  {
int paginaNumero = 1;

var pdf = new ViewAsPdf
{
  ViewName = "RelVendasPorPeriodo",
  PageSize = Size.A4,
  IsGrayScale = true,
  Model = vendas.ToPagedList(paginaNumero, vendas.Count),    
};
return pdf;
  }
}
Listagem 15. Implementação da action RelVendasPorPeriodo

Vejamos alguns detalhes sobre a implementação dessa listagem.

  • Linha 1: Aqui temos a definição da action RelVendasPorPeriodo, que recebe como parâmetro a dataInicial e dataFinal;
  • Linha 3: Aqui é realizada a consulta ao banco de dados, já filtrando o relatório referente ao intervalo das vendas, através do método Where que recebe uma expressão lambda como argumento;
  • Linha 5 e 6: Aqui foram criadas duas propriedades na ViewBag para transferirmos para a view as datas de filtragem dos pedidos.

Nas linhas 8 a 28 basicamente repetimos o procedimento realizado no relatório anterior, verificando se deve ser gerado o PDF, ou se devemos retornar a view convencional com os dados a serem exibidos em tela. O último passo para finalizar este relatório será criar a view correspondente ao mesmo, cujo código encontra-se nas Listagens 16 e 17.


@model PagedList.IPagedList<Relatorios_ASPNET_MVC.Models.PedidoCabecalho>

@{ ViewBag.Title = "Relatório de Vendas";
Layout = "~/Views/Shared/_Layout.cshtml"; }

<div class="panel panel-default">
<div class="panel-heading"><h4><b>Relatório Venda 
por Pedido/Período De: @ViewBag.dataInicial á @ViewBag.dataFinal</b>
</h4></div>
<div class="panel-body">
<div class="row">
  <div class="col-md-12">
    <table class="table">
      <tr>
          <th>Pedido </th>
          <th>Item </th>
          <th>Descrição </th>
          <th>Quantidade </th>
          <th>Valor Unitário </th>
          <th>Sub Total </th>
          <th>Status Item </th>
          <th>Data Venda</th>
      </tr>
      @{
          foreach (var item in Model)
          {
            foreach (var itens in item.ItemPedido)
            {
              <tr>
                <td>@Html.DisplayFor(modelItem => 
                itens.PedidoCabecalho.PedidoCabId)</td>
                <td>@Html.DisplayFor(modelItem => itens.ItemPedidoId)</td>
                <td>@Html.DisplayFor(modelItem => itens.Produto.Descricao)</td>
                <td>@Html.DisplayFor(modelItem => itens.Quantidade)</td>
                <td>@Html.DisplayFor(modelItem => itens.ValorUnitario)</td>
                <td>@Html.DisplayFor(modelItem => itens.Subtotal)</td>
                <td>@Html.DisplayFor(modelItem => itens.StatusItem)</td>
                <td>@Html.DisplayFor(modelItem => itens.PedidoCabecalho.DataPedido)
                </td>
              </tr>
            }
            <tr>
              <th>Cliente</th><th>@item.Cliente.Nome</th>
              <th></th><th>@item.QtdeItens</th>
              <th></th><th>@item.ValorTotalPedido</th>
              <th></th><th></th>
            </tr>
          }
     }
      <tr>
          <th></th>
          <th></th>
          <th>Total Itens:</th>
          <th>@Model.Sum(t => t.QtdeItens)</th>
          <th>Total Geral:</th>
          <th>@Model.Sum(t => t.ValorTotalPedido)</th>
          <th></th>
          <th></th>
      </tr>
      <tr>
          <td><b>@Model.Count registos de @Model.TotalItemCount</b></td>
          <td>@Html.ActionLink("GERAR PDF", "RelVendasPorPeriodo", 
          new { gerarPDF = true, dataInicial = ViewBag.dataInicial, 
          dataFinal = ViewBag.dataFinal })</td>
      </tr>
    </table>
  </div>
</div>
Listagem 16. Implementação da view RelVendasPorPeriodo.cshtml

Vejamos a seguir alguns detalhes importantes sobre esse trecho da view.

  • Linhas 11 a 21:Neste bloco é criada uma tabela para exibir os dados e informado o cabeçalho de cada coluna do relatório;
  • Linhas 23 a 55: Neste intervalo temos dois foreach aninhados para que possamos exibir o subtotal da venda e itens do pedido;
  • Linhas 48 e 59: Aqui são calculados os totais e exibidos no rodapé da tabela;
  • Linha 56: Geramos o link que irá redirecionar para a mesma action, porém passando o argumento gerarPDF como true, para ativar a funcionalidade do plug-in Rotativa.

@{
  if (Model.TotalItemCount != Model.Count)
  {
    <div class="row">
      <div class="col-md-12">
        Página @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) 
        de @Model.PageCount

        @if (Model.HasPreviousPage)
        {
          @Html.ActionLink("<<", "RelVendasPorPeriodo", 
          new { pagina = 1, sortOrder = ViewBag.CurrentSort, currentFilter = 
          ViewBag.CurrentFilter, dataInicial = ViewBag.dataInicial, dataFinal = 
          @ViewBag.dataFinal })
          @Html.Raw(" ");
          @Html.ActionLink("< Anterior", "RelVendasPorPeriodo", new { pagina = 
          Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter = 
          ViewBag.CurrentFilter, dataInicial = ViewBag.dataInicial, dataFinal = 
          ViewBag.dataFinal })
        }
        else
        {
          @:<<
          @Html.Raw(" ");
          @:< Anterior
        }

        @if (Model.HasNextPage)
        {
          @Html.ActionLink("Próxima >", "RelVendasPorPeriodo", 
          new { pagina = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, 
          currentFilter = ViewBag.CurrentFilter, dataInicial = ViewBag.dataInicial, 
          dataFinal = ViewBag.dataFinal })
          @Html.Raw(" ");
          @Html.ActionLink(">>", "RelVendasPorPeriodo", 
          new { pagina = Model.PageCount, sortOrder = ViewBag.CurrentSort, 
          currentFilter = ViewBag.CurrentFilter, dataInicial = ViewBag.dataInicial, 
          dataFinal = ViewBag.dataFinal })
        }
        else
        {
          @:Próxima >
          @Html.Raw(" ")
          @:>>
      }
      </div>
    </div>
  }
}
</div>
</div>
Listagem 17. Implementação da paginação na view RelVendasPorPeriodo.cshtml

Nessa listagem estamos implementando a funcionalidade de paginação da tabela, adicionando links dinamicamente para avançar entre os registros de acordo com a quantidade total de linhas e a quantidade a ser exibida por página. Nos blocos que se iniciam nas linhas 8 e 21, respectivamente, utilizamos os recursos oferecidos pelo PagedList para verificar se devemos permitir ao usuário navegar para a página anterior e para a próxima.

Ao executar o projeto, iremos direto para a página do formulário de datas do período de vendas, e logo após informá-las, devemos clicar no botão 'Gerar Relatório' para que o sistema possa apresentar o mesmo, como na Figura 8.

Como gerar Relatórios no ASP.NET MVC
Figura 8. Relatório de vendas

Clicando no botão 'GERAR PDF' novamente, teremos o resultado visto na Figura 9.

Como gerar Relatórios no ASP.NET MVC
Figura 9. Relatório de vendas em PDF

A criação de relatórios no ASP.NET MVC é facilitada pelo fato de trabalharmos com as próprias views do sistema, gerando-as em HTML e podendo utilizar recursos como o Bootstrap para oferecer a melhor visualização em resoluções de telas distintas, além de garantir a compatibilidade com os principais browsers modernos.

Neste artigo não foram explorados todos os recursos disponíveis nos plug-ins PagedList e Rotativa, ficando como sugestão para o leitor buscar maiores detalhes sobre o que é possível fazer com essas ferramentas na página oficial de cada uma delas, disponível na seção Links.