首页 > 解决方案 > 预览 OpenXML 创建的 zip 文件中的文档时出错

问题描述

我使用 OpenXML 工具创建了 word 和 excel 文件的字节数组,并使用 ZipArchive 压缩它们并返回文件字节。

httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK);
httpResponseMessage.Content = new ByteArrayContent(zipFileBytes);
httpResponseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip");
httpResponseMessage.Content.Headers.ContentLength = zipFileBytes.Length;
httpResponseMessage.Content.Headers.Add("xfilename", zipFileName);
httpResponseMessage.StatusCode = HttpStatusCode.OK;
httpResponseMessage.Content.Headers.Add("Access-Control-Expose-Headers", "xfilename");

return httpResponseMessage;

解压 zip 文件后即可下载并打开该文件。

但是,它不能通过窗口浏览器或其他解压缩软件进行查看。尝试在窗口资源管理器中打开文档时,出现错误消息

“Windows 无法完成提取。无法创建目标文件”

关于如何解决这个问题的任何想法?可以在 OpenXML 创建的 zip 中查看文档吗?

更新: 我正在使用“Open XML SDK 2.5 Productivity Tool”来生成代码。下面的代码是生成文档的代码。(详情请使用工具生成代码,因为代码太多了)

using DocumentFormat.OpenXml.Packaging;
using Ap = DocumentFormat.OpenXml.ExtendedProperties;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;
using M = DocumentFormat.OpenXml.Math;
using Ovml = DocumentFormat.OpenXml.Vml.Office;
using V = DocumentFormat.OpenXml.Vml;
using W15 = DocumentFormat.OpenXml.Office2013.Word;
using A = DocumentFormat.OpenXml.Drawing;
using Thm15 = DocumentFormat.OpenXml.Office2013.Theme;

namespace GeneratedCode
{
    public class GeneratedClass
    {
        // Creates a WordprocessingDocument.
    public void CreatePackage(DataModel dataModel, List<DataModel> dataList, out string filename, out Byte[] fileBytes)
    {
        filename = string.Empty;
        fileBytes = null;
        using (MemoryStream ms = new MemoryStream())
        {
            try
            {
                using (WordprocessingDocument package = WordprocessingDocument.Create(ms, WordprocessingDocumentType.Document))
                {
                    CreateParts(package, dataModel, dataList);
                }

                string extension = ".docx";
                filename = "TestDoc" + extension;
                fileBytes = ms.ToArray();
                ms.Close();

                return;
            }
            catch (System.Exception)
            {
                throw;
            }
        }
    }
}

然后,我使用下面的代码生成 zip 文件,并从 CreatePackage 函数传递数组字节列表。

public byte[] zipByteDocument(List<Tuple<Byte[], string>> fileBytes)
{
    // the output bytes of the zip
    byte[] zipFileBytes = null;

    // create a working memory stream
    using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream())
    {
        // create a zip
        using (System.IO.Compression.ZipArchive zip = new System.IO.Compression.ZipArchive(memoryStream, System.IO.Compression.ZipArchiveMode.Create, true))
        {
            // interate through the source files
            foreach (Tuple<Byte[], string> file in fileBytes)
            {
                // add the item name to the zip
                System.IO.Compression.ZipArchiveEntry zipItem = zip.CreateEntry(file.Item2);
                // add the item bytes to the zip entry by opening the original file and copying the bytes 
                using (System.IO.MemoryStream originalFileMemoryStream = new System.IO.MemoryStream(file.Item1))
                {
                    using (System.IO.Stream entryStream = zipItem.Open())
                    {
                        originalFileMemoryStream.CopyTo(entryStream);
                    }
                }
            }
        }
        zipFileBytes = memoryStream.ToArray();
    }
    return zipFileBytes;
}

最后,我将 zipFileBytes 传递给 httpResponseMessage 并且可以下载。但是如果不解压缩 zip 文件就无法预览。

标签: c#openxmlhttpresponseattachment

解决方案


我创建了一些单元测试(见下文),这表明您共享的代码应该没有问题(但是请注意,我并没有简单地复制您的代码)。生成的代码或您未共享的其他代码有可能是罪魁祸首。

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using CodeSnippets.IO;
using Xunit;

namespace CodeSnippets.Tests.IO.Compression
{
    public class ZipArchiveTests
    {
        private static byte[] CreateZipArchiveBytes(IEnumerable<(byte[], string)> files)
        {
            using MemoryStream stream = CreateZipArchiveStream(files);
            return stream.ToArray();
        }

        private static MemoryStream CreateZipArchiveStream(IEnumerable<(byte[], string)> files)
        {
            var stream = new MemoryStream();
            using (CreateZipArchive(stream, files))
                return stream;
        }

        private static ZipArchive CreateZipArchive(Stream stream, IEnumerable<(byte[], string)> files)
        {
            if (stream == null) throw new ArgumentNullException(nameof(stream));
            if (files == null) throw new ArgumentNullException(nameof(files));

            var archive = new ZipArchive(stream, ZipArchiveMode.Create, true);

            foreach ((byte[] fileContent, string fileName) in files)
            {
                ZipArchiveEntry archiveEntry = archive.CreateEntry(fileName);
                using Stream entryStream = archiveEntry.Open();
                entryStream.Write(fileContent, 0, fileContent.Length);
            }

            return archive;
        }

        private static ZipArchive ReadZipArchive(byte[] zipArchiveBytes)
        {
            return new ZipArchive(new MemoryStream(zipArchiveBytes), ZipArchiveMode.Read, false);
        }

        private static byte[] ReadEntryBytes(ZipArchive zipArchive, string entryName)
        {
            ZipArchiveEntry entry = zipArchive.GetEntry(entryName) ?? throw new Exception();
            var entryBytes = new byte[entry.Length];

            using Stream entryStream = entry.Open();
            entryStream.Read(entryBytes, 0, (int) entry.Length);
            return entryBytes;
        }

        private static HttpResponseMessage CreateResponseMessage(byte[] content, string fileName, string mediaType)
        {
            var message = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new ByteArrayContent(content)
            };

            message.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = fileName
            };
            message.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
            message.Content.Headers.ContentLength = content.Length;

            return message;
        }

        [Fact]
        public async Task CreateResponseMessage_ZipArchiveBytes_Success()
        {
            // Arrange.
            const string path = "Resources\\ZipContents.docx";
            string fileName = Path.GetFileName(path);
            byte[] fileContent = File.ReadAllBytes(path);

            byte[] zipArchiveBytes = CreateZipArchiveBytes(new[]
            {
                (fileContent, fileName)
            });

            // Act.
            using HttpResponseMessage message = CreateResponseMessage(zipArchiveBytes, "ZipArchive.zip", "application/zip");
            HttpContent messageContent = message.Content;
            byte[] messageBytes = await messageContent.ReadAsByteArrayAsync();

            // Assert.
            // Original zipArchiveBytes and recevied messageBytes are equal.
            Assert.Equal(zipArchiveBytes, messageBytes);

            // Original file content and received ZIP archive content are equal.
            using ZipArchive zipArchive = ReadZipArchive(messageBytes);
            byte[] entryContent = ReadEntryBytes(zipArchive, fileName);

            Assert.Equal(fileContent.Length, entryContent.Length);
            Assert.Equal(fileContent, entryContent);
        }

        [Fact]
        public void CreateZipArchiveBytes_WordDocument_ZipFileSuccessfullyCreated()
        {
            // Arrange.
            const string path = "Resources\\ZipContents.docx";
            string fileName = Path.GetFileName(path);
            byte[] fileContent = File.ReadAllBytes(path);

            // Act.
            byte[] zipArchiveBytes = CreateZipArchiveBytes(new[]
            {
                (fileContent, fileName)
            });

            File.WriteAllBytes("ZipArchive_Bytes.zip", zipArchiveBytes);

            // Assert.
            using ZipArchive zipArchive = ReadZipArchive(zipArchiveBytes);
            byte[] entryContent = ReadEntryBytes(zipArchive, fileName);

            Assert.Equal(fileContent.Length, entryContent.Length);
            Assert.Equal(fileContent, entryContent);
        }
    }
}

2019-12-06 更新

我修改了单元测试以证明这适用于多个文档。这是第一个:

        [Fact]
        public void CreateZipArchiveBytes_Directory_ZipFileSuccessfullyCreated()
        {
            // Arrange, creating a ZIP archive with more than one entry.
            List<(byte[] fileContent, string fileName)> files = Directory
                .EnumerateFiles("Resources")
                .Select(path => (File.ReadAllBytes(path), Path.GetFileName(path)))
                .ToList();

            Assert.True(files.Count > 1);

            // Act.
            byte[] zipArchiveBytes = CreateZipArchiveBytes(files);
            File.WriteAllBytes("ZipArchive_Directory.zip", zipArchiveBytes);

            // Assert.
            using ZipArchive zipArchive = ReadZipArchive(zipArchiveBytes);

            Assert.Equal(files.Count, zipArchive.Entries.Count);

            foreach (ZipArchiveEntry entry in zipArchive.Entries)
            {
                byte[] fileContent = files
                    .Where(file => file.fileName == entry.Name)
                    .Select(file => file.fileContent)
                    .Single();

                using Stream entryStream = entry.Open();
                byte[] entryContent = entryStream.ToArray();

                Assert.Equal(fileContent, entryContent);
            }
        }

下一个单元测试结合HttpResponseMessage.

        [Fact]
        public async Task CreateResponseMessage_ZipArchiveDirectory_Success()
        {
            // Arrange, creating a ZIP archive with more than one entry.
            List<(byte[] fileContent, string fileName)> files = Directory
                .EnumerateFiles("Resources")
                .Select(path => (File.ReadAllBytes(path), Path.GetFileName(path)))
                .ToList();

            Assert.True(files.Count > 1);

            byte[] zipArchiveBytes = CreateZipArchiveBytes(files);

            // Act.
            using HttpResponseMessage message = CreateResponseMessage(zipArchiveBytes, "ZipArchive.zip", "application/zip");
            HttpContent messageContent = message.Content;
            byte[] messageBytes = await messageContent.ReadAsByteArrayAsync();

            // Assert.
            // Original zipArchiveBytes and recevied messageBytes are equal.
            Assert.Equal(zipArchiveBytes, messageBytes);

            // Original directory content and received ZIP archive content are equal.
            using ZipArchive zipArchive = ReadZipArchive(messageBytes);

            Assert.Equal(files.Count, zipArchive.Entries.Count);

            foreach (ZipArchiveEntry entry in zipArchive.Entries)
            {
                byte[] fileContent = files
                    .Where(file => file.fileName == entry.Name)
                    .Select(file => file.fileContent)
                    .Single();

                await using Stream entryStream = entry.Open();
                byte[] entryContent = await entryStream.ToArrayAsync();

                Assert.Equal(fileContent, entryContent);
            }
        }

核心方法没有改变。

完整的源代码可以在我的CodeSnippets GitHub 存储库中找到。


推荐阅读