Отображение таблицы с помощью Entity Framework

Существует ряд задач, когда по какой-то причине удобнее экспортировать данные из баз данных в PDF-документ без использования недавно популярной схемы конвертации HTML в PDF.

Эта статья покажет вам, как создать PDF-документ с использованием Aspose.PDF for .NET.

Основы генерации PDF с Aspose.PDF

Одним из самых важных классов в Aspose.PDF является класс Document. Этот класс является движком рендеринга PDF. Для представления структуры PDF библиотека Aspose.PDF использует модель Document-Page, где:

  • Document - содержит свойства PDF-документа, включая коллекцию страниц.
  • Page - содержит свойства конкретной страницы и различные коллекции элементов, связанных с этой страницей.

Поэтому, чтобы создать PDF-документ с Aspose.PDF, вам следует выполнить следующие шаги:

  1. Создайте объект Document.
  2. Добавьте страницу (объект Page) для объекта Document.
  3. Создайте объекты, которые будут размещены на странице (например, текстовый фрагмент, таблица и т.д.).
  4. Добавьте созданные элементы в соответствующую коллекцию на странице (в нашем случае это будет коллекция абзацев).
  5. Сохраните документ в виде PDF-файла.

Наиболее распространенной проблемой является вывод данных в табличном формате. Для обработки таблиц используется класс Table. Этот класс дает нам возможность создавать таблицы и размещать их в документе, используя Rows и Cells. Таким образом, чтобы создать таблицу, вам нужно добавить необходимое количество строк и заполнить их соответствующим количеством ячеек.

Следующий пример создает таблицу 4x10.

При инициализации объекта Table использовались минимальные настройки оформления:

  • ColumnWidths - ширина столбцов (по умолчанию).
  • DefaultCellPadding - отступы по умолчанию для ячейки таблицы.
  • Border - атрибуты рамки таблицы (стиль, толщина, цвет).
  • DefaultCellBorder - атрибуты рамки ячейки (стиль, толщина, цвет).

В результате мы получаем таблицу 4x10 с равными по ширине столбцами.

Таблица 4x10

Экспорт данных из объектов ADO.NET

Класс Table предоставляет методы для взаимодействия с источниками данных ADO.NET - ImportDataTable и ImportDataView. Первый метод импортирует данные из DataTable, второй - из DataView. Предполагая, что эти объекты не очень удобны для работы в шаблоне MVC, мы ограничимся кратким примером. В этом примере (строка 50) вызывается метод ImportDataTable и получает в качестве параметров экземпляр DataTable и дополнительные настройки, такие как флаг заголовка и начальная позиция (строки/столбцы) для вывода данных.

Экспорт данных из Entity Framework

Более актуальным для современного .NET является импорт данных из ORM-фреймворков. В этом случае разумно расширить класс Table методами расширения для импорта данных из простого списка или из сгруппированных данных. Приведем пример для одного из самых популярных ORM - 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());
                    }
                }
            }
        }
    }
}

Атрибуты Data Annotations часто используются для описания моделей и помогают нам создавать таблицу. Поэтому для метода ImportEntityList был выбран следующий алгоритм генерации таблицы:

  • строки 12-18: построение строки заголовка и добавление ячеек заголовка в соответствии с правилом “Если присутствует DisplayAttribute, то берем его значение, иначе берем имя свойства”.
  • строки 50-53: построение строк данных и добавление ячеек строки в соответствии с правилом “Если определен атрибут DataTypeAttribute, то проверяем, нужно ли сделать дополнительные настройки оформления для него, а иначе просто конвертируем данные в строку и добавляем в ячейку;”.

В этом примере были сделаны дополнительные настройки для DataType.Currency (строки 32-34) и DataType.Date (строки 35-43), но вы можете добавить другие при необходимости. Алгоритм метода ImportGroupedData почти такой же, как и предыдущий. Используется дополнительный класс GroupViewModel для хранения сгруппированных данных.

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

Поскольку мы обрабатываем группы, сначала мы генерируем строку для ключевого значения (строки 66-71), а затем - строки этой группы.