C#从OpenXML返回内存流导致Word文件损坏

8

我在使用OpenXML的MemoryStream时遇到了问题。如果我在单个方法中完成所有步骤,则可以成功打开Word文件、更改它并通过HttpResponse下载它。

但是,如果我尝试在两个不同的类(或方法)中返回MemoryStream,那么我会得到一个损坏的word文件。我认为这可能是刷新或缓冲区问题,但我找不到解决方案。

以下是可行的代码:

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

有什么想法吗?

谢谢。

3个回答

7
以下是我用于从内存流生成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;
        }
    }

这非常好!这正是我需要的,以便我的代码可以在内存文件上运行。 - SebastianC

1
看起来在你返回时流已经关闭。它位于一个using块中,会不会在filltemplate过程结束时立即关闭内存流?

我刚刚也发现了同样的问题。当我看到你的回答时,我已经准备编辑我的问题并提供解决方案了。而你是对的!我在using语句块中返回了一个流。这个return语句并没有创建一个副本,而是创建了一个引用。因此,在调用方法中,我得到了一个指向不再在using语句块中的流的引用。流被释放了,内容也不再可用了。谢谢。 - DotNetBeliever

0

gashac发布的答案没有描述不调用流dispose会遇到的问题。

不处理内存流会导致内存泄漏(与“using语句”相同)。

内存流将数据保存在内存中,而文件流将数据保存在硬盘上。

解决方案:

将内存流保存为字节数组,处理内存流并返回字节数组。

如何返回字节数组而不是流

请参见以下线程以将文件作为字节数组返回: HttpResponseMessage Content won't display PDF


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接