С# возвращает поток памяти из OpenXML, что приводит к поврежденному файлу слов

У меня проблема с MemoryStream из OpenXML. Мне удается открыть файл Word, изменить его и загрузить через HttpResponse, если я выполняю все шаги одним методом.

Но если я попытаюсь сделать это в двух разных классах (или методах), вернув MemoryStream, я получу поврежденный файл слова. Я думал о проблеме с промывкой или буфером, но не нашел решения.

Вот рабочий код:

    public void FillTemplateOpenXmlWord(HttpResponse response)
    {
        string filePath = @"c:\template.docx";
        byte[] filebytes = File.ReadAllBytes(filePath);

        using (MemoryStream stream = new MemoryStream(filebytes))
        {
            using (WordprocessingDocument myDoc = WordprocessingDocument.Open(stream, true))
            {
                // do some changes
                ...
                myDoc.MainDocumentPart.Document.Save();
            }

            string docx = "docx";
            response.Clear();
            response.ClearHeaders();
            response.ClearContent();
            response.AddHeader("content-disposition", "attachment; filename=\"" + docx + ".docx\"");
            response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            response.ContentEncoding = Encoding.GetEncoding("ISO-8859-1");
            stream.Position = 0;
            stream.CopyTo(response.OutputStream);
            response.End();
        }
    }

Вот нерабочий код:

    public void OpenFile(HttpResponse response)
    {
        MemoryStream stream = this.FillTemplateOpenXmlWord();

        string docx = "docx";
        response.Clear();
        response.ClearHeaders();
        response.ClearContent();
        response.AddHeader("content-disposition", "attachment; filename=\"" + docx + ".docx\"");
        response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        response.ContentEncoding = Encoding.GetEncoding("ISO-8859-1");
        stream.Position = 0;
        stream.CopyTo(response.OutputStream);
        response.End();
    }

    public MemoryStream FillTemplateOpenXmlWord()
    {
        string filePath = @"c:\template.docx";
        byte[] filebytes = File.ReadAllBytes(filePath);

        using (MemoryStream stream = new MemoryStream(filebytes))
        {
            using (WordprocessingDocument myDoc = WordprocessingDocument.Open(stream, true))
            {
                // do some changes
                ...
                myDoc.MainDocumentPart.Document.Save();
            }

            return stream;
        }
    }

Любая идея ?

Спасибо


person DotNetBeliever    schedule 21.01.2014    source источник


Ответы (3)


похоже, что поток закрывается, когда вы возвращаетесь. он находится в блоке использования. разве это не закроет поток памяти, как только завершится процедура filltemplate?

person gashach    schedule 21.01.2014
comment
Я только что нашел то же самое. Я был очень близок к тому, чтобы отредактировать свой вопрос и дать решение, когда получил ваш ответ. И вы правы! Я возвращаю поток в предложении using. Возврат создает не копию, а ссылку. Итак, в вызывающем методе я получаю ссылку на поток, которого больше нет в предложении using. Поток ликвидирован, контент больше недоступен. Спасибо - person DotNetBeliever; 21.01.2014

Вот что я использую для создания файлов OpenXML из потока памяти. В этом случае он создает файл XLSX из шаблона на сервере, но он должен быть аналогичным для других форматов OpenXml.

Действие контроллера:

public class ExportController : Controller
{
    public FileResult Project(int id)
    {
        var model = SomeDateModel.Load(id); 
        ProjectExport export = new ProjectExport();
        var excelBytes = export.Export(model);
        FileResult fr = new FileContentResult(excelBytes, "application/vnd.ms-excel")
        {
            FileDownloadName = string.Format("Export_{0}_{1}.xlsx", DateTime.Now.ToString("yyMMdd"), model.Name)
        };

        return fr;
    }
}

// Вспомогательный класс

public class ProjectExport
{
    private WorkbookPart workbook;
    private Worksheet ws;

    public byte[] Export(SomeDateModel model)
    {
        var template = new FileInfo(HostingEnvironment.MapPath(@"~\Export\myTemplate.xlsx"));
        byte[] templateBytes = File.ReadAllBytes(template.FullName);

        using (var templateStream = new MemoryStream())
        {
            templateStream.Write(templateBytes, 0, templateBytes.Length);
            using (var excelDoc = SpreadsheetDocument.Open(templateStream, true))
            {
                workbook = excelDoc.WorkbookPart;
                var sheet = workbook.Workbook.Descendants<Sheet>().First();

                ws = ((WorksheetPart)workbook.GetPartById(sheet.Id)).Worksheet;

                sheet.Name = model.Name;
                // Here write some other stuff for setting values in cells etc...
            }
            templateStream.Position = 0;
            var result = templateStream.ToArray();
            templateStream.Flush();

            return result;
        }
    }
person Tomino    schedule 21.09.2015
comment
Это прекрасно работает! Это было именно то, что мне было нужно, чтобы мой код работал с файлами в памяти. - person SebastianC; 27.06.2018

Ответ, опубликованный gashac, не описывает проблемы, которые вы собираетесь получить, не вызывая dispose в потоке.

Отказ от удаления потока памяти приводит к утечке памяти (то же, что и "условие использования").

Потоки памяти хранят данные в памяти, тогда как файловые потоки хранят данные на жестком диске.

Решение:

Сохраните поток памяти в массив байтов, удалите поток памяти и верните массив байтов.

Как вернуть bytearray вместо потока

См. следующий поток, чтобы вернуть файл в виде массива байтов: HttpResponseMessage Content не будет отображать PDF

person Dennis Hansen    schedule 01.04.2015