ASP.NET MVC 3: Экспорт в Excel или что такое XMLExport?

Опубликовано 19.08.2011 10:09:00 в категории Микросервисы

При работе над очередным проектом, потребовалось реализовать экспорт данных в MS Excel. Решил поделиться информацией о том, как это можно сделать без особых усилий. Сборка создана согласно спецификации XML Spreadsheet Reference.

Тестовый проект “MvcExportToXML”

Я создал простой тестовый проект на ASP.NET MVC 3. Установил пакет с SampleData и на главной странице вывел список Person и добавил ссылку “Export to Excel”, вот эта ссылка и будет отправной точкой для моей статьи. Сборка создана согласно спецификации XML Spreadsheet Reference.

Это код контроллера (Controller):

public ActionResult Index()
{
   ViewBag.Message = "Welcome to ASP.NET MVC!";
   List<Person> people = People.GetPeople();
   return View(people);
}

Это код представления (View)

@model IEnumerable<SampleData.Person>
@{
    ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
    To learn more about ASP.NET MVC visit <a href=http://asp.net/mvc title="ASP.NET MVC Website">
        http://asp.net/mvc</a>.
</p>
<p>@Html.ActionLink("Export to XML", "Export")</p>
<table>
    <tr>
        <th>
            Name
        </th>
        <th>
            Age
        </th>
        <th>
            Gender
        </th>
        <th>
            Weight
        </th>
        <th>
            Country
        </th>
    </tr>
    @foreach (SampleData.Person item in Model)
 {
     <tr>
        <td>@item.Name</td>
        <td>@item.Age</td>
        <td>@item.Gender</td>
        <td>@item.Weight</td>
        <td>@item.Country</td>
    </tr>
 }
</table>

Это результат:

image

Начнем с Nuget-пакета

Устанавливаем экспортный Nuget-пакет. Да, я в очередной раз из простой сборки сделал Nuget-пакет, потому что считаю, что это очень удобно (да и людям приятно). Для установки пакета XmlExport вводим в консоли менеджера пакетов:

PM> Install-Package XmlExport
Successfully installed 'XmlExport 0.1.1'.
Successfully added 'XmlExport 0.1.1' to MvcExportToXML.

Готово! пакет установлен. Теперь перейдем к выгрузке отображаемых данных на странице в XML. Это не просто XML, этот XML с легкостью понимает MS Excel, а значит, данные можно будет увидеть в Excel-таблице.

Создадим новый метод в контроллере Home:

public ActionResult Export()
{

}

Добавляем namespace

using Calabonga.Xml.Exports;

Теперь надо сделать новый ExcelResult, унаследовав его от ActionResult, чтобы нажатие на кнопку экспорта пользователю предлагался сгенерированный файл для сохранения. Мой ExcelResult выглядит так:

/// <summary>
/// Выдает пользователю для загрузки файл Excel.
/// </summary>
public class ExcelResult : ActionResult
{
    /// <summary>
    /// Создает экземпляр класса, которые выдает файл Excel
    /// </summary>
    /// <param name="fileName">наименование файла для экспорта</param>
    /// <param name="report">готовый набор данные для экпорта</param>
    public ExcelResult(string fileName, string report)
    {
        this.Filename = fileName;
        this.Report = report;
    }
    public string Report { get; private set; }
    public string Filename { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
        HttpContext.Current.Response.BufferOutput = true;
        HttpContext.Current.Response.AddHeader("content-disposition",
                             string.Format("attachment; filename={0}", Filename));
        HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
        HttpContext.Current.Response.Charset = "utf-8";
        HttpContext.Current.Response.Write(Report);
        HttpContext.Current.Response.Flush();
        HttpContext.Current.Response.End();
    }
}

Надо в методе Export указать возращаемый тип ExcelResult и сгенерировать данные для экспорта. Не буду расписывать “что и почем”, а просто приведу немного кода:

string result = string.Empty;
Workbook wb = new Workbook();

// properties
wb.Properties.Author = "Calabonga";
wb.Properties.Created = DateTime.Today;
wb.Properties.LastAutor = "Calabonga";
wb.Properties.Version = "14";

// options sheets
wb.ExcelWorkbook.ActiveSheet = 1;
wb.ExcelWorkbook.DisplayInkNotes = false;
wb.ExcelWorkbook.FirstVisibleSheet = 1;
wb.ExcelWorkbook.ProtectStructure = false;
wb.ExcelWorkbook.WindowHeight = 800;
wb.ExcelWorkbook.WindowTopX = 0;
wb.ExcelWorkbook.WindowTopY = 0;
wb.ExcelWorkbook.WindowWidth = 600;

// create style s1 for header
Style s1 = new Style("s1");
s1.Font.Bold = true;
s1.Font.Italic = true;
s1.Font.Color = "#FF0000";
wb.AddStyle(s1);

// create style s2 for header
Style s2 = new Style("s2");
s2.Font.Bold = true;
s2.Font.Italic = true;
s2.Font.Size = 12;
s2.Borders.Add(new Border());
s2.Font.Color = "#0000FF";
wb.AddStyle(s2);

// First sheet
Worksheet ws = new Worksheet("Лист 1");

// adding headers
ws.AddCell(0, 0, "qwerty1", s1.UniqueName);
ws.AddCell(0, 1, "qwerty2", s1.UniqueName);
ws.AddCell(0, 2, "qwerty3", s1.UniqueName);

// adding row1
ws.AddCell(1, 0, 1);
ws.AddCell(1, 1, 2);
ws.AddCell(1, 2, 3);

// adding row2
ws.AddCell(2, 0, 4);
ws.AddCell(2, 1, 5);
ws.AddCell(2, 2, 6);

wb.AddWorksheet(ws);

// Second sheet
Worksheet ws2 = new Worksheet("Лист 2");
ws2.AddCell(0, 0, 1);
ws2.AddCell(1, 0, 2);
ws2.AddCell(2, 0, 3);
ws2.AddCell(3, 0, 4);
ws2.AddCell(4, 0, 5);
ws2.AddCell(5, 0, 6);
ws2.AddCell(6, 0, 7);
ws2.AddCell(7, 0, 8);
ws2.AddCell(8, 0, 9);
ws2.AddCell(9, 0, 10);
ws2.AddCell(10, 0, 11);
ws2.AddCell(11, 0, 12);

ws2.AddCell(0, 1, 13);
ws2.AddCell(1, 1, 14);
ws2.AddCell(2, 1, 15);
ws2.AddCell(3, 1, 16);
ws2.AddCell(4, 1, 17);
ws2.AddCell(5, 1, 18);
ws2.AddCell(6, 1, 19);
ws2.AddCell(7, 1, 20);
ws2.AddCell(8, 1, 21);
ws2.AddCell(9, 1, 22);
ws2.AddCell(10, 1, 23);
ws2.AddCell(11, 1, 24);

wb.AddWorksheet(ws2);

// Third sheet
Worksheet ws3 = new Worksheet("Пользователи");

// Adding Headers
ws3.AddCell(0, 0, "Наименование (Name)", s2.UniqueName);
ws3.AddCell(0, 1, "Возраст (Age)", s2.UniqueName);
ws3.AddCell(0, 2, "Пол (Gender)", s2.UniqueName);
ws3.AddCell(0, 3, "Вес (Weight)", s2.UniqueName);
ws3.AddCell(0, 4, "Страна (Country)", s2.UniqueName);

// get data
List<Person> people = People.GetPeople();

int totalRows = 0;

// appending rows with data
for (int i = 0; i < people.Count; i++)
{
   ws3.AddCell(i + 1, 0, people[i].Name);
   ws3.AddCell(i + 1, 1, people[i].Age);
   ws3.AddCell(i + 1, 2, people[i].Gender);
   ws3.AddCell(i + 1, 3, people[i].Weight);
   ws3.AddCell(i + 1, 4, people[i].Country);
   totalRows++;
}
totalRows++;

// appending footer with formulas
ws3.AddCell(totalRows, 0, string.Empty);
ws3.AddCell(totalRows, 1, 0, "=AVERAGE(R[-" + (totalRows - 1) + "]C:R[-1]C)", "");
ws3.AddCell(totalRows, 2, string.Empty);
ws3.AddCell(totalRows, 3, 0, "=SUM(R[-" + (totalRows - 1) + "]C:R[-1]C)", "");
ws3.AddCell(totalRows, 4, string.Empty);

wb.AddWorksheet(ws3);

// generate xml
string workstring = wb.ExportToXML();

// Send to user file
return new ExcelResult("Persons.xls", workstring);

Многовато получилось, но зато всё понятно. Правда? Особенно если учесть наличие комментариев. Единственное что осталось сделать, так это привести скриншоты каждого из трёх листов сформированной книги Person.xls.

Первый лист:

image

Второй лист:

image

И, наконец, то ради чего…

image

Обратите внимание, что формулы не просчитаны потому что книга показана в режиме “Защищенный просмотр”.

2

1

Но если нажать кнопку “Разрешить редактирование”, то сразу же увидим результат работы формул:

3

Вот и всё. Пишите в комментарии свои вопросы, дополнения, пожелания и замечания.

P.S.: После добавления некоторых необходимых классов и свойств для Worksheet и Workbook пришлось отказать от поддержки библиотеки под Silverlight 4.

Версия 1.1.6 от 30.09.2011: Добавлена возможно экспорта простого HTML в MS Word. Например рассмотрим такой пример. Есть текст какого-то договора, редактирование которого производится в html-редакторе (в моем случае это ckeditor). В своем приложении (MVC3) создал:

    internal class WordResult: ActionResult
    {
        /// <summary>
        /// Наименование файла для сохранения документа на стороне клиента.
        /// </summary>
        public string FileName { get; private set; }

        /// <summary>
        /// MVC url представления которой надо сохранить в MS Word
        /// </summary>
        public string Content { get; private set; }

        /// <summary>
        /// Заголовок HTML
        /// </summary>
        public string Title { get; private set; }

        /// <summary>
        /// Создает экземпляр WordResult
        /// </summary>
        /// <param name="filename">имя файла</param>
        /// <param name="content">mvc url представления</param>
        /// <param name="title">заголовок документа</param>
        public WordResult(string filename, string title, string content)
        {
            this.Title= title;
            this.FileName = filename;
            this.Content = content;
        }

public override void ExecuteResult(ControllerContext context)
{

    string wordstring = WordDocument.CreateFromHtml(this.Content, "Dogovor");

    HttpContext ctx = HttpContext.Current;
    ctx.Response.AppendHeader("Content-Type", "application/msword");
    ctx.Response.Clear();
    ctx.Response.Charset = "Windows-1251";
    //ctx.Response.ContentEncoding = Encoding.UTF8;
    ctx.Response.AddHeader("content-disposition", "attachment;filename=" + this.FileName);
    ctx.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ctx.Response.ContentType = "application/ms-word";
    ctx.Response.Write(wordstring);
    ctx.Response.End();
    ctx.Response.Flush();
    ctx.Response.Clear();
}
    }
}

Обратите внимание на строку номер 34. Именно в ней я и превращаю свой договор из формата HTML  в формат, которой MS Word воспринимает как "родной". Естественно, что перед печатью договора, я подменяю нужные поля на требуемые значения (строка 6):

public ActionResult Print(int id)
{
    Check.Argument.IsNotNegativeOrZero(id, "id");
    Contract contract = contractRepository.Find(id);
    Check.Argument.IsNotNull(contract, "contract");
    string contractText = DataReplacer.Replace(contract.TypeOfDeal.ContractText, contract);
    return new WordResult(String.Format("{0}_{1}.doc", contract.Number, contract.TypeOfDeal.Name), "Дововор услуги", contractText);
}

Вот и всё пока для этой библиотеки. Пишите комментарии.

Версия пакета 0.3.1 от 26.03.2013: Версия nuget-пакета обновилась (новая версия 0.3.1). Добавлены новые возможности: ширина колонок и авторазмер для ширины колонок, сквозные ячейки при печати нескольких страниц и многое другое.

Download

По просьбе некоторых активных читателей блога (привет тебе Алексей О.) работоспособность демонстрационного проекта восстановлена:

  • обновлены nuget-пакеты, вернее их версии;
  • установлена опция автоматического скачивания недостающих nuget-пакетов;
  • адаптирован для версии Visual Studio 2012;
  • поправлены ошибки.

Скачать демонстрационный проект для Visual Studio 2012 (обновление от 09.05.2013 года, кстати, с праздником! С Днем победы! Ура!)

​Проект выложен в github. Nuget-пакет обновился до .NET 4.5.2. Сборка создана согласно спецификации XML Spreadsheet Reference.

Новая сборка для ASP.NET Core (NetStandard2.1)

Комментарии к статье ()

Загрузка...

Что-то пошло не по сценарию и завершилось ошибкой. Перезагрузить страницу (F5) 🗙

Переподключаем сервер...

Переподключение сломалось... Пробуем еще раз сек.

Не получилось переподключится.
Пожалуйста, обновите страницу F5

Сессия была поставлена сервером на паузу.

Восстановить сессию не получилось.
Пожалуйста, обновите страницу F5.