I am attempt to create a zip file in memory, from multiple other zip files read from file streams. It appears that it is able to read the files correctly, and create a zip file; however, when the response is being created all content headers get placed into invalidHeaders. This causes the download to never occur, and instead a bad response page is loaded.
using (var memoryStream = new MemoryStream())
{
// Gather all zips into a single zip file
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach(var zipFile in zipFiles)
{
archive.CreateEntryFromFile(zipFile.ZipFilePath, Path.GetFileName(zipFile.ZipFilePath));
}
}
// Now we have our memory stream with our zip
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new StreamContent(memoryStream);
message.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
message.Content.Headers.ContentDisposition.FileName = "AllZIPFiles.zip";
message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
message.Content.Headers.ContentLength = memoryStream.Length;
// Content headers placed into invalidHeaders?
return ResponseMessage(message);
}
After the ZipArchive does its work, the position of the stream will be at the end of the stream.
Before sending such stream as a response, make sure that you set the stream position to 0 like this:
memoryStream.Position = 0;
Related
Hi im using the code in this blogpost :
https://blog.stephencleary.com/2016/11/streaming-zip-on-aspnet-core.html
In order to stream a zip file with .Net core. I made it work but since i did not add the content-length header in the response when i donwload the zip file, it won't show the download progress in chrome. Since i know in advance the zip file size I can actually set the content-length header, with the SetHeadersAndLog method
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.internal.fileresultexecutorbase.setheadersandlog?view=aspnetcore-2.0
but when i do so I have the following error :
System.InvalidOperationException: Response Content-Length mismatch: too many bytes written (144144633 of 144144627).
Any idea why the response is not the same length as the zip file ?
Here's the code to serve the file:
this._httpContext.Response.ContentType = "application/octet-stream";
this._httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
this._httpContext.Response.ContentLength = estimatedFileSize;
FileCallbackResult result = new FileCallbackResult(new MediaTypeHeaderValue("application/octet-stream"), estimatedFileSize, async (outputStream, _) =>
{
using (ZipArchive zip = new ZipArchive(outputStream, ZipArchiveMode.Create, false))
{
foreach (string filepath in Directory.EnumerateFiles(existingDirectory.FullName, "*.*", SearchOption.AllDirectories))
{
string relativepath = filepath.Replace(existingDirectory.FullName + "\\", string.Empty);
ZipArchiveEntry zipEntry = zip.CreateEntry(relativepath, CompressionLevel.Fastest);
using (Stream zipstream = zipEntry.Open())
{
using (Stream stream = new FileStream(filepath, FileMode.Open))
{
await stream.CopyToAsync(zipstream);
}
}
}
}
})
{
FileDownloadName = $"{package.FileName}.zip",
};
You need to seek the stream back to the beginning.
I am currently working on integrating Amazon Prime on our system and being stuck at getting the label back as ZPL format.
Basically, Amazon returns a base64 string, we will need to convert that string to a byte array, then save that array as a *.gzip file. From that gzip file, we can extract the content and get the zpl label content.
My question is, how we can do all of above without storing any temp files to system. I have researched some solutions but none is working for me.
My current code as below:
var str = "base64string";
var label = Convert.FromBase64String(str);
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var demoFile = archive.CreateEntry("label.zip");
var entryStream = demoFile.Open();
using (var bw = new BinaryWriter(entryStream))
{
bw.Write(label);
}
var data = new MemoryStream();
using (var zip = ZipFile.Read(entryStream))
{
zip["label"].Extract(data);
}
data.Seek(0, SeekOrigin.Begin);
entryStream.Close();
}
using (var fileStream = new FileStream(#"D:\test.zip", FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
If I save the file as test.zip, I can successfully get the label back. But if I try to extract it directly to another stream, I get an error
A stream from ZipArchiveEntry has been disposed
I've done something similar, taking PNG label data from a zipped web response. This is how I went about that
using (WebClient webClient = new WebClient())
{
// Download. Expect this to be a zip file
byte[] data = webClient.DownloadData(urlString);
MemoryStream memoryStream = new MemoryStream(data);
ZipArchive zipArchive = new ZipArchive(memoryStream);
foreach (var zipEntry in zipArchive.Entries)
{
// Can check file name here and ignore anything in zip we're not expecting
if (!zipEntry.Name.EndsWith(".png")) continue;
// Open zip entry as stream
Stream extractedFile = zipEntry.Open();
// Convert stream to memory stream
MemoryStream extractedMemoryStream = new MemoryStream();
extractedFile.CopyTo(extractedMemoryStream);
// At this point the extractedMemoryStream is a sequence of bytes containing image data.
// In this test project I'm pushing that into a bitmap image, just to see something on screen, but could as easily be written to a file or passed for storage to sql or whatever.
BitmapDecoder decoder = PngBitmapDecoder.Create(extractedMemoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapFrame frame = decoder.Frames.First();
frame.Freeze();
this.LabelImage.Source = frame;
}
}
I was overthinking it. I finally found a simple way to do it. We just need to convert that base64 string to bytes array and use GzipStream to directly decompress it. I leave the solution here in case someone needs it. Thanks!
var label = Convert.FromBase64String(str);
using (var compressedStream = new MemoryStream(label))
using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var resultStream = new MemoryStream())
{
zipStream.CopyTo(resultStream);
return resultStream.ToArray();
}
I'm trying to return a file in a ASP.NET Web API Controller. This file is a dynamically-generated PDF saved in a MemoryStream.
The client (browser) receives the file successfully, but when I open the file, I see that all the pages are totally blank.
The thing is that if I take the same MemoryStream and write it to a file, this disk file is displayed correctly, so I assume that the problem is related to the file transfer via Web.
My controller looks like this:
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
memStream.Position = 0;
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(memStream.ToArray()); //OR: new StreamContent(memStream);
return result;
}
Just to try, if I write the stream to disk, it's displayed correctly:
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
memStream.Position = 0;
using (var fs = new FileStream("C:\\Temp\\test.pdf", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
memStream.CopyTo(fs);
}
return null;
}
The differences are:
PDF saved on disk: 34KB
PDF transferred via web: 60KB (!)
If I compare both files contents, the main differences are:
File Differences
On the left is the PDF transferred via web; on the right, the PDF saved to disk.
Is there something wrong with my code?
Maybe something related to encodings?
Thanks!
Well, it turned out to be a client (browser) problem, not a server problem. I'm using AngularJS in the frontend, so when the respose was received, Angular automatically converted it to a Javascript string. In that conversion, the binary contents of the file were somehow altered...
Basically it was solved by telling Angular not to convert the response to a string:
$http.get(url, { responseType: 'arraybuffer' })
.then(function(response) {
var dataBlob = new Blob([response.data], { type: 'application/pdf'});
FileSaver.saveAs(dataBlob, 'myFile.pdf');
});
And then saving the response as a file, helped by the Angular File Saver service.
I guess you should set ContentDisposition and ContentType like this:
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(memStream.ToArray())
};
//this line
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "YourName.pdf"
};
//and this line
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return result;
}
Try this
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
//get buffer
var buffer = memStream.GetBuffer();
//content length for header
var contentLength = buffer.Length;
var statuscode = HttpStatusCode.OK;
var response = Request.CreateResponse(statuscode);
response.Content = new StreamContent(new MemoryStream(buffer));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = contentLength;
ContentDispositionHeaderValue contentDisposition = null;
if (ContentDispositionHeaderValue.TryParse("inline; filename=my_filename.pdf", out contentDisposition)) {
response.Content.Headers.ContentDisposition = contentDisposition;
}
return response;
}
I am trying to download a zipped file from the server and trying to show the content of each files in zipped folder to the view.
I wrote a separate code where the file is on my laptop and I ran across each file and dislpayed the content such as
static void Main(string[] args)
{
string filePath = "C:\\ACL Data\\New folder\\files.zip";
var zip= new ZipInputStream(File.OpenRead(filePath));
var filestream=new FileStream(filePath,FileMode.Open,FileAccess.Read);
ZipFile zipfile = new ZipFile(filestream);
ZipEntry item;
while ((item = zip.GetNextEntry()) != null)
{
Console.WriteLine(item.Name);
using (StreamReader s = new StreamReader(zipfile.GetInputStream(item)))
{
Console.WriteLine(s.ReadToEnd());
}
}
Console.Read();
}
I am using sharplibzip library to implement this
This is the case when the zip file is located locally in the system. My next task scenario is what if the zipped file is located on the server. I am figuring out the way to implement it, below is the code what I assume should work
static void Main(string[] args)
{
string url = "https://test/code/304fd9c6-7e53-42a2-845a-624608bfd2ce.zip";
WebRequest webRequest = WebRequest.Create(url);
webRequest.Method = "GET";
WebResponse webResponse = webRequest.GetResponse();
var zip = new ZipInputStream(webResponse.GetResponseStream());
ZipEntry item1;
//var zip= new ZipInputStream(File.OpenRead(filePath));
var filestream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
ZipFile zipfile = new ZipFile(filestream);
ZipEntry item;
while ((item = zip.GetNextEntry()) != null)
{
Console.WriteLine(item.Name);
using (StreamReader s = new StreamReader(zipfile.GetInputStream(item)))
{
Console.WriteLine(s.ReadToEnd());
}
}
Console.Read();
}
I am stuck at this part: var filestream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
This expect the first parameter to be path of the zip file. Since in the new scenario zip file is located remotely on the server. What should be the parameter in this case?
Your original code opens the stream twice on the following rows, which I think is causing some confusion:
var zip= new ZipInputStream(File.OpenRead(filePath));
var filestream=new FileStream(filePath,FileMode.Open,FileAccess.Read);
There is an overload to the ZipFile constructor that takes "any" Stream rather than specifically a FileStream, which you - unsurprisingly - can only create for files.
However, you cannot use the stream returned by GetResponseStream directly, because it's CanSeek property is false. This is because it's a NetworkStream, which can only be read once from beginning to end. SharpZipLib needs random access to read the file contents.
Depending on the size of the ZIP file, loading it in memory may be an option. If you expect large files, writing it to a temporary file may be better.
This should do the trick, without using both ZipInputStream and ZipFile, by enumerating through ZipFile instead:
string url = "https://test/code/304fd9c6-7e53-42a2-845a-624608bfd2ce.zip";
WebRequest webRequest = WebRequest.Create(url);
webRequest.Method = "GET";
WebResponse webResponse = webRequest.GetResponse();
using (var responseStream = webResponse.GetResponseStream())
using (var ms = new MemoryStream())
{
// Copy entire file into memory. Use a file if you expect a lot of data
responseStream.CopyTo(ms);
var zipFile = new ZipFile(ms);
foreach (ZipEntry item in zipFile)
{
Console.WriteLine(item.Name);
using (var s = new StreamReader(zipFile.GetInputStream(item)))
{
Console.WriteLine(s.ReadToEnd());
}
}
}
Console.Read();
PS: starting .NET 4.5, there is support for ZIP files built in. See the ZipArchive class.
We have a page that users can download media and we construct a folder structure similar to the following and zip it up and send it back to the user in the response.
ZippedFolder.zip
- Folder A
- File 1
- File 2
- Folder B
- File 3
- File 4
The existing implementation that accomplishes this saves files and directories temporarily to file system and then deletes them at the end. We are trying to get away from doing this and would like to accomplish this entirely in memory.
I am able to successfully create a ZipFile with files in it, but the problem I am running into is creating Folder A and Folder B and adding files to those and then adding those two folders to the Zip File.
How can I do this without saving to the file system?
The code for just saving the file streams to the zip file and then setting the Output Stream on the response is the following.
public Stream CompressStreams(IList<Stream> Streams, IList<string> StreamNames, Stream OutputStream = null)
{
MemoryStream Response = null;
using (ZipFile ZippedFile = new ZipFile())
{
for (int i = 0, length = Streams.Count; i < length; i++)
{
ZippedFile.AddEntry(StreamNames[i], Streams[i]);
}
if (OutputStream != null)
{
ZippedFile.Save(OutputStream);
}
else
{
Response = new MemoryStream();
ZippedFile.Save(Response);
// Move the stream back to the beginning for reading
Response.Seek(0, SeekOrigin.Begin);
}
}
return Response;
}
EDIT We are using DotNetZip for the zipping/unzipping library.
Here's another way of doing it using System.IO.Compression.ZipArchive
public Stream CompressStreams(IList<Stream> Streams, IList<string> StreamNames, Stream OutputStream = null)
{
MemoryStream Response = new MemoryStream();
using (ZipArchive ZippedFile = new ZipArchive(Response, ZipArchiveMode.Create, true))
{
for (int i = 0, length = Streams.Count; i < length; i++)
using (var entry = ZippedFile.CreateEntry(StreamNames[i]).Open())
{
Streams[i].CopyTo(entry);
}
}
if (OutputStream != null)
{
Response.Seek(0, SeekOrigin.Begin);
Response.CopyTo(OutputStream);
}
return Response;
}
and a little test:
using (var write = new FileStream(#"C:\users\Public\Desktop\Testzip.zip", FileMode.OpenOrCreate, FileAccess.Write))
using (var read = new FileStream(#"C:\windows\System32\drivers\etc\hosts", FileMode.Open, FileAccess.Read))
{
CompressStreams(new List<Stream>() { read }, new List<string>() { #"A\One.txt" }, write);
}
re: your comment -- sorry, not sure if it creates something in the background, but you're not creating it yourself to do anything