jqgrid + EF + MVC: как экспортировать в Excel? Какой метод вы предлагаете? - PullRequest
4 голосов
/ 18 февраля 2012

Я использую jqgrid (стандарт) с EF 4 + MVC3 . Я хотел бы реализовать экспорт Excel. Какой метод вы мне предложите?

Для генерации Excel Я бы хотел использовать эту библиотеку доктора Стивена Вальтера, которая имеет три типа вывода и позволяет также определять заголовки. Пожалуйста, скажите мне, если вы найдете это действительным для моих целей.

Я задаю этот вопрос, потому что я все еще пытаюсь реализовать экспорт Excel и нашел несколько методов. Некоторые предлагают сделать экспорт в csv, другие указывают, что он должен возвращать вывод JSON, и мне не ясно, присутствует ли эта возможность в бесплатной версии jqgrid. В любом случае я хотел бы передать данные объекту Вальтера.

О коде jqgrid я нашел этот интересный ответ Олега , но я не понимаю, можно ли применить его к моим потребностям.

К сожалению, к настоящему времени я нашел только части решений для экспорта в Excel с EF MVC, но решения или полных примеров не было ...

Что касается логики MVC, я собираюсь реализовать и разработать этот код , как любезно предложено @ Tommy.

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

Спасибо за вашу драгоценную помощь! С наилучшими пожеланиями

Ответы [ 2 ]

11 голосов
/ 19 февраля 2012

Как я уже писал ранее (см. здесь и здесь , например), лучший способ экспорта данных сетки в 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, документированный с большим форматированием ячеек.

1 голос
/ 18 февраля 2012

Я посмотрел на пост Стивена, и он чертовски стар, что, кстати, не делает его неправильно.Если вам не нужно настраивать форматирование, заголовки и стили, тогда я думаю, что использовать CSV, так как это очень просто.Что еще более важно, не думайте, что экспорт Excel с сайта MVC, который внутренне использует EF для доступа к данным, сложнее, чем, скажем, сайт Ruby on Rails, который использует ActiveRecord.Для меня это независимые проблемы, экспорт не должен ничего нового о базовых технологиях (по крайней мере, не напрямую), просто структура ваших данных, вот и все.Поиск библиотек codeplex, позволяющих выполнять чтение / запись и экспорт в Excel, в наши дни их множество, множество действительно хороших решений, которые регулярно поддерживаются и тестируются тысячами разработчиков по всему миру.Если бы я был тобой, я бы не использовал решение Стивена, потому что, похоже, он время от времени набирал его в блокноте, а затем вставлял в пост - никаких юнит-тестов, никаких точек расширяемости + это в VB, так что это еще сложнее понять, но можетэто всего лишь я.Надеюсь, что это поможет и удачи

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...