Error when previewing document in zip file created by OpenXML - c#

I used OpenXML tools to create a byte array of the word and excel file and zip them using ZipArchive and return the filebyte.
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;
The file can be download and open after unzip the zip file.
However, it cannot be review by window explorer or other unzip software.
When trying to open the document in the window explorer, error message
"Windows cannot complete the extraction. The destination file could
not be created"
Any ideas about how to solve this issue? Can the documents be review inside the zip which created by OpenXML?
UPDATE:
I'm using "Open XML SDK 2.5 Productivity Tool" to generate the code. And the code below is the one generate the document. (For detail, please use the tool to generate the code, since it is toooooooo many line of them)
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;
}
}
}
}
Then, I generate the zip file using the code below and passing the list of array byte from the 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;
}
And finally, I pass the zipFileBytes to the httpResponseMessage and it can be download. But fail to be preview without unzipping the zip file.

I've created a few unit tests (see below), which demonstrate that there should not be an issue with the code you have shared (noting, however, that I have not simply replicated your code). There is a chance that the generated code or other code that you did not share is the culprit.
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);
}
}
}
Update 2019-12-06
I amended the unit tests to also demonstrate that this works with multiple documents. Here's the first one:
[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);
}
}
The next unit test demonstrates the same in conjunction with the 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);
}
}
The core methods have not changed.
The full source code can be found in my CodeSnippets GitHub repository.

Related

Invalid zip file reading from a Stream in C#

I have the following code:
private static byte[] ConverterStringToByte(Stream body)
{
string fileName = "data_" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".zip";
// Take out the bytes from the memory stream and safely close the stream
using (var ms = new MemoryStream())
{
body.CopyTo(ms);
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, false))
{
var zipEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
using (BinaryWriter writer = new BinaryWriter(zipEntry.Open()))
{
ms.Position = 0;
writer.Write(ms.ToArray());
}
}
return ms.ToArray();
}
}
I am downloading the file successfully, however I'm getting
invalid file
when trying to open
I think it should be something like this. Not sure about fileName though, because it's the name of the file being put into archive, so I don't think it should have *.zip extension. Unless you are creating a zip of zips.
static byte[] ConverterStringToByte(Stream body)
{
string fileName = #"data_" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".zip";
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, false))
{
var zipEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
using (var destStream = zipEntry.Open())
{
body.CopyTo(destStream);
}
}
return ms.ToArray();
}
}

Download multiple files .NET CORE Web API as ArchiveZip return root directory

I make a private class to get the bytes of the file and then return it into a zip file. Basically, I just want to pick specific files inside my folder and then zip it then download it. Here's my class:
private FileResult DownloadMultipleFiles(List<byte[]> byteArrayList)
{
var zipName = $"archive-EvidenceFiles-{DateTime.Now.ToString("yyyy_MM_dd-HH_mm_ss")}.zip";
using (MemoryStream ms = new MemoryStream())
{
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in byteArrayList)
{
string fPath = Encoding.ASCII.GetString(file);
var entry = archive.CreateEntry(fPath, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
{
zipStream.Write(file, 0, file.Length);
}
}
}
return File(ms.ToArray(), "application/zip", zipName);
}
}
And then, here's my controller:
[HttpGet("GetBundleFiles/{rhaId}")]
public async Task<IActionResult> GetBundleFiles(string rhaId)
{
List<byte[]> filesPath = new List<byte[]>();
var results = await _rhaFileEvidence.GetByRhaID(rhaId);
var files = results.ToList();
if (files.Count == 0)
return Ok(new { status = "null", message = "Empty data" });
files.ForEach(file =>
{
var fPath = file.FilePath;
byte[] bytes = Encoding.ASCII.GetBytes(fPath);
filesPath.Add(bytes);
});
return DownloadMultipleFiles(filesPath);
}
The controller works well, I can download the zip but when I open it, I can't get the files instead I get the root directory of the project I saved, like D:. I think I make mistake when making the memory stream or something, is there any suggestion how can I fix this? (Paste some solution code in the answer please)
you are getting the directory root because when you use archive.CreateEntry you are passing the file full path in parameter, you should be using only the file name
var entry = archive.CreateEntry(System.IO.Path.GetFileName(fPath), CompressionLevel.Fastest);
a second issue is that you actually saving the file path to your files not the content of the original file. you can update your DownloadMultipleFiles like this
private FileResult DownloadMultipleFiles(List<byte[]> byteArrayList)
{
var zipName = $"archive-EvidenceFiles-{DateTime.Now.ToString("yyyy_MM_dd-HH_mm_ss")}.zip";
using (MemoryStream ms = new MemoryStream())
{
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in byteArrayList)
{
string fPath = Encoding.ASCII.GetString(file);
var entry = archive.CreateEntry(System.IO.Path.GetFileName(fPath), CompressionLevel.Fastest);
using (var zipStream = entry.Open())
{
var bytes = System.IO.File.ReadAllBytes(fPath);
zipStream.Write(bytes, 0, bytes.Length);
}
}
}
return File(ms.ToArray(), "application/zip", zipName);
}
}

Returning a zipfile from a web api

I have built an asp net web api. I need to return a zipfile, as a result of some inner logic. I'm using this code and it works, but the resulting zip file, when unzipped manually, gave me this error "There are data after the end of the payload"
using (ZipFile zip = new ZipFile())
{
...
zip.Save(di.FullName + "\\" + "Update.zip");
}
string path = Path.Combine(Properties.Settings.Default.PathDisposizioniHTML, "Update.zip");
var response = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new System.IO.FileStream(path, System.IO.FileMode.Open);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
This is how i receive the data in a .net console application:
using (Stream output = File.OpenWrite(#"C:\prova\MyFile.zip"))
using (Stream input = httpResponse.GetResponseStream())
{
input.CopyTo(output);
}
If you already have the zip file on your system, you shouldn't need to do anything special before sending it as a response.
This should work:
string filePath = #"C:\myfolder\myfile.zip";
return File(filePath, "application/zip");
If you're making the file on the fly, i.e. getting other files and programatically putting them into a zip file for the user, the following should work:
public IActionResult GetZipFile(){
//location of the file you want to compress
string filePath = #"C:\myfolder\myfile.ext";
//name of the zip file you will be creating
string zipFileName = "zipFile.zip";
byte[] result;
using (MemoryStream zipArchiveMemoryStream = new MemoryStream())
{
using (ZipArchive zipArchive = new ZipArchive(zipArchiveMemoryStream, ZipArchiveMode.Create, true))
{
ZipArchiveEntry zipEntry = zipArchive.CreateEntry(zipFileName);
using (Stream entryStream = zipEntry.Open())
{
using (MemoryStream tmpMemory = new MemoryStream(System.IO.File.ReadAllBytes(filePath)))
{
tmpMemory.CopyTo(entryStream);
};
}
}
zipArchiveMemoryStream.Seek(0, SeekOrigin.Begin);
result = zipArchiveMemoryStream.ToArray();
}
return File(result, "application/zip", zipFileName);
}
This is taken from a recent ASP.NET project of my own.

Invalid zip file after creating it with System.IO.Compression

I'm trying to create a zip file that contains one or more files.
I'm using the .NET framework 4.5 and more specifically System.IO.Compression namespace.
The objective is to allow a user to download a zip file through a ASP.NET MVC application.
The zip file is being generated and sent to the client but when I try to open it by doing double click on it I get the following error:
Windows cannot open the folder.
The compressed (zipped) folder ... is invalid.
Here's my code:
[HttpGet]
public FileResult Download()
{
var fileOne = CreateFile(VegieType.POTATO);
var fileTwo = CreateFile(VegieType.ONION);
var fileThree = CreateFile(VegieType.CARROT);
IEnumerable<FileContentResult> files = new List<FileContentResult>() { fileOne, fileTwo, fileThree };
var zip = CreateZip(files);
return zip;
}
private FileContentResult CreateFile(VegieType vType)
{
string fileName = string.Empty;
string fileContent = string.Empty;
switch (vType)
{
case VegieType.BATATA:
fileName = "batata.csv";
fileContent = "THIS,IS,A,POTATO";
break;
case VegieType.CEBOLA:
fileName = "cebola.csv";
fileContent = "THIS,IS,AN,ONION";
break;
case VegieType.CENOURA:
fileName = "cenoura.csv";
fileContent = "THIS,IS,A,CARROT";
break;
default:
break;
}
var fileBytes = Encoding.GetEncoding(1252).GetBytes(fileContent);
return File(fileBytes, MediaTypeNames.Application.Octet, fileName);
}
private FileResult CreateZip(IEnumerable<FileContentResult> files)
{
byte[] retVal = null;
if (files.Any())
{
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
{
foreach (var f in files)
{
var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
entryStream.Write(f.FileContents, 0, f.FileContents.Length);
entryStream.Close();
}
}
zipStream.Position = 0;
retVal = zipStream.ToArray();
}
}
}
return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");
}
Can anyone please shed some light on why is windows saying that my zip file is invalid when I double click on it.
A final consideration, I can open it using 7-Zip.
You need to get the MemoryStream buffer via ToArray after the ZipArchive object gets disposed. Otherwise you end up with corrupted archive.
And please note that I have changed the parameters of ZipArchive constructor to keep it open when adding entries.
There is some checksumming going on when the ZipArchive is beeing disposed so if you read the MemoryStream before, it is still incomplete.
private FileResult CreateZip(IEnumerable<FileContentResult> files)
{
byte[] retVal = null;
if (files.Any())
{
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
foreach (var f in files)
{
var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
using (BinaryWriter writer = new BinaryWriter(entry.Open()))
{
writer.Write(f.FileContents, 0, f.FileContents.Length);
writer.Close();
}
}
zipStream.Position = 0;
}
retVal = zipStream.ToArray();
}
}
return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");
}
Just return the stream...
private ActionResult CreateZip(IEnumerable files)
{
if (files.Any())
{
MemoryStream zipStream = new MemoryStream();
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
{
foreach (var f in files)
{
var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
entryStream.Write(f.FileContents, 0, f.FileContents.Length);
entryStream.Close();
}
}
}
zipStream.Position = 0;
return File(zipStream, MediaTypeNames.Application.Zip, "horta.zip");
}
return new EmptyResult();
}
Try changing
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
to
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
In this usage, the archive is forced to write to the stream when it is closed. However, if the leaveOpen argument of the constructor is set to false, it will close the underlying stream too.
When I added a wrong name for the entry as in the example
var fileToZip = "/abc.txt";
ZipArchiveEntry zipFileEntry = zipArchive.CreateEntry(fileToZip);
I got the same error. After correcting the file name, it is ok now.
I got the "The compressed (zipped) folder ... is invalid." error because my entries were named with a leading "/" in front of them. Some zip extractors had no problem with this but the Windows one does. I resolved it by removing the slash from the entry name (from "/file.txt" to "file.txt").

Zip package with Unicode chars in file name can't be uncompressed with System.IO.Packaging.Package?

Have some troubles with file package manipulation.
Here is short description of issue:
If a package contains a file with Unicode characters in it, compressing of it works well(can open package and be uncompressed it in windows) but if try to use this Package.Open(.., FileMode.Open), it results in error "File contains corrupted data".
Question: What am i doing wrong? Can't figure this out. Please advise.
Here is code for compressing and uncompromising binary data in-memory that i use:
public virtual byte[] UnZipFile(byte[] data, string packageFileName)
{
using (var inputStream = new MemoryStream(data))
{
using (var package = Package.Open(inputStream, FileMode.Open))
{
var fileUri = PackUriHelper.CreatePartUri(new Uri(#"/" + packageFileName, UriKind.Relative));
using (var outputStream = new MemoryStream())
{
using (var partStream = package.GetPart(fileUri).GetStream())
{
partStream.CopyTo(outputStream);
}
return outputStream.ToArray();
}
}
}
}
public virtual byte[] ZipFile(byte[] data, string packageFileName)
{
using (var outMs = new MemoryStream())
{
using (var package = Package.Open(outMs, FileMode.Create))
{
var fileUri = PackUriHelper.CreatePartUri(new Uri(#"/" + packageFileName, UriKind.Relative));
var contentType = #"data/" + packageFileName.Substring(packageFileName.LastIndexOf(".", StringComparison.Ordinal) + 1);
using (var zipStream = package.CreatePart(fileUri, contentType, CompressionOption.Normal).GetStream())
{
using (var ms = new MemoryStream(data))
{
ms.CopyTo(zipStream);
}
}
}
return outMs.ToArray();
}
}
solved by using newer lib, System.IO.Compression, thanks for pointing out this #Hans Passant

Categories