So, here’s a question: “Why use DotNetZip instead of the built-in ZipArchive?”
The answer is: You can use ZipArchive, but you would need to work around a bug in the ZipArchive class.
The straightforward approach is to just use ZipArchive with PushStreamContent, very similar to the previous example:
This code should work, but it will actually fail (as of .NET 4.6.2). It will throw a NotSupportedException from within the PushStreamContent callback, so it’s annoying to find and debug.
Here’s the problem:
Streams can be “seekable.” Seekable streams allow reading the Position property and updating it via the Seek method.
The ASP.NET response output stream is not seekable. This is a common aspect of all network streams.
ZipArchive should work with write-only (non-seekable) streams. However (and this is the bug), it will actually readPosition even for non-seekable streams in order to build up its list of zip entry offsets in the zip file.
The important parts of this stream wrapper look like this (the other members of this type just forward to the underlying stream):
The only tricky part about this wrapper is that we want to be sure to override the asynchronous methods as well as the synchronous ones. This is because the Stream base class will provide a default implementation of these that just runs the synchronous APIs on a thread pool thread. In other words, it’s using fake asynchrony by default! So we need to override them to provide true asynchrony.
With that wrapper in place, our action method can be updated:
And ZipArchive is quite happy with the stream wrapper!
A fully-working solution for ASP.NET 4.6 (using the built-in ZipArchive) is available on GitHub.
About Stephen Cleary
Stephen Cleary is a Christian, husband, father, and programmer living in Northern Michigan.