C# return memory stream from OpenXML resulting to a corrupted word file

DotNetBeliever picture DotNetBeliever · Jan 21, 2014 · Viewed 20k times · Source

I have a problem with a MemoryStream from OpenXML. I succeed with opening a Word file, changing it and downloading it through the HttpResponse if I do all the steps in a single method.

But if I try to do it in two different classes (or methods) by returning the MemoryStream, I get a corrupted word file. I thought about a flushing or buffer problem but I don't find a solution.

Here is the working code :

    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();
        }
    }

Here is the non-working code :

    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;
        }
    }

Any idea ?

thank you

Answer

Tomino picture Tomino · Sep 21, 2015

Here's what I'm using for generating OpenXML files from memory stream. In this case it makes XLSX file from template on server, but it should be similar for other OpenXml formats.

Controller action:

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;
    }
}

// Helper class

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;
        }
    }