Renderizar tabela com Entity Framework

Existem várias tarefas em que, por algum motivo, é mais conveniente exportar dados de bancos de dados para um documento PDF sem usar o esquema de conversão de HTML para PDF, que se tornou popular recentemente.

Este artigo mostrará como gerar um documento PDF usando o Aspose.PDF for .NET.

Fundamentos da geração de PDF com Aspose.PDF

Uma das classes mais importantes no Aspose.PDF é a classe Document. Esta classe é um mecanismo de renderização de PDF. Para apresentar uma estrutura PDF, a biblioteca Aspose.PDF utiliza o modelo Documento-Página, onde:

  • Documento - contém as propriedades do documento PDF, incluindo a coleção de páginas.
  • Página - contém as propriedades de uma página específica e várias coleções de elementos associados a essa página.

Portanto, para criar um documento PDF com Aspose.PDF, você deve seguir estas etapas:

  1. Criar o objeto Document.
  2. Adicionar a página (o objeto Page) para o objeto Document.
  3. Criar objetos que serão colocados na página (por exemplo, fragmento de texto, tabela, etc.).
  4. Adicionar os itens criados à coleção correspondente na página (neste caso, será uma coleção de parágrafos).
  5. Salvar o documento como arquivo PDF.

O problema mais comum é a saída de dados em formato de tabela. A classe Table é usada para processar tabelas. Esta classe nos dá a capacidade de criar tabelas e colocá-las no documento, usando Rows e Cells. Assim, para criar a tabela, você precisa adicionar o número necessário de linhas e preenchê-las com o número apropriado de células.

O seguinte exemplo cria a tabela 4x10.

Ao inicializar o objeto Table, foram usadas as configurações mínimas de aparência:

Como resultado, obtemos a tabela 4x10 com colunas de largura igual.

Tabela 4x10

Exportando Dados de Objetos ADO.NET

A classe Table fornece métodos para interagir com fontes de dados ADO.NET - ImportDataTable e ImportDataView. O primeiro método importa dados do DataTable, o segundo do DataView. Premetendo que esses objetos não são muito convenientes para trabalhar no modelo MVC, nos limitaremos a um breve exemplo. Neste exemplo (linha 50), o método ImportDataTable é chamado e recebe como parâmetros uma instância de DataTable e configurações adicionais, como a flag de cabeçalho e a posição inicial (linhas/colunas) para a saída de dados.

Exportando Dados do Entity Framework

Mais relevante para o .NET moderno é a importação de dados de frameworks ORM. Neste caso, é uma boa ideia estender a classe Table com métodos de extensão para importar dados de uma lista simples ou de dados agrupados. Vamos dar um exemplo para um dos ORMs mais populares - Entity Framework.

public static class PdfHelper
{
    private static void ImportEntityList<TSource>(this Aspose.Pdf.Table table, IList<TSource> data)
    {
        var headRow = table.Rows.Add();

        var props = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            headRow.Cells.Add(prop.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute dd ? dd.Name : prop.Name);
        }

        foreach (var item in data)
        {
            // Add row to table
            var row = table.Rows.Add();
            // Add table cells
            foreach (var t in props)
            {
                var dataItem = t.GetValue(item, null);
                if (t.GetCustomAttribute(typeof(DataTypeAttribute)) is DataTypeAttribute dataType)
                    switch (dataType.DataType)
                    {

                        case DataType.Currency:
                            row.Cells.Add(string.Format("{0:C}", dataItem));
                            break;
                        case DataType.Date:
                            var dateTime = (DateTime)dataItem;
                            if (t.GetCustomAttribute(typeof(DisplayFormatAttribute)) is DisplayFormatAttribute df)
                            {
                                row.Cells.Add(string.IsNullOrEmpty(df.DataFormatString)
                                    ? dateTime.ToShortDateString()
                                    : string.Format(df.DataFormatString, dateTime));
                            }
                            break;
                        default:
                            row.Cells.Add(dataItem.ToString());
                            break;
                    }
                else
                {
                    row.Cells.Add(dataItem.ToString());
                }
            }
        }
    }

    private static void ImportGroupedData<TKey, TValue>(this Aspose.Pdf.Table table, IEnumerable<Models.GroupViewModel<TKey, TValue>> groupedData)
    {
        var headRow = table.Rows.Add();
        var props = typeof(TValue).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            headRow.Cells.Add(prop.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute dd ? dd.Name : prop.Name);
        }

        foreach (var group in groupedData)
        {
            // Add group row to table
            var row = table.Rows.Add();
            var cell = row.Cells.Add(group.Key.ToString());
            cell.ColSpan = props.Length;
            cell.BackgroundColor = Aspose.Pdf.Color.DarkGray;
            cell.DefaultCellTextState.ForegroundColor = Aspose.Pdf.Color.White;

            foreach (var item in group.Values)
            {
                // Add data row to table
                var dataRow = table.Rows.Add();
                // Add cells
                foreach (var t in props)
                {
                    var dataItem = t.GetValue(item, null);

                    if (t.GetCustomAttribute(typeof(DataTypeAttribute)) is DataTypeAttribute dataType)
                        switch (dataType.DataType)
                        {
                            case DataType.Currency:
                                dataRow.Cells.Add(string.Format("{0:C}", dataItem));
                                break;
                            case DataType.Date:
                                var dateTime = (DateTime)dataItem;
                                if (t.GetCustomAttribute(typeof(DisplayFormatAttribute)) is DisplayFormatAttribute df)
                                {
                                    dataRow.Cells.Add(string.IsNullOrEmpty(df.DataFormatString)
                                        ? dateTime.ToShortDateString()
                                        : string.Format(df.DataFormatString, dateTime));
                                }
                                break;
                            default:
                                dataRow.Cells.Add(dataItem.ToString());
                                break;
                        }
                    else
                    {
                        dataRow.Cells.Add(dataItem.ToString());
                    }
                }
            }
        }
    }
}

Os atributos de Anotações de Dados são frequentemente usados para descrever modelos e nos ajudam a criar a tabela. Portanto, o seguinte algoritmo de geração de tabela foi escolhido para ImportEntityList:

  • linhas 12-18: construir uma linha de cabeçalho e adicionar células de cabeçalho de acordo com a regra “Se o DisplayAttribute estiver presente, então pegue seu valor, caso contrário, pegue o nome da propriedade”.
  • linhas 50-53: construir as linhas de dados e adicionar células de linha de acordo com a regra “Se o atributo DataTypeAttribute estiver definido, então verificamos se precisamos fazer configurações de design adicionais para ele, e caso contrário, apenas convertemos os dados para string e adicionamos à célula;”.

Neste exemplo, personalizações adicionais foram feitas para DataType.Currency (linhas 32-34) e DataType.Date (linhas 35-43), mas você pode adicionar outras se necessário. O algoritmo do método ImportGroupedData é quase o mesmo que o anterior. Uma classe adicional GroupViewModel é usada para armazenar os dados agrupados.

using System.Collections.Generic;
public class GroupViewModel<K,T>
{
    public K Key;
    public IEnumerable<T> Values;
}

Como processamos grupos, primeiro geramos uma linha para o valor da chave (linhas 66-71), e depois - as linhas deste grupo.