Введение: почему "без Office" и что внутри?
Если вы когда-нибудь искали, как сгенерировать Excel-файл на C#, вы наверняка натыкались на культовый вопрос на Stack Overflow с десятками тысяч просмотров. И главная боль там — фраза "without installing Microsoft Office". Почему? Потому что тянуть за собой COM-объекты, зависеть от версии Excel на сервере и ловить ошибки вроде "Retrieving the COM class factory for component... failed" — это путь в ад для разработчика.
Но как же тогда создавать .XLSX и .XLS файлы? Ответ кроется в том, как устроен сам формат. Современный Excel (начиная с 2007 года) — это обычный ZIP-архив с XML-файлами внутри. Да-да, вы не ослышались: возьмите любой .xlsx, переименуйте в .zip, откройте — и увидите папки xl, _rels, docProps и кучу XML-документов. Каждый лист, каждая ячейка, каждый стиль — это аккуратно структурированный XML. А старый формат .XLS — это бинарный формат (BIFF), но для него тоже есть open-source решения.
В этой статье мы заглянем под капот этого процесса: разберём популярные библиотеки, напишем живой код и поймём, что на самом деле происходит, когда мы вызываем Save(). Поехали!
1. Три кита C# для работы с Excel без Office
На сегодняшний день есть три главных претендента, которые решают задачу "из коробки". Каждый из них работает с форматом напрямую, минуя установленный Office.
1.1. EPPlus (лицензия LGPL / Proprietary)
Самая популярная библиотека для .NET. Изначально была бесплатной, но с версии 5.0 перешла на платную лицензию для коммерческого использования. Однако для образовательных целей и небольших проектов её всё ещё можно использовать (версия 4.5.3.3 — последняя свободная). EPPlus работает только с .XLSX.
Как это работает под капотом: EPPlus использует System.IO.Packaging (тот самый ZIP-архив) и генерирует XML-файлы в соответствии со спецификацией Office Open XML (OOXML). Она умеет создавать стили, формулы, диаграммы — всё, что вы видите в Excel.
1.2. ClosedXML
Бесплатная, open-source (лицензия MIT). Тоже работает только с .XLSX. Отличается очень дружелюбным API, который напоминает работу с самим Excel: вы работаете с Workbook, Worksheet, Cell. Под капотом — тот же Open XML, но ClosedXML берёт на себя всю рутину по созданию XML-структуры.
1.3. NPOI
Порт знаменитой Java-библиотеки Apache POI. Главное преимущество — умеет работать как с .XLSX (через XSSFWorkbook), так и со старым .XLS (через HSSFWorkbook). Бесплатно, open-source (Apache 2.0). Идеально, если вам нужна поддержка legacy-форматов.
Под капотом для .XLS: NPOI напрямую пишет бинарные данные в формате BIFF8. Это сложнее, чем XML, но зато не требует сжатия.
2. Пишем код: создаём Excel-файл тремя способами
Давайте создадим простой отчёт с заголовками и данными. Ниже — примеры для каждой библиотеки.
2.1. Пример с ClosedXML (проще всего)
using ClosedXML.Excel;
// Создаём книгу
using (var workbook = new XLWorkbook())
{
// Добавляем лист
var worksheet = workbook.Worksheets.Add("Отчёт");
// Заголовки
worksheet.Cell(1, 1).Value = "Имя";
worksheet.Cell(1, 2).Value = "Возраст";
worksheet.Cell(1, 3).Value = "Город";
// Данные
worksheet.Cell(2, 1).Value = "Анна";
worksheet.Cell(2, 2).Value = 28;
worksheet.Cell(2, 3).Value = "Москва";
worksheet.Cell(3, 1).Value = "Иван";
worksheet.Cell(3, 2).Value = 35;
worksheet.Cell(3, 3).Value = "Санкт-Петербург";
// Стиль для заголовков
var headerRange = worksheet.Range("A1:C1");
headerRange.Style.Font.Bold = true;
headerRange.Style.Fill.BackgroundColor = XLColor.LightGray;
// Автоширина колонок
worksheet.Columns().AdjustToContents();
// Сохраняем
workbook.SaveAs("report.xlsx");
}
Что происходит под капотом? XLWorkbook создаёт в памяти структуру, которая при вызове SaveAs сериализуется в ZIP-архив с XML. Загляните в папку xl/worksheets/ — там будет sheet1.xml с вашими данными.
2.2. Пример с EPPlus (версия 4.5.3.3)
using OfficeOpenXml;
// Лицензия для EPPlus 4.5 (не требуется для старых версий)
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage())
{
var sheet = package.Workbook.Worksheets.Add("Данные");
// Заголовки
sheet.Cells[1, 1].Value = "Продукт";
sheet.Cells[1, 2].Value = "Цена";
sheet.Cells[1, 3].Value = "Количество";
// Данные
sheet.Cells[2, 1].Value = "Ноутбук";
sheet.Cells[2, 2].Value = 75000;
sheet.Cells[2, 3].Value = 10;
sheet.Cells[3, 1].Value = "Мышь";
sheet.Cells[3, 2].Value = 1500;
sheet.Cells[3, 3].Value = 50;
// Формула суммы
sheet.Cells[4, 2].Formula = "SUM(B2:B3)";
// Стили
using (var range = sheet.Cells[1, 1, 1, 3])
{
range.Style.Font.Bold = true;
range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);
}
sheet.Cells.AutoFitColumns();
package.SaveAs(new FileInfo("inventory.xlsx"));
}
Под капотом EPPlus: Он создаёт объектную модель документа (Workbook → Worksheets → Cells), а затем записывает всё в OOXML-пакет. Интересно, что EPPlus не использует LINQ to XML напрямую — у него свой движок для генерации XML, оптимизированный под скорость.
2.3. Пример с NPOI (для старого .XLS)
using NPOI.HSSF.UserModel; // для .XLS
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel; // для .XLSX
// Создаём книгу для .XLS
IWorkbook workbook = new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet("Лист1");
// Создаём стиль заголовка
IC