Отображение таблицы с использованием Entity Framework
Есть ряд задач, когда по каким-то причинам удобнее экспортировать данные из баз данных в документ PDF, не используя недавно популярную схему конвертации HTML в PDF.
В этой статье показано, как генерировать документ PDF с использованием Aspose.PDF для .NET.
Основы создания PDF с Aspose.PDF
Одним из самых важных классов в Aspose.PDF является класс Document. Этот класс является движком для рендеринга PDF. Для представления структуры PDF библиотека Aspose.PDF использует модель Документ-Страница, где:
- Document - содержит свойства документа PDF, включая коллекцию страниц;
- Документ - содержит свойства PDF-документа, включая коллекцию страниц;
- Страница - содержит свойства конкретной страницы и различные коллекции элементов, связанных с этой страницей.
Следовательно, чтобы создать PDF-документ с помощью Aspose.PDF, вам следует выполнить следующие шаги:
- Создать объект Document;
- Добавить страницу (объект Page) в объект Document;
- Создать объекты, которые размещаются на странице (например, текстовый фрагмент, таблица и т.д.)
- Добавить созданные элементы в соответствующую коллекцию на странице (в нашем случае это будет коллекция параграфов);
- Сохранить документ в формате PDF.
// Шаг 1
var document = new Document
{
PageInfo = new PageInfo { Margin = new MarginInfo(28, 28, 28, 42) }
};
// Шаг 2
var pdfPage = document.Pages.Add();
// Шаг 3
var textFragment = new TextFragment(reportTitle);
// ..........................................
var table = new Table
{
// .................................
};
// Шаг 4
pdfPage.Paragraphs.Add(textFragment);
pdfPage.Paragraphs.Add(table);
// Шаг 5
using (var streamOut = new MemoryStream())
{
document.Save(streamOut);
return new FileContentResult(streamOut.ToArray(), "application/pdf")
{
FileDownloadName = "tenants.pdf"
};
}
Самая распространенная проблема - это вывод данных в табличной форме. Класс Table используется для обработки таблиц. Этот класс дает нам возможность создавать таблицы и размещать их в документе, используя Строки и Ячейки. Таким образом, чтобы создать таблицу, вам нужно добавить необходимое количество строк и заполнить их соответствующим количеством ячеек.
Следующий пример создает таблицу 4x10.
var table = new Table
{
// Установка автоматической ширины столбцов таблицы
ColumnWidths = "25% 25% 25% 25%",
// Установка отступов ячейки
DefaultCellPadding = new MarginInfo(10, 5, 10, 5), // Слева Снизу Справа Сверху
// Установка цвета границы таблицы в Зеленый
Border = new BorderInfo(BorderSide.All, .5f, Color.Green),
// Установка границ для ячеек таблицы в Черный
DefaultCellBorder = new BorderInfo(BorderSide.All, .2f, Color.Green),
};
for (var rowCount = 0; rowCount < 10; rowCount++)
{
// Добавление строки в таблицу
var row = table.Rows.Add();
// Добавление ячеек в таблицу
for (int i = 0; i < 4; i++)
{
row.Cells.Add($"Cell ({i+1}, {rowCount +1})");
}
}
// Добавление объекта таблицы на первую страницу входного документа
document.Pages[1].Paragraphs.Add(table);
При инициализации объекта Table были использованы минимальные настройки внешнего вида:
- ColumnWidths - ширина столбцов (по умолчанию);
- DefaultCellPadding - стандартные поля ячейки таблицы;
- Border - атрибуты рамки таблицы (стиль, толщина, цвет);
- DefaultCellBorder - атрибуты рамки ячейки (стиль, толщина, цвет).
В результате получаем таблицу 4x10 с равной шириной столбцов.
Экспорт данных из объектов ADO.NET
Класс Table предоставляет методы для взаимодействия с источниками данных ADO.NET - ImportDataTable и ImportDataView. Класс Table предоставляет методы для взаимодействия с источниками данных ADO.NET - ImportDataTable и ImportDataView. Учитывая, что эти объекты не очень удобны для работы в шаблоне MVC, мы ограничимся кратким примером. В этом примере (строка 50) вызывается метод ImportDataTable и получает в качестве параметров экземпляр DataTable и дополнительные настройки, такие как флаг заголовка и начальная позиция (строки/столбцы) для вывода данных.
// Создать новый документ PDF
var document = new Document
{
PageInfo = new PageInfo { Margin = new MarginInfo(28, 28, 28, 42) }
};
var pdfPage = document.Pages.Add();
// Инициализирует новый экземпляр TextFragment для заголовка отчета
var textFragment = new TextFragment(reportTitle1);
Table table = new Table
{
// Установить ширину столбцов таблицы
ColumnWidths = "25% 25% 25% 25%",
// Установить отступы в ячейках
DefaultCellPadding = new MarginInfo(10, 5, 10, 5), // Лево Низ Право Верх
// Установить цвет границы таблицы в зеленый
Border = new BorderInfo(BorderSide.All, .5f, Color.Green),
// Установить границы для ячеек таблицы в черный цвет
DefaultCellBorder = new BorderInfo(BorderSide.All, .2f, Color.Green),
};
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("config.json", false)
.Build();
var connectionString = configuration.GetSection("connectionString").Value;
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentException("Отсутствует строка подключения в config.json");
var resultTable = new DataTable();
using (var conn = new SqlConnection(connectionString))
{
const string sql = "SELECT * FROM Tennats";
using (var cmd = new SqlCommand(sql, conn))
{
using (var adapter = new SqlDataAdapter(cmd))
{
adapter.Fill(resultTable);
}
}
}
table.ImportDataTable(resultTable,true,1,1);
// Добавить объект таблицы на первую страницу входного документа
document.Pages[1].Paragraphs.Add(table);
using (var streamOut = new MemoryStream())
{
document.Save(streamOut);
return new FileContentResult(streamOut.ToArray(), "application/pdf")
{
FileDownloadName = "demotable2.pdf"
};
}
Экспорт данных из Entity Framework
Более актуально для современного .NET является импорт данных из ORM-фреймворков. В этом случае хорошей идеей будет расширение класса Table методами расширения для импорта данных из простого списка или из сгруппированных данных. Давайте приведем пример для одного из самых популярных ORM - Entity Framework.
public static class PdfHelper
{
public static void ImportEntityList<TSource>(this 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)
{
// Добавить строку в таблицу
var row = table.Rows.Add();
// Добавить ячейки таблицы
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());
}
}
}
}
public static void ImportGroupedData<TKey,TValue>(this 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)
{
// Добавить строку группы в таблицу
var row = table.Rows.Add();
var cell = row.Cells.Add(group.Key.ToString());
cell.ColSpan = props.Length;
cell.BackgroundColor = Pdf.Color.DarkGray;
cell.DefaultCellTextState.ForegroundColor = Pdf.Color.White;
foreach (var item in group.Values)
{
// Добавить строку данных в таблицу
var dataRow = table.Rows.Add();
// Добавить ячейки
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), а после этого - строки этой группы.