creating a zip file from an object directly without disk IO - c#

I am writing a REST API which will take in a JSON request object. The request object will have to be serialized to a file in JSON format; the file has to be compressed into a zip file and the ZIP file has to be posted to another service, for which I would have to deserialize the ZIP file. All this because the service I have to call expects me to post data as ZIP file. I am trying to see if I can avoid disk IO. Is there a way to directly convert the object into a byte array representing ZIP content in-memory instead of all the above steps?
Note : I'd prefer accomplishing this using .net framework libraries (as against external libraries)

Yes, it is possible to create a zip file completely on memory, here is an example using SharpZip Library (Update: A sample using ZipArchive added at the end):
public static void Main()
{
var fileContent = Encoding.UTF8.GetBytes(
#"{
""fruit"":""apple"",
""taste"":""yummy""
}"
);
var zipStream = new MemoryStream();
var zip = new ZipOutputStream(zipStream);
AddEntry("file0.json", fileContent, zip); //first file
AddEntry("file1.json", fileContent, zip); //second file (with same content)
zip.Close();
//only for testing to see if the zip file is valid!
File.WriteAllBytes("test.zip", zipStream.ToArray());
}
private static void AddEntry(string fileName, byte[] fileContent, ZipOutputStream zip)
{
var zipEntry = new ZipEntry(fileName) {DateTime = DateTime.Now, Size = fileContent.Length};
zip.PutNextEntry(zipEntry);
zip.Write(fileContent, 0, fileContent.Length);
zip.CloseEntry();
}
You can obtain SharpZip using Nuget command PM> Install-Package SharpZipLib
Update:
Note : I'd prefer accomplishing this using .net framework libraries (as against external libraries)
Here is an example using Built-in ZipArchive from System.IO.Compression.Dll
public static void Main()
{
var fileContent = Encoding.UTF8.GetBytes(
#"{
""fruit"":""apple"",
""taste"":""yummy""
}"
);
var zipContent = new MemoryStream();
var archive = new ZipArchive(zipContent, ZipArchiveMode.Create);
AddEntry("file1.json",fileContent,archive);
AddEntry("file2.json",fileContent,archive); //second file (same content)
archive.Dispose();
File.WriteAllBytes("testa.zip",zipContent.ToArray());
}
private static void AddEntry(string fileName, byte[] fileContent,ZipArchive archive)
{
var entry = archive.CreateEntry(fileName);
using (var stream = entry.Open())
stream.Write(fileContent, 0, fileContent.Length);
}

You could use the GZipStream class along with MemoryStream.
A quick example:
using System.IO;
using System.IO.Compression;
//Put JSON into a MemoryStream
var theJson = "Your JSON Here";
var jsonStream = new MemoryStream();
var jsonStreamWriter = new StreamWriter(jsonStream);
jsonStreamWriter.Write(theJson);
jsonStreamWriter.Flush();
//Reset stream so it points to the beginning of the JSON
jsonStream.Seek(0, System.IO.SeekOrigin.Begin);
//Create stream to hold your zipped JSON
var zippedStream = new MemoryStream();
//Zip JSON and put it in zippedStream via compressionStream.
var compressionStream = new GZipStream(zippedStream, CompressionLevel.Optimal);
jsonStream.CopyTo(compressionStream);
//Reset zipped stream to point at the beginning of data
zippedStream.Seek(0, SeekOrigin.Begin);
//Get ByteArray with zipped JSON
var zippedJsonBytes = zippedStream.ToArray();

You should try the ZipArchive Class streaming to a MemoryStream Class

Yes. You can return it as a binary stream. Depending on the language, you can use special libraries. You will also need libraries on the client.

Related

Extract tgz file in memory and access files in C#

I have a service that downloads a *.tgz file from a remote endpoint. I use SharpZipLib to extract and write the content of that compressed archive to disk. But now I want to prevent writing the files to disk (because that process doesn't have write permissions on that disk) and keep them in memory.
How can I access the decompressed files from memory? (Let's assume the archive holds simple text files)
Here is what I have so far:
public void Decompress(byte[] byteArray)
{
Stream inStream = new MemoryStream(byteArray);
Stream gzipStream = new GZipInputStream(inStream);
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream);
tarArchive.ExtractContents(#".");
tarArchive.Close();
gzipStream.Close();
inStream.Close();
}
Check this and this out.
Turns out, ExtractContents() works by iterating over TarInputStream. When you create your TarArchive like this:
TarArchive.CreateInputTarArchive(gzipStream);
it actually wraps the stream you're passing into a TarInputStream. Thus, if you want more fine-grained control over how you extract files, you must use TarInputStream directly.
See, if you can iterate over files, directories and actual file contents like this:
Stream inStream = new MemoryStream(byteArray);
Stream gzipStream = new GZipInputStream(inStream);
using (var tarInputStream = new TarInputStream(gzipStream))
{
TarEntry entry;
while ((entry = tarInputStream.GetNextEntry()) != null)
{
var fileName = entry.Name;
using (var fileContents = new MemoryStream())
{
tarInputStream.CopyEntryContents(fileContents);
// use entry, fileName or fileContents here
}
}
}

Read file content using TextReader requires full path of file - Beginner

I have taken a IFormFile as an input and I need to read its content.
And, I am going to read the file content using the following code. However, File.OpenText requires a file path as input.
TextReader fileReader = File.OpenText(pathToFile)
Since I am not going to save the file in a physical location I have no file path with me at hand to pass to File.OpenText as an input.
How can I solve this ?
public async Task<string> PdfFileSave(IFormFile file, string nameOfThePerson)
{
TextReader fileReader = File.OpenText(pathToFile);
}
Try this:
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
byte[] fileBytes = memoryStream.ToArray();
string text = Encoding.UTF8.GetString(fileBytes);
}
you can use File.OpenReadStream():
public async Task<string> PdfFileSave(IFormFile file, string nameOfThePerson)
{
TextReader fileReader = new StreamReader( file.OpenReadStream() );
}

Reading zip file from byte array using Ionic.zip

I have a piece of code that allows to decompress a byte array:
public static byte[] Decompress(this byte[] data)
{
using (ZipFile zout = ZipFile.Read(data))
{
ZipEntry entry = zout.FirstOrDefault();
Assert.ObjectIsNotNull(entry, "Unable to find default ZIP entry");
MemoryStream zos = new MemoryStream();
entry.Extract(zos);
return zos.ToArray();
}
}
I upgraded to the latest version of Ionic.zip and now I am getting the following error:
Cannot convert byte[] to string.
The overload ZipFile.Read(byte[]) is no longer available in the most recent version.
How can I read a zip file from a byte array?
The ZipFile.Read method takes either a filename or a stream to read, so you need to provide a stream for it to read:
using (MemoryStream stream = new MemoryStream(data))
using (ZipFile zout = ZipFile.Read(stream))
{
// ....
You can use the built-in ZipArchive class in System.IO.Commpression.
using(var stream = new MemoryStream(data))
{
using(var archive = new ZipArchive(stream))
{
// Use the archive
}
}
ZipArchive
https://msdn.microsoft.com/en-us/library/hh158268(v=vs.110).aspx
MemoryStream
https://msdn.microsoft.com/en-us/library/e55f3s5k(v=vs.110).aspx
You will need to add a reference to System.IO.Compression, it is not in mscorlib.

sharpziplib compressed files to be uncompressed externally

I have a scenario where by I want to zip an email attachment using SharpZipLib. Then the end user will open the attachment and will unzip the attached file.
Will the file originally zipped file using SharpZipLib be easily unzipped by other programs for my end user?
It depends on how you use SharpZipLib. There is more than one way to compress the data with this library.
Here is example of method that will create a zip file that you will be able to open in pretty much any zip aware application:
private static byte[] CreateZip(byte[] fileBytes, string fileName)
{
using (var memoryStream = new MemoryStream())
using (var zipStream = new ZipOutputStream(memoryStream))
{
var crc = new Crc32();
crc.Reset();
crc.Update(fileBytes);
var zipEntry =
new ZipEntry(fileName)
{
Crc = crc.Value,
DateTime = DateTime.Now,
Size = fileBytes.Length
};
zipStream.PutNextEntry(zipEntry);
zipStream.Write(fileBytes, 0, fileBytes.Length);
zipStream.Finish();
zipStream.Close();
return memoryStream.ToArray();
}
}
Usage:
var fileBytes = File.ReadAllBytes(#"C:/1.xml");
var zipBytes = CreateZip(fileBytes, "MyFile.xml");
File.WriteAllBytes(#"C:/2.zip", zipBytes);
This CreateZip method is optimized for the cases when you already have bytes in memory and you just want to compress them and send without even saving to disk.

Create Zip archive from multiple in memory files in C#

Is there a way to create a Zip archive that contains multiple files, when the files are currently in memory? The files I want to save are really just text only and are stored in a string class in my application. But I would like to save multiple files in a single self-contained archive. They can all be in the root of the archive.
It would be nice to be able to do this using SharpZipLib.
Use ZipEntry and PutNextEntry() for this. The following shows how to do it for a file, but for an in-memory object just use a MemoryStream
FileStream fZip = File.Create(compressedOutputFile);
ZipOutputStream zipOStream = new ZipOutputStream(fZip);
foreach (FileInfo fi in allfiles)
{
ZipEntry entry = new ZipEntry((fi.Name));
zipOStream.PutNextEntry(entry);
FileStream fs = File.OpenRead(fi.FullName);
try
{
byte[] transferBuffer[1024];
do
{
bytesRead = fs.Read(transferBuffer, 0, transferBuffer.Length);
zipOStream.Write(transferBuffer, 0, bytesRead);
}
while (bytesRead > 0);
}
finally
{
fs.Close();
}
}
zipOStream.Finish();
zipOStream.Close();
Using SharpZipLib for this seems pretty complicated. This is so much easier in DotNetZip. In v1.9, the code looks like this:
using (ZipFile zip = new ZipFile())
{
zip.AddEntry("Readme.txt", stringContent1);
zip.AddEntry("readings/Data.csv", stringContent2);
zip.AddEntry("readings/Index.xml", stringContent3);
zip.Save("Archive1.zip");
}
The code above assumes stringContent{1,2,3} contains the data to be stored in the files (or entries) in the zip archive. The first entry is "Readme.txt" and it is stored in the top level "Directory" in the zip archive. The next two entries are stored in the "readings" directory in the zip archive.
The strings are encoded in the default encoding. There is an overload of AddEntry(), not shown here, that allows you to explicitly specify the encoding to use.
If you have the content in a stream or byte array, not a string, there are overloads for AddEntry() that accept those types. There are also overloads that accept a Write delegate, a method of yours that is invoked to write data into the zip. This works for easily saving a DataSet into a zip file, for example.
DotNetZip is free and open source.
This function should create a byte array from a stream of data: I've created a simple interface for handling files for simplicity
public interface IHasDocumentProperties
{
byte[] Content { get; set; }
string Name { get; set; }
}
public void CreateZipFileContent(string filePath, IEnumerable<IHasDocumentProperties> fileInfos)
{
using (var memoryStream = new MemoryStream())
{
using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach(var fileInfo in fileInfos)
{
var entry = zipArchive.CreateEntry(fileInfo.Name);
using (var entryStream = entry.Open())
{
entryStream.Write(fileInfo.Content, 0, fileInfo.Content.Length);
}
}
}
using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, System.IO.FileAccess.Write))
{
memoryStream.Position = 0;
memoryStream.CopyTo(fileStream);
}
}
}
Yes, you can use SharpZipLib to do this - when you need to supply a stream to write to, use a MemoryStream.
I come across this problem, using the MSDN example I created this class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Packaging;
using System.IO;
public class ZipSticle
{
Package package;
public ZipSticle(Stream s)
{
package = ZipPackage.Open(s, FileMode.Create);
}
public void Add(Stream stream, string Name)
{
Uri partUriDocument = PackUriHelper.CreatePartUri(new Uri(Name, UriKind.Relative));
PackagePart packagePartDocument = package.CreatePart(partUriDocument, "");
CopyStream(stream, packagePartDocument.GetStream());
stream.Close();
}
private static void CopyStream(Stream source, Stream target)
{
const int bufSize = 0x1000;
byte[] buf = new byte[bufSize];
int bytesRead = 0;
while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
target.Write(buf, 0, bytesRead);
}
public void Close()
{
package.Close();
}
}
You can then use it like this:
FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);
ZipSticle zip = new ZipSticle(str);
zip.Add(File.OpenRead("C:/Users/C0BRA/SimpleFile.txt"), "Some directory/SimpleFile.txt");
zip.Add(File.OpenRead("C:/Users/C0BRA/Hurp.derp"), "hurp.Derp");
zip.Close();
str.Close();
You can pass a MemoryStream (or any Stream) to ZipSticle.Add such as:
FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);
ZipSticle zip = new ZipSticle(str);
byte[] fileinmem = new byte[1000];
// Do stuff to FileInMemory
MemoryStream memstr = new MemoryStream(fileinmem);
zip.Add(memstr, "Some directory/SimpleFile.txt");
memstr.Close();
zip.Close();
str.Close();
Note this answer is outdated; since .Net 4.5, the ZipArchive class allows zipping files in-memory. See johnny 5's answer below for how to use it.
You could also do it a bit differently, using a Serializable object to store all strings
[Serializable]
public class MyStrings {
public string Foo { get; set; }
public string Bar { get; set; }
}
Then, you could serialize it into a stream to save it.
To save on space you could use GZipStream (From System.IO.Compression) to compress it. (note: GZip is stream compression, not an archive of multiple files).
That is, of course if what you need is actually to save data, and not zip a few files in a specific format for other software.
Also, this would allow you to save many more types of data except strings.
I was utilizing Cheeso's answer by adding MemoryStreams as the source of the different Excel files. When I downloaded the zip, the files had nothing in them. This could be the way we were getting around trying to create and download a file over AJAX.
To get the contents of the different Excel files to be included in the Zip, I had to add each of the files as a byte[].
using (var memoryStream = new MemoryStream())
using (var zip = new ZipFile())
{
zip.AddEntry("Excel File 1.xlsx", excelFileStream1.ToArray());
zip.AddEntry("Excel File 2.xlsx", excelFileStream2.ToArray());
// Keep the file off of disk, and in memory.
zip.Save(memoryStream);
}
Use a StringReader to read from your string objects and expose them as Stream s.
That should make it easy to feed them to your zip-building code.

Categories

Resources