How to create ZipArchive from files in memory in C#? - c#

Is it somehow possible to create a ZipArchive from the file(s) in memory (and not actually on the disk).
Following is the use case:
Multiple files are received in an IEnumerable<HttpPostedFileBase> variable. I want to zip all these files together using ZipArchive. The problem is that ZipArchive only allows CreateEntryFromFile, which expects a path to the file, where as I just have the files in memory.
Question:
Is there a way to use a 'stream' to create the 'entry' in ZipArchive, so that I can directly put in the file's contents in the zip?
I don't want to first save the files, create the zip (from the saved files' paths) and then delete the individual files.
Here, attachmentFiles is IEnumerable<HttpPostedFileBase>
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var attachment in attachmentFiles)
{
zipArchive.CreateEntryFromFile(Path.GetFullPath(attachment.FileName), Path.GetFileName(attachment.FileName),
CompressionLevel.Fastest);
}
}
...
}

Yes, you can do this, using the ZipArchive.CreateEntry method, as #AngeloReis pointed out in the comments, and described here for a slightly different problem.
Your code would then look like this:
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var attachment in attachmentFiles)
{
var entry = zipArchive.CreateEntry(attachment.FileName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
attachment.InputStream.CopyTo(entryStream);
}
}
}
...
}

First off thanks #Alex for the perfect answer.
Also for the scenario you need to read from file systems :
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in filesAddress)
{
zipArchive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
...
}
with the help of System.IO.Compression.ZipFileExtensions

Related

Mvc - Download multiple files as Zip Error as "Unreadable content" while opening downloaded zipped file

I am Using MVC.Net application and I want to download multiple files as zipped. I have written code with memory stream and ZipArchive in my controller action method. With that code I am able to successfully download the files as zipped folder. But when I unzipped those and trying to open them then I am getting the error as below
opening word document with Microsoft word - Word found unreadable content error
opening image file(.png) - dont support file format error
Here is my controller method code to zip the files
if (sendApplicationFiles != null && sendApplicationFiles.Any())
{
using (var compressedFileStream = new MemoryStream())
{
// Create an archive and store the stream in memory.
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, true))
{
foreach (var file in sendApplicationFiles)
{
// Create a zip entry for each attachment
var zipEntry = zipArchive.CreateEntry(file.FileName);
// Get the stream of the attachment
using (var originalFileStream = new MemoryStream(file.FileData))
using (var zipEntryStream = zipEntry.Open())
{
// Copy the attachment stream to the zip entry stream
originalFileStream.CopyTo(zipEntryStream);
}
}
}
return new FileContentResult(compressedFileStream.ToArray(), "application/zip") { FileDownloadName = "Filename.zip" };
}
}
Expecting the document content should load without error
I did not investigate your code, but this link might help. There are few examples.
https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-compress-and-extract-files

Zip created but no files in it

Can someone tell me what's wrong with my code? I want to zip multiple xml into one file yet the result file is always empty.
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
string[] xmls = Directory.GetFiles(#"c:\temp\test", "*.xml");
foreach (string xml in xmls)
{
var file = zip.CreateEntry(xml);
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write(xml);
}
}
}
using (FileStream fs = new FileStream(#"C:\Temp\test.zip", FileMode.Create))
{
zipStream.Position = 0;
zipStream.CopyTo(fs);
}
}
See the remarks in the documentation (emphasis mine):
The entryName string should reflect the relative path of the entry you want to create within the zip archive. There is no restriction on the string you provide. However, if it is not formatted as a relative path, the entry is created, but you may get an exception when you extract the contents of the zip archive. If an entry with the specified path and name already exists in the archive, a second entry is created with the same path and name.
You are using an absolute path here:
var file = zip.CreateEntry(xml);
My guess is that when you try to open the archive, it is failing silently to show the entries.
Change your code to use the names of the files without their path:
var file = zip.CreateEntry(Path.GetFileName(xml));
As a separate issue, notice that you're just writing the name of the file to the ZIP entry, rather than the actual file. I imagine you want something like this instead:
var zipEntry = zip.CreateEntry(Path.GetFileName(xml));
using (var entryStream = file.Open())
{
using var fileStream = File.OpenRead(xml);
fileStream.CopyTo(entryStream);
}

How to download Azure Blobs by referencing the file?

I want to download files from Azure using C# then stream those into MemoryStream after that return/display to the user in Front-end with a link (Azure URI - which goes to the Azure blob) and the user will be able to see those PDF files in the browser or download them. There are multiple blobs/files in Azure so, I want to loop through each file and download to stream for example: using a foreach.
I'm not sure how can I reference those blobs CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName); as here I could give a name of the specific file but I've multiple files so not sure what to go here "fileName".
Code:
var files = container.ListBlobs();
foreach (var file in files)
{
using (var memoryStream = new MemoryStream())
{
CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
blockBlob.DownloadToStream(memoryStream);
}
}
I'm not sure if I'm looping correcting right now in the code and downloading every blob?
Also, I tried replacing fileName with file.Uri.Segments.Last() -
I guess which gets the name of blobs.
The problem I'm having is that this foreach is just getting me one PDF file whenever I try to use the links in front-end. So, I need to know how can I properly loop through each file and download them?
So, I need to know how can I properly loop through each file and download them?
We can't download the mutiple files from the memory directly. If zip file is acceptable, you could use a compressed file such as a zip file to transfer multiple files instead. The following is my demo code, it works correctly on my side.
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in files)
{
if (file.GetType() != typeof(CloudBlockBlob)) continue;
var blob = (CloudBlockBlob) file;
var entry = zipArchive.CreateEntry(blob.Name, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blob.Name);
blockBlob.DownloadToStream(entryStream);
}
}
}
}

Convert .zip file into byte array and re-create .zip from byte array

I have converted a .zip file into a byte[], and now I am trying to convert the byte[] back to the original .zip file. I am running out of the options that I have tried. Anyone give me a pointer how can I achieve this?
You want the System.IO.Compression.ZipArchive class:
using (ZipArchive zip = ZipFile.Open("test.zip", ZipArchiveMode.Create))
{
var entry = zip.CreateEntry("File Name.txt");
using (StreamWriter sw = new StreamWriter(entry.Open()))
{
sw.Write("Some Text");
}
}
using (ZipArchive zip = ZipFile.Open("test.zip", ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zip.Entries)
{
using (StreamReader sr = new StreamReader(entry.Open()))
{
var result = sr.ReadToEnd();
}
}
}
You probably don't want to read in the raw zip file into a byte array first and then try to decompress it. Instead, access it through this helper method.
Note the use of ZipArchive.Entries to access the sub-files stored in the single zip archive; this tripped me up when first learning to use zip files.

Append text to existing Gzip file

I'm working on a log program, which would dump data into gzip archive.
The first entry would look like this:
using (var fs = File.OpenWrite(logFile))
{
using (var gs = new GZipStream(fs, CompressionMode.Compress))
{
using (var sw = new StreamWriter(gs))
{
sw.WriteLine(logEntry);
}
}
}
Now I want add other lines to that file without having to re-read all file content and than to re-write it in a way that the result can be read with a single GZipStream.
What is the best way to do that?
You can use gzlog.h and gzlog.c from the zlib distribution in the examples directory. They do exactly what you're looking for.

Categories

Resources