jqgrid + EF + MVC: как экспортировать в Excel? Какой метод вы предлагаете?
Я использую jqgrid (стандартный) с EF 4 + MVC3. Я хотел бы реализовать экспорт Excel. Какой метод вы мне предложите?
Чтобы генерировать Excel, я бы хотел использовать эту библиотеку доктора Стивена Вальтера, которая имеет три типа вывода и позволяет также определять заголовки. Пожалуйста, скажите мне, если вы найдете это действительным для моих целей.
Я задаю этот вопрос, потому что я все еще пытаюсь реализовать экспорт Excel и нашел несколько методов. Некоторые предлагают сделать экспорт в csv, другие указывают, что он должен возвращать вывод JSON, и мне не ясно, присутствует ли эта возможность в бесплатной версии jqgrid. В любом случае, я хотел бы передать данные объекту Вальтера.
Что касается кода jqgrid, я нашел этот интересный ответ Олега, но я не понимаю, можно ли применить его к моим потребностям.
К сожалению, к настоящему времени я нашел только части решений для экспорта в Excel с EF MVC, но не нашел решения или полных примеров...
Что касается логики MVC, я собираюсь реализовать и разработать этот код, как любезно предложено @Tommy.
Пожалуйста, извините, если вопрос может быть глупым, я только начинающий (энтузиаст).
Спасибо за вашу драгоценную помощь!С уважением
2 ответа
Как я писал ранее (см. Здесь и здесь, например), лучший способ экспортировать данные сетки в XML - это использование Open XML SDK 2.0.
Пост доктора Стивена Вальтера показывает, как создать HTML-файл, который может быть прочитан в Excel. Это не файл Excel, и его все равно необходимо преобразовать в формат Excel. Использование CSV имеет еще больше проблем. В зависимости от содержимого исходной таблицы автоматическое преобразование в типы данных Excel может быть абсолютно неправильным. В одном проекте, который я разработал для заказчика, сетка содержала информацию о программных продуктах: название продукта, версию и так далее. Версия программного обеспечения иногда выглядит как дата (например, 1.3.1963), и такие ячейки будут неправильно преобразованы (в немецком языке в качестве разделителя в дате используется "."). В результате возникли действительно серьезные проблемы. Использование CSV с текстами, имеющими запятые внутри, также часто неправильно импортируется. Даже когда цитируешь ячейки с запятыми (,
) и избежал текстов, имеющих квоты, импорт по-прежнему будет неправильным, особенно в первом столбце. Я не хочу объяснять здесь всю историю всех попыток и ошибок, но в конце концов я решил отказаться от использования CSV и HTML и начал использовать Open XML SDK 2.0, который позволяет создавать реальные файлы Excel с расширением XLSX., Способ кажется мне идеальным, потому что не нужно устанавливать какие-либо компоненты Office на сервере, никаких дополнительных лицензий.
Единственное ограничение заключается в том, что можно использовать DocumentFormat.OpenXml.dll
поэтому ваша серверная программа должна работать в любой операционной системе Windows. Как известно, XLSX-файл - это ZIP-файл, в котором содержатся некоторые XML-файлы. Если вы все еще не знаете, что я рекомендую переименовать файл XLSX в ZIP-файл и распаковать его. Open XML SDK 2.0 - это библиотека, которая работает с XLSX-файлами, как с XML-файлами. Поэтому никаких дополнительных компонентов Office не требуется.
Можно найти много информации о том, как использовать Open XML SDK 2.0 (см. Здесь, здесь и здесь). Множество полезных примеров кода, которые одна камера может найти непосредственно в MSDN (см. Здесь). Тем не менее практическое использование Open XML SDK 2.0 не так просто, по крайней мере, в первый раз. Поэтому я создал демо из частей кода, которые я использовал сам.
Вы можете скачать демонстрационный проект здесь. Демонстрация является продолжением демонстраций из ответа и этого.
Для экспорта данных я использую DataForExcel
вспомогательный класс. Имеет конструктор в виде
DataForExcel(string[] headers, DataType[] colunmTypes, List<string[]> data,
string sheetName)
или в немного упрощенной форме
DataForExcel(string[] headers, List<string[]> data, string sheetName)
и единственный публичный метод
CreateXlsxAndFillData(Stream stream)
Использование класса для создания файла Excel может быть следующим
var excelData = new DataForExcel (
// column Header
new[]{"Col1", "Col2", "Col3"},
new[]{DataForExcel.DataType.String, DataForExcel.DataType.Integer,
DataForExcel.DataType.String},
new List<string[]> {
new[] {"a", "1", "c1"},
new[] {"a", "2", "c2"}
},
"Test Grid");
Stream stream = new FileStream ("Test.xlsx", FileMode.Create);
excelData.CreateXlsxAndFillData (stream);
stream.Close();
Использование в демоверсии ASP.NET MVC заключается в следующем
static readonly string[] HeadersQuestions = {
"Id", "Votes", "Title"
};
static readonly DataForExcel.DataType[] ColunmTypesQuestions = {
DataForExcel.DataType.Integer,
DataForExcel.DataType.Integer,
DataForExcel.DataType.String
};
public ActionResult ExportAllQuestionsToExcel () {
var context = new HaackOverflowEntities ();
var questions = context.Questions;
questions.MergeOption = MergeOption.NoTracking; // we don't want to update the data
// to be able to use ToString() below which is NOT exist in the LINQ to Entity
// we should include in query only the properies which we will use below
var query = questions.ToList ();
if (query.Count == 0)
return new EmptyResult ();
var data = new List<string[]> (query.Count);
data.AddRange (query.Select (item => new[] {
item.Id.ToString(CultureInfo.InvariantCulture),
item.Votes.ToString(CultureInfo.InvariantCulture),
item.Title
}));
return new ExcelResult (HeadersQuestions, ColunmTypesQuestions, data,
"Questions.xlsx", "Questions");
}
где ExcelResult
определяются как
public class ExcelResult : ActionResult {
private readonly DataForExcel _data;
private readonly string _fileName;
public ExcelResult (string[] headers, List<string[]> data, string fileName, string sheetName) {
_data = new DataForExcel (headers, data, sheetName);
_fileName = fileName;
}
public ExcelResult (string[] headers, DataForExcel.DataType[] colunmTypes, List<string[]> data, string fileName, string sheetName) {
_data = new DataForExcel (headers, colunmTypes, data, sheetName);
_fileName = fileName;
}
public override void ExecuteResult (ControllerContext context) {
var response = context.HttpContext.Response;
response.ClearContent();
response.ClearHeaders();
response.Cache.SetMaxAge (new TimeSpan (0));
using (var stream = new MemoryStream()) {
_data.CreateXlsxAndFillData (stream);
//Return it to the client - strFile has been updated, so return it.
response.AddHeader ("content-disposition", "attachment; filename=" + _fileName);
// see http://filext.com/faq/office_mime_types.php
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
response.ContentEncoding = Encoding.UTF8;
stream.WriteTo (response.OutputStream);
}
response.Flush();
response.Close();
}
}
Чтобы сделать код полным, я должен включить код класса DataForExcel
:
public class DataForExcel {
public enum DataType {
String,
Integer
}
private readonly string[] _headers;
private readonly DataType[] _colunmTypes;
private readonly List<string[]> _data;
private readonly string _sheetName = "Grid1";
private readonly SortedSet<string> _os = new SortedSet<string> ();
private string[] _sharedStrings;
private static string ConvertIntToColumnHeader(int index) {
var sb = new StringBuilder ();
while (index > 0) {
if (index <= 'Z' - 'A') // index=0 -> 'A', 25 -> 'Z'
break;
sb.Append (ConvertIntToColumnHeader (index / ('Z' - 'A' + 1) - 1));
index = index % ('Z' - 'A' + 1);
}
sb.Append ((char)('A' + index));
return sb.ToString ();
}
private static Row CreateRow(UInt32 index, IList<string> data) {
var r = new Row { RowIndex = index };
for (var i = 0; i < data.Count; i++)
r.Append (new OpenXmlElement[] { CreateTextCell (ConvertIntToColumnHeader (i), index, data[i]) });
return r;
}
private Row CreateRowWithSharedStrings(UInt32 index, IList<string> data) {
var r = new Row { RowIndex = index };
for (var i = 0; i < data.Count; i++)
r.Append (new OpenXmlElement[] { CreateSharedTextCell (ConvertIntToColumnHeader (i), index, data[i]) });
return r;
}
private Row CreateRowWithSharedStrings(UInt32 index, IList<string> data, IList<DataType> colunmTypes) {
var r = new Row { RowIndex = index };
for (var i = 0; i < data.Count; i++)
if (colunmTypes != null && i < colunmTypes.Count && colunmTypes[i] == DataType.Integer)
r.Append (new OpenXmlElement[] { CreateNumberCell (ConvertIntToColumnHeader (i), index, data[i]) });
else
r.Append (new OpenXmlElement[] { CreateSharedTextCell (ConvertIntToColumnHeader (i), index, data[i]) });
return r;
}
private static Cell CreateTextCell(string header, UInt32 index, string text) {
// create Cell with InlineString as a child, which has Text as a child
return new Cell (new InlineString (new Text { Text = text })) {
// Cell properties
DataType = CellValues.InlineString,
CellReference = header + index
};
}
private Cell CreateSharedTextCell(string header, UInt32 index, string text) {
for (var i=0; i<_sharedStrings.Length; i++) {
if (String.Compare (_sharedStrings[i], text, StringComparison.Ordinal) == 0) {
return new Cell (new CellValue { Text = i.ToString (CultureInfo.InvariantCulture) }) {
// Cell properties
DataType = CellValues.SharedString,
CellReference = header + index
};
}
}
// create Cell with InlineString as a child, which has Text as a child
throw new InstanceNotFoundException();
}
private static Cell CreateNumberCell(string header, UInt32 index, string numberAsString) {
// create Cell with CellValue as a child, which has Text as a child
return new Cell (new CellValue { Text = numberAsString }) {
// Cell properties
CellReference = header + index
};
}
private void FillSharedStringTable(IEnumerable<string> data) {
foreach (var item in data)
_os.Add (item);
}
private void FillSharedStringTable(IList<string> data, IList<DataType> colunmTypes) {
for (var i = 0; i < data.Count; i++)
if (colunmTypes == null || i >= colunmTypes.Count || colunmTypes[i] == DataType.String)
_os.Add (data[i]);
}
public DataForExcel(string[] headers, List<string[]> data, string sheetName) {
_headers = headers;
_data = data;
_sheetName = sheetName;
}
public DataForExcel(string[] headers, DataType[] colunmTypes, List<string[]> data, string sheetName) {
_headers = headers;
_colunmTypes = colunmTypes;
_data = data;
_sheetName = sheetName;
}
private void FillSpreadsheetDocument(SpreadsheetDocument spreadsheetDocument) {
// create and fill SheetData
var sheetData = new SheetData ();
// first row is the header
sheetData.AppendChild (CreateRow (1, _headers));
//const UInt32 iAutoFilter = 2;
// skip next row (number 2) for the AutoFilter
//var i = iAutoFilter + 1;
UInt32 i = 2;
// first of all collect all different strings in OrderedSet<string> _os
foreach (var dataRow in _data)
if (_colunmTypes != null)
FillSharedStringTable (dataRow, _colunmTypes);
else
FillSharedStringTable (dataRow);
_sharedStrings = _os.ToArray ();
foreach (var dataRow in _data)
sheetData.AppendChild (_colunmTypes != null
? CreateRowWithSharedStrings (i++, dataRow, _colunmTypes)
: CreateRowWithSharedStrings (i++, dataRow));
var sst = new SharedStringTable ();
foreach (var text in _os)
sst.AppendChild (new SharedStringItem (new Text (text)));
// add empty workbook and worksheet to the SpreadsheetDocument
var workbookPart = spreadsheetDocument.AddWorkbookPart ();
var worksheetPart = workbookPart.AddNewPart<WorksheetPart> ();
var shareStringPart = workbookPart.AddNewPart<SharedStringTablePart> ();
shareStringPart.SharedStringTable = sst;
shareStringPart.SharedStringTable.Save ();
// add sheet data to Worksheet
worksheetPart.Worksheet = new Worksheet (sheetData);
worksheetPart.Worksheet.Save ();
// fill workbook with the Worksheet
spreadsheetDocument.WorkbookPart.Workbook = new Workbook (
new FileVersion { ApplicationName = "Microsoft Office Excel" },
new Sheets (
new Sheet {
Name = _sheetName,
SheetId = (UInt32Value)1U,
// generate the id for sheet
Id = workbookPart.GetIdOfPart (worksheetPart)
}
)
);
spreadsheetDocument.WorkbookPart.Workbook.Save ();
spreadsheetDocument.Close ();
}
public void CreateXlsxAndFillData(Stream stream) {
// Create workbook document
using (var spreadsheetDocument = SpreadsheetDocument.Create (stream, SpreadsheetDocumentType.Workbook)) {
FillSpreadsheetDocument (spreadsheetDocument);
}
}
}
Приведенный выше код создает новый файл XLSX напрямую. Вы можете расширить код для поддержки большего количества типов данных, как String
а также Integer
который я использовал в коде.
В более профессиональной версии вашего приложения вы можете создать несколько шаблонов XLSX для экспорта различных таблиц. В коде вы можете вместо этого поместить данные в ячейки, поэтому вместо создания таблицы измените электронную таблицу. Таким образом, вы можете создавать идеально отформатированные файлы XLSX. Примеры из MSDN (см. Здесь) помогут вам реализовать способ, когда это потребуется.
ОБНОВЛЕНО: ответ содержит обновленный код, который позволяет генерировать документы Excel с большим форматированием ячеек.
Я посмотрел на пост Стивена, и он чертовски стар, что, кстати, не делает его неправильно. Если вам не нужно настраивать форматирование, заголовки и стили, тогда я думаю, что использовать CSV, так как это очень просто.
Что еще более важно, не думайте, что экспорт Excel с сайта MVC, который внутренне использует EF для доступа к данным, сложнее, чем, скажем, сайт Ruby on Rails, который использует ActiveRecord. Для меня это независимые проблемы, экспорт не должен ничего нового о базовых технологиях (по крайней мере, не напрямую), просто структура ваших данных, вот и все.
Поиск библиотек codeplex, позволяющих выполнять чтение / запись и экспорт в Excel, в наши дни их множество, множество действительно хороших решений, которые регулярно поддерживаются и тестируются тысячами разработчиков по всему миру. Если бы я был тобой, я бы не использовал решение Стивена, потому что, похоже, он время от времени набирал его в блокноте, а затем вставлял в пост - никаких юнит-тестов, никаких точек расширяемости + это в VB, так что его еще сложнее понять, но может быть это всего лишь я. Надеюсь, что это поможет и удачи