簡體   English   中英

預覽 OpenXML 創建的 zip 文件中的文檔時出錯

[英]Error when previewing document in zip file created by OpenXML

我使用 OpenXML 工具使用 ZipArchive 創建單詞和 excel 文件和 zip 的字節數組並返回文件字節。

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 文件后即可下載並打開該文件。

但是,window explorer 或其他解壓軟件無法查看。 嘗試在 window 資源管理器中打開文檔時,出現錯誤消息

“Windows 無法完成提取。無法創建目標文件”

關於如何解決這個問題的任何想法? 可以在 OpenXML 創建的 zip 中查看文檔嗎?

更新:我正在使用“打開 XML SDK 2.5 生產力工具”來生成代碼。 下面的代碼是生成文檔的代碼。 (詳情請使用工具生成代碼,因為代碼太多了)

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 function 傳遞數組字節列表。

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 文件就無法預覽。

我創建了一些單元測試(見下文),這表明您共享的代碼應該沒有問題(但是請注意,我並沒有簡單地復制您的代碼)。 生成的代碼或您未共享的其他代碼有可能是罪魁禍首。

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 存儲庫中找到。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM