I've been trying to get the "new" ZipArchive included in .NET 4.5 (System.IO.Compression.ZipArchive
) to work in a ASP.NET site. But it seems like it doesn't like writing to the stream of HttpContext.Response.OutputStream
.
My following code example will throw
System.NotSupportedException: Specified method is not supported
as soon as a write is attempted on the stream.
The CanWrite
property on the stream returns true.
If I exchange the OutputStream with a filestream, pointing to a local directory, it works. What gives?
ZipArchive archive = new ZipArchive(HttpContext.Response.OutputStream, ZipArchiveMode.Create, false);
ZipArchiveEntry entry = archive.CreateEntry("filename");
using (StreamWriter writer = new StreamWriter(entry.Open()))
{
writer.WriteLine("Information about this package.");
writer.WriteLine("========================");
}
Stacktrace:
[NotSupportedException: Specified method is not supported.]
System.Web.HttpResponseStream.get_Position() +29
System.IO.Compression.ZipArchiveEntry.WriteLocalFileHeader(Boolean isEmptyFile) +389
System.IO.Compression.DirectToArchiveWriterStream.Write(Byte[] buffer, Int32 offset, Int32 count) +94
System.IO.Compression.WrappedStream.Write(Byte[] buffer, Int32 offset, Int32 count) +41
Note: This has been fixed in .Net Core 2.0. I'm not sure what is the status of the fix for .Net Framework.
Calbertoferreira's answer has some useful information, but the conclusion is mostly wrong. To create an archive, you don't need seek, but you do need to be able to read the
Position
.According to the documentation, reading
Position
should be supported only for seekable streams, butZipArchive
seems to require this even from non-seekable streams, which is a bug.So, all you need to do to support writing ZIP files directly to
OutputStream
is to wrap it in a customStream
that supports gettingPosition
. Something like:Using this, the following code will write a ZIP archive into
OutputStream
:If you compare your code adaptation with the version presented in MSDN page you'll see that the ZipArchiveMode.Create is never used, what is used is ZipArchiveMode.Update.
Despite that, the main problem is the OutputStream that doesn't support Read and Seek which is need by the ZipArchive in Update Mode:
You weren't getting any exceptions with the create mode because it only needs to write:
I believe you can't create a zip file directly in the OutputStream since it's a network stream and seek is not supported:
An alternative could be writing to a memory stream, then use the OutputStream.Write method to send the zip file.
EDIT: With feedback from comments and further reading, you could be creating large Zip files, so the memory stream could cause you problems.
In this case i suggest you create the zip file on the web server then output the file using Response.WriteFile .
Presumably this is not an MVC app, where you could easily just use the
FileStreamResult
class.I'm using this currently with
ZipArchive
created using aMemoryStream
, so I know it works.With that in mind, have a look at the
FileStreamResult.WriteFile()
method:(Entire FileStreamResult on CodePlex)
Here is how I'm generating and returning the
ZipArchive
.You should have no issues replacing the FSR with the guts of the
WriteFile
method from above, whereFileStream
becomesresultStream
from the code below:Finally, if you will be writing large streams (or a larger number of them at any given time), then you may want to consider using anonymous pipes to write the data to the output stream immediately after you write it to the underlying stream in the zip file. Because you will be holding all the file contents in memory on the server. The end of this answer to a similar question has a nice explanation of how to do that.
A refinement to svick's answer of 2nd February 2014. I found that it was necessary to implement some more methods and properties of the Stream abstract class and to declare the pos member as long. After that it worked like a charm. I haven't extensively tested this class, but it works for the purposes of returning a ZipArchive in the HttpResponse. I assume I've implemented Seek and Read correctly, but they may need some tweaking.
An simplified version of svick's answer for zipping a server-side file and sending it via the OutputStream:
(In case this seems obvious, it wasn't to me!)