I have a DLL with embedded Excel file. The goal is to retrieve this file and create some entry (Empty_File.txt in this example). When I'm using FileStream - the entry gets created, but when I'm using MemoryStream - entry isn't created.
var filePath = "C:\\Temp\\Test2.xlsx";
var asm = typeof(Program).Assembly;
var asmName = asm.GetName().Name;
using var resourceStream = asm.GetManifestResourceStream($"{asmName}.Resources.Template.xlsx");
if (File.Exists(filePath)) File.Delete(filePath);
await UseFileStream(resourceStream, filePath);
// or
await UseMemoryStream(resourceStream, filePath);
static async Task UseMemoryStream(Stream resourceStream, string filePath)
{
using (var ms = new MemoryStream())
{
await resourceStream.CopyToAsync(ms);
using (var zip = new ZipArchive(ms, ZipArchiveMode.Update))
{
zip.CreateEntry("Empty_File.txt");
using (var fs = CreateFileStream(filePath))
{
ms.Seek(0L, SeekOrigin.Begin);
await ms.CopyToAsync(fs);
}
}
}
}
static async Task UseFileStream(Stream resourceStream, string filePath)
{
using var fs = CreateFileStream(filePath);
await resourceStream.CopyToAsync(fs);
using var zip = new ZipArchive(fs, ZipArchiveMode.Update);
zip.CreateEntry("Empty_File.txt");
}
static FileStream CreateFileStream(string filePath) =>
new FileStream(filePath, new FileStreamOptions
{
Access = FileAccess.ReadWrite,
Mode = FileMode.Create,
Share = FileShare.None
});
Per the docs for ZipArchive.Dispose:
This method finishes writing the archive and releases all resources used by the ZipArchive object. Unless you construct the object by using the ZipArchive(Stream, ZipArchiveMode, Boolean) constructor overload and set its leaveOpen parameter to true, all underlying streams are closed and no longer available for subsequent write operations.
You are currently writing to the file stream before this happens, so the changes to the zip file haven't been written yet.
You'll also note from this that the underlying MemoryStream will be disposed unless you specify leaveOpen: true in the constructor, which would prevent you copying to the file afterwards.
So putting both of these together:
static async Task UseMemoryStream(Stream resourceStream, string filePath)
{
using (var ms = new MemoryStream())
{
await resourceStream.CopyToAsync(ms);
using (var zip = new ZipArchive(ms, ZipArchiveMode.Update, leaveOpen: true))
{
zip.CreateEntry("Empty_File.txt");
}
using (var fs = CreateFileStream(filePath))
{
ms.Seek(0L, SeekOrigin.Begin);
await ms.CopyToAsync(fs);
}
}
}
Related
I'm trying to create a zip stream on the fly with some byte array data and make it download via my MVC action.
But the downloaded file always gives the following corrupted error when opened in windows.
And this error when I try to xtract from 7z
But note that the files extracted from the 7z is not corrupted.
I'm using ZipArchive and the below is my code.
private byte[] GetZippedPods(IEnumerable<POD> pods, long consignmentID)
{
using (var zipStream = new MemoryStream())
{
//Create an archive and store the stream in memory.
using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
int index = 1;
foreach (var pod in pods)
{
var zipEntry = zipArchive.CreateEntry($"POD{consignmentID}{index++}.png", CompressionLevel.NoCompression);
using (var originalFileStream = new MemoryStream(pod.ByteData))
{
using (var zipEntryStream = zipEntry.Open())
{
originalFileStream.CopyTo(zipEntryStream);
}
}
}
return zipStream.ToArray();
}
}
}
public ActionResult DownloadPOD(long consignmentID)
{
var pods = _consignmentService.GetPODs(consignmentID);
var fileBytes = GetZippedPods(pods, consignmentID);
return File(fileBytes, MediaTypeNames.Application.Octet, $"PODS{consignmentID}.zip");
}
What am I doing wrong here.
Any help would be highly appreciated as I'm struggling with this for a whole day.
Thanks in advance
Move zipStream.ToArray() outside of the zipArchive using.
The reason for your problem is that the stream is buffered. There's a few ways to deal wtih it:
You can set the stream's AutoFlush property to true.
You can manually call .Flush() on the stream.
Or, since it's MemoryStream and you're using .ToArray(), you can simply allow the stream to be Closed/Disposed first (which we've done by moving it outside the using).
I Dispose ZipArchive And error solved
public static byte[] GetZipFile(Dictionary<string, List<FileInformation>> allFileInformations)
{
MemoryStream compressedFileStream = new MemoryStream();
//Create an archive and store the stream in memory.
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, true))
{
foreach (var fInformation in allFileInformations)
{
var files = allFileInformations.Where(x => x.Key == fInformation.Key).SelectMany(x => x.Value).ToList();
for (var i = 0; i < files.Count; i++)
{
ZipArchiveEntry zipEntry = zipArchive.CreateEntry(fInformation.Key + "/" + files[i].FileName);
var caseAttachmentModel = Encoding.UTF8.GetBytes(files[i].Content);
//Get the stream of the attachment
using (var originalFileStream = new MemoryStream(caseAttachmentModel))
using (var zipEntryStream = zipEntry.Open())
{
//Copy the attachment stream to the zip entry stream
originalFileStream.CopyTo(zipEntryStream);
}
}
}
//i added this line
zipArchive.Dispose();
return compressedFileStream.ToArray();
}
}
public void SaveZipFile(){
var zipFileArray = Global.GetZipFile(allFileInformations);
var zipFile = new MemoryStream(zipFileArray);
FileStream fs = new FileStream(path + "\\111.zip",
FileMode.Create,FileAccess.Write);
zipFile.CopyTo(fs);
zipFile.Flush();
fs.Close();
zipFile.Close();
}
I was also having problems with this and I found my issue was not the generation of the archive itself but rather how I was handing my GET request in AngularJS.
This post helped me: how to download a zip file using angular
The key was adding responseType: 'arraybuffer' to my $http call.
factory.serverConfigExportZIP = function () {
return $http({
url: dataServiceBase + 'serverConfigExport',
method: "GET",
responseType: 'arraybuffer'
})
};
you can remove "using" and use Dispose and Close methods
it's work for me
...
zip.Dispose();
zipStream.Close();
return zipStream.ToArray();
I know this is a C# question but for managed C++, delete the ZipArchive^ after you're done with it to fix the error.
ZipArchive^ zar = ZipFile::Open(starget, ZipArchiveMode::Create);
ZipFileExtensions::CreateEntryFromFile(zar, sfile1, "file.txt");
ZipFileExtensions::CreateEntryFromFile(zar, sfile2, "file2.txt");
delete zar;
when i wanted to create zip file directly from MemoryStream which i used for ZipArchive i was getting error ( "unexpected end of data" or zero length file )
there are three points to get ride of this error
set the last parameter of ZipArchive constructor to true ( it leaves to leave stream open after ZipArchive disposed )
call dispose() on ZipArchive and dispose it manually.
create another MemoryStream based on which you set in ZipArchive constructor, by calling ToArray() method.
here is sample code :
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create,))
{
foreach (var s3Object in objectList.S3Objects)
{
var entry = archive.CreateEntry(s3Object.Key, CompressionLevel.NoCompression);
using (var entryStream = entry.Open())
{
var request = new GetObjectRequest { BucketName = command.BucketName, Key = s3Object.Key };
using (var getObjectResponse = await client.GetObjectAsync(request))
{
await getObjectResponse.ResponseStream.CopyToAsync(entryStream);
}
}
}
archive.Dispose();
using (var fileStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write))
{
var zipFileMemoryStream = new MemoryStream(memoryStream.ToArray());
zipFileMemoryStream.CopyTo(fileStream);
zipFileMemoryStream.Flush();
fileStream.Close();
zipFileMemoryStream.Close();
}
}
}
I had the same problem... In this case I just needed to move the ToArray() (byte[]) from MemoryStream outside the using (var zipArchive = new ZipArchive...
I think it is necessary for using related to ZipArchive to completely close and dispose of the file before converting it into a byte array
I'm not sure where and what am I doing wrong, but the zip that I'm creating using DotNetZip library, is creating a zip file whose contents are blank. Or the size of file in zip is showing as 0Kb and unable to open it.
Code:
public static async Task DotNetZipFileAsync(MemoryStream stream, string bucket, List<List<string>> pdfFileSet, IAmazonS3 s3Client)
{
using Ionic.Zip.ZipFile zip = new ZipFile();
foreach (var pdfFile in pdfFileSet)
{
foreach (var file in pdfFile)
{
GetObjectRequest request = new GetObjectRequest
{
BucketName = bucket,
Key = file
};
using GetObjectResponse response = await s3Client.GetObjectAsync(request);
using Stream responseStream = response.ResponseStream;
ZipEntry zipEntry = zip.AddEntry(file.Split('/')[^1], responseStream);
await responseStream.CopyToAsync(stream);
}
}
zip.Save(stream);
stream.Seek(0,SeekOrigin.Begin);
await stream.CopyToAsync(new FileStream(#"C:\LocalRepo\Temp.zip", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite));
}
}
}
Your code has at least two problems:
The read stream is completely consumed by the await responseStream.CopyToAsync(stream). You could rewind the responseStream to cope with this, but saving the data into the memory stream is completely useless.
The response stream is disposed before zip.Save is called.
What you could do: keep the streams open until Save is called and dispose them afterwards. As Alexey Rumyantsev discovered (see comments), also the GetObjectResponse objects need to be kept until the ZIP file is saved.
using Ionic.Zip.ZipFile zip = new ZipFile();
var disposables = List<IDisposable>();
try
{
foreach (var pdfFile in pdfFileSet)
{
foreach (var file in pdfFile)
{
GetObjectRequest request = new GetObjectRequest
{
BucketName = bucket,
Key = file
};
var response = await s3Client.GetObjectAsync(request);
disposables.Add(response);
var responseStream = response.ResponseStream;
disposables.Add(responseStream);
ZipEntry zipEntry = zip.AddEntry(file.Split('/')[^1], responseStream);
}
}
using var fileStream = new FileStream(#"C:\LocalRepo\Temp.zip", FileMode.Create, FileAccess.Write);
zip.Save(fileStream);
}
finally
{
foreach (var disposable in disposables)
{
disposable.Dispose();
}
}
The documentation has some hints ony how this could be made smarter.
public static async Task DotNetZipFileAsync(string bucket, List<List<string>> pdfFileSet, IAmazonS3 s3Client)
{
int read;
using Ionic.Zip.ZipFile zip = new ZipFile();
byte[] buffer = new byte[16 * 1024];
foreach (var pdfFile in pdfFileSet)
{
foreach (var file in pdfFile)
{
GetObjectRequest request = new GetObjectRequest
{
BucketName = bucket,
Key = file
};
using GetObjectResponse response = await s3Client.GetObjectAsync(request);
using Stream responseStream = response.ResponseStream;
using (MemoryStream ms = new MemoryStream())
{
while ((read = responseStream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
zip.AddEntry(file.Split('/')[^1], ms.ToArray());
}
}
}
using var fileStream = new FileStream(#"C:\LocalRepo\Temp.zip", FileMode.Create, FileAccess.Write);
zip.Save(fileStream);
}
I am creating a zip file that, appears, valid but is always invalid after I have put it to a Amazon S3 bucket. I am using System.IO.Compression for the task and AmazonS3Client for uploading:
private byte[] GenerateZipFile(string tenant)
{
byte[] zipData;
var results = QueryAggregateTable(tenant);
using (var memoryStream = new MemoryStream())
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var item in results)
{
var archiveEntry = archive.CreateEntry($"{item.RowKey:D3}.json", CompressionLevel.Fastest);
using (var entryStream = archiveEntry.Open())
{
var entryBytes = Encoding.UTF8.GetBytes(item.Data);
entryStream.Write(entryBytes, 0, item.Data.Length);
}
}
zipData = memoryStream.ToArray();
}
return zipData;
}
and
private async Task UploadToAmazon(byte[] zipData, string tenant)
{
var bucketName = _config["egestionBucketName"];
var configCreds = _config["egestionAwsCredentials"].Split(":");
var awsCreds = new BasicAWSCredentials(configCreds[0], configCreds[1]);
var awsRegion = Amazon.RegionEndpoint.GetBySystemName(_config["egestionRegionEndpointSystemName"]);
var s3Client = new AmazonS3Client(awsCreds, awsRegion);
using (var stream = new MemoryStream(zipData))
{
var putRequest = new PutObjectRequest
{
BucketName = bucketName,
Key = $"{tenant}-{DateTime.UtcNow.ToString("s")}.zip",
InputStream = stream,
CannedACL = S3CannedACL.BucketOwnerFullControl
};
await s3Client.PutObjectAsync(putRequest);
}
}
The byte array looks good after returning from generation and the upload method does, in fact, load a file with the correct name to the bucket. When I attempt to download the file to check it I cannot open it with a message stating it is invalid.
I have had some problems with async/await and suspect it may be something related but there is no non async option for PutObject that I can find. Any help appreciated.
This is not an async-await issue.
The bytes from the memory stream are being collected before the archive has had a chance to write all the data to the stream. The uploaded archive is incomplete and therefore invalid when downloaded.
Move
zipData = memoryStream.ToArray();
to outside of the archive using block so that any buffered data is flushed to the backing stream when the archive is disposed.
//...
using (var memoryStream = new MemoryStream()) {
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
foreach (var item in results) {
var archiveEntry = archive.CreateEntry($"{item.RowKey:D3}.json", CompressionLevel.Fastest);
using (var entryStream = archiveEntry.Open()) {
var entryBytes = Encoding.UTF8.GetBytes(item.Data);
entryStream.Write(entryBytes, 0, entryBytes.Length);
}
}
}//Archive disposed and pushed any remaining buffered data to the stream.
zipData = memoryStream.ToArray();
}
//...
I need to create a zip file in memory, then send the zip file to the client. However, there are cases where the created zip file will need to contain other zip files that were also generated in memory. For instance, the file structure might look like this:
SendToClient.zip
InnerZip1.zip
File1.xml
File2.xml
InnerZip2.zip
File3.xml
File4.xml
I've been attempting to use the System.IO.Compression.ZipArchive library. I cannot use the System.IO.Compression.ZipFile library because my project's version of .NET is not compatible with it.
Here's an example of what I've tried.
public Stream GetMemoryStream() {
var memoryStream = new MemoryStream();
string fileContents = "Lorem ipsum dolor sit amet";
string entryName = "Lorem.txt";
string innerZipName = "InnerZip.zip";
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
ZipArchiveEntry entry = archive.CreateEntry(Path.Combine(innerZipName, entryName), CompressionLevel.Optimal);
using (var writer = new StreamWriter(entry.Open())) {
writer.Write(fileContents);
}
}
return memoryStream
}
However, this just puts Lorem.txt in a folder called "Inner.zip" (instead of in an actual zip file).
I can create an empty inner zip file if I create an entry called "Inner.zip" without writing to it. I can't add anything to it, though, and writing to an entry called "Inner.zip\Lorem.txt" afterward just creates a folder again (alongside the identically named empty .zip file).
I've also tried creating a separate archive, serializing it with a memory stream, then writing that to the original archive as a .zip.
public Stream CreateOuterZip() {
var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
ZipArchiveEntry entry = archive.CreateEntry("Outer.zip", CompressionLevel.NoCompression);
using (var writer = new BinaryWriter(entry.Open())) {
writer.Write(GetMemoryStream().ToArray());
}
}
return memoryStream;
}
This just creates an invalid .zip file that windows doesn't know how to open, though.
Thanks in advance!
So I created a FileStream instead of a MemoryStream so the code can be tested easier
public static Stream CreateOuterZip()
{
string fileContents = "Lorem ipsum dolor sit amet";
// Final zip file
var fs = new FileStream(
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SendToClient.zip"), FileMode.OpenOrCreate);
// Create inner zip 1
var innerZip1 = new MemoryStream();
using (var archive = new ZipArchive(innerZip1, ZipArchiveMode.Create, true))
{
var file1 = archive.CreateEntry("File1.xml");
using (var writer = new BinaryWriter(file1.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
var file2 = archive.CreateEntry("File2.xml");
using (var writer = new BinaryWriter(file2.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
}
// Create inner zip 2
var innerZip2 = new MemoryStream();
using (var archive = new ZipArchive(innerZip2, ZipArchiveMode.Create, true))
{
var file3 = archive.CreateEntry("File3.xml");
using (var writer = new BinaryWriter(file3.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
var file4 = archive.CreateEntry("File4.xml");
using (var writer = new BinaryWriter(file4.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
}
using (var archive = new ZipArchive(fs, ZipArchiveMode.Create, true))
{
// Create inner zip 1
var innerZipEntry = archive.CreateEntry("InnerZip1.zip");
innerZip1.Position = 0;
using (var s = innerZipEntry.Open())
{
innerZip1.WriteTo(s);
}
// Create inner zip 2
var innerZipEntry2 = archive.CreateEntry("InnerZip2.zip");
innerZip2.Position = 0;
using (var s = innerZipEntry2.Open())
{
innerZip2.WriteTo(s);
}
}
fs.Close();
return fs; // The file is written, can probably just close this
}
You can obviously modify this method to return a MemoryStream, or change the method to Void to just have the zip file written out to disk
You should create ZipArchive for internal zip file also. Write it to stream (memorystream). And after write this stream as general stream into main zip.
static Stream Inner() {
var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
var demoFile = archive.CreateEntry("foo2.txt");
using (var entryStream = demoFile.Open())
using (var streamWriter = new StreamWriter(entryStream)) {
streamWriter.Write("Bar2!");
}
}
return memoryStream;
}
static void Main(string[] args) {
using (var memoryStream = new MemoryStream()) {
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
var demoFile = archive.CreateEntry("foo.txt");
using (var entryStream = demoFile.Open())
using (var streamWriter = new StreamWriter(entryStream)) {
streamWriter.Write("Bar!");
}
var zip = archive.CreateEntry("inner.zip");
using (var entryStream = zip.Open()) {
var inner = Inner();
inner.Seek(0, SeekOrigin.Begin);
inner.CopyTo(entryStream);
}
}
using (var fileStream = new FileStream(#"d:\test.zip", FileMode.Create)) {
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
Thanks to this answer.
I'm creating a Zip file from files I'm downloading from the internet in bytes[],
but I have an Issue, I don't know what I'm doing wrong... I generate the zip file but it's corrupted, the file size is correct(is not 0).
Could you help me please?
Maybe I didn't understand it well.
public <ActionResult> SomeFunction()
{
var invoices = GetInvoices();
WebClient client = new WebClient();
byte[] zipBytes = null;
using (var compressedFileStream = new MemoryStream())
{
using (ZipArchive zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, leaveOpen: true))
{
foreach (var invoice in invoices)
{
// This has correct values.
byte[] fileBytes = client.DownloadData(invoice.XmlUri);
// Create the instance of the file.
var zipEntry = zipArchive.CreateEntry(invoice.XmlFileName);
// Get the stream of the file.
using (var entryStream = new MemoryStream(fileBytes))
// Get the Stream of the zipEntry
using (var zipEntryStream = zipEntry.Open())
{
// Adding the file to the zip file.
entryStream.CopyTo(zipEntryStream);
}
}
}
zipBytes = compressedFileStream.ToArray();
}
return File(zipBytes , System.Net.Mime.MediaTypeNames.Application.Octet, "test.zip");
}
Move
zipBytes = compressedFileStream.ToArray();
To after the archive has been disposed so that all data is flushed to the underlying stream.
public <ActionResult> SomeFunction() {
var invoices = GetInvoices();
WebClient client = new WebClient();
byte[] zipBytes = null;
using (var compressedFileStream = new MemoryStream()) {
using (ZipArchive zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, leaveOpen: true)) {
foreach (var invoice in invoices) {
// This has correct values.
byte[] fileBytes = client.DownloadData(invoice.XmlUri);
// Create the instance of the file.
var zipEntry = zipArchive.CreateEntry(invoice.XmlFileName);
// Get the stream of the file.
using (var entryStream = new MemoryStream(fileBytes))
// Get the Stream of the zipEntry
using (var zipEntryStream = zipEntry.Open()) {
// Adding the file to the zip file.
entryStream.CopyTo(zipEntryStream);
}
}
}
zipBytes = compressedFileStream.ToArray();
}
return File(zipBytes , System.Net.Mime.MediaTypeNames.Application.Octet, "test.zip");
}
Reference ZipArchive.Dispose()
This method finishes writing the archive and releases all resources used by the ZipArchive object.
Unless you construct the object by using the ZipArchive(Stream, ZipArchiveMode, Boolean) constructor overload and set its leaveOpen parameter to true, all underlying streams are closed and no longer available for subsequent write operations.
When you are finished using this instance of ZipArchive, call Dispose() to release all resources used by this instance. You should eliminate further references to this ZipArchive instance so that the garbage collector can reclaim the memory of the instance instead of keeping it alive for finalization.