Compress Data on the Fly in C#

Overview

Aspose.ZIP for .NET lets you create archive entries from a stream provider. With overloads such as CreateEntry(string name, Func<Stream> streamProvider), you pass a delegate that returns the entry stream only when Aspose.ZIP is ready to consume it, instead of opening all source streams before archive composition starts.

This approach is especially useful when the source data comes from:

With this pattern, you do not need to download each file beforehand or keep several network streams open while the archive is being prepared.

Why use a stream provider

The traditional CreateEntry(name, Stream) overload expects a stream that is already open. That works well for local files and memory streams, but it is less convenient for remote resources:

The stream-provider overload solves this by deferring the download until the entry is actually being compressed.

The sample URLs below use the reserved example.com domain. Replace them with your actual endpoints in a real application.

Safely dispose HTTP resources

When a stream provider returns response.Content.ReadAsStream(), the HttpResponseMessage must stay alive until Aspose.ZIP finishes reading that stream. The helper below binds the response lifetime to the returned stream, so disposing the stream also disposes the HTTP response.

 1using System;
 2using System.IO;
 3using System.Net.Http;
 4
 5public sealed class ResponseOwnedStream : Stream
 6{
 7    private readonly HttpResponseMessage response;
 8    private readonly Stream inner;
 9
10    public ResponseOwnedStream(HttpResponseMessage response)
11    {
12        this.response = response ?? throw new ArgumentNullException(nameof(response));
13        this.inner = response.Content.ReadAsStream();
14    }
15
16    public override bool CanRead => inner.CanRead;
17    public override bool CanSeek => inner.CanSeek;
18    public override bool CanWrite => inner.CanWrite;
19    public override long Length
20    {
21        get
22        {
23            if (!inner.CanSeek)
24                throw new NotSupportedException("The underlying HTTP stream does not support seeking.");
25
26            return inner.Length;
27        }
28    }
29
30    public override long Position
31    {
32        get
33        {
34            if (!inner.CanSeek)
35                throw new NotSupportedException("The underlying HTTP stream does not support seeking.");
36
37            return inner.Position;
38        }
39        set
40        {
41            if (!inner.CanSeek)
42                throw new NotSupportedException("The underlying HTTP stream does not support seeking.");
43
44            inner.Position = value;
45        }
46    }
47
48    public override void Flush() => inner.Flush();
49    public override int Read(byte[] buffer, int offset, int count) => inner.Read(buffer, offset, count);
50    public override long Seek(long offset, SeekOrigin origin) => inner.Seek(offset, origin);
51    public override void SetLength(long value) => inner.SetLength(value);
52    public override void Write(byte[] buffer, int offset, int count) => inner.Write(buffer, offset, count);
53
54    protected override void Dispose(bool disposing)
55    {
56        if (disposing)
57        {
58            inner.Dispose();
59            response.Dispose();
60        }
61
62        base.Dispose(disposing);
63    }
64}

Compose ZIP archive from HTTP response

The following example downloads each file only when ZIP composition reaches that entry. CloseEntrySource = true makes Aspose.ZIP close each entry stream as soon as compression is complete, which in turn disposes the underlying HttpResponseMessage.

 1using System;
 2using System.IO;
 3using System.Net.Http;
 4using Aspose.Zip;
 5using Aspose.Zip.Saving;
 6
 7string[] urls =
 8[
 9    "https://example.com/files/file1.txt",
10    "https://example.com/files/file2.txt",
11    "https://example.com/files/file3.txt"
12];
13
14using (HttpClient httpClient = new HttpClient())
15using (FileStream zipFile = File.Open("result.zip", FileMode.Create))
16using (Archive archive = new Archive())
17{
18    foreach (string url in urls)
19    {
20        Uri uri = new Uri(url);
21        string entryName = Path.GetFileName(uri.AbsolutePath);
22
23        archive.CreateEntry(entryName, () =>
24        {
25            using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
26            HttpResponseMessage response = httpClient.Send(request, HttpCompletionOption.ResponseHeadersRead);
27            response.EnsureSuccessStatusCode();
28            return new ResponseOwnedStream(response);
29        });
30    }
31
32    archive.Save(zipFile, new ArchiveSaveOptions() { CloseEntrySource = true });
33}

Compose 7z archive from HTTP response

The same idea works for 7z archives. Here the stream provider is passed to SevenZipArchive.CreateEntry, and the save options close each downloaded stream right after the entry has been written to the archive.

 1using System;
 2using System.IO;
 3using System.Net.Http;
 4using Aspose.Zip.Saving;
 5using Aspose.Zip.SevenZip;
 6
 7string[] urls =
 8[
 9    "https://example.com/files/file1.txt",
10    "https://example.com/files/file2.txt",
11    "https://example.com/files/file3.txt"
12];
13
14using (HttpClient httpClient = new HttpClient())
15using (FileStream sevenZipFile = File.Open("result.7z", FileMode.Create))
16using (SevenZipArchive archive = new SevenZipArchive())
17{
18    foreach (string url in urls)
19    {
20        Uri uri = new Uri(url);
21        string entryName = Path.GetFileName(uri.AbsolutePath);
22
23        archive.CreateEntry(entryName, () =>
24        {
25            using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
26            HttpResponseMessage response = httpClient.Send(request, HttpCompletionOption.ResponseHeadersRead);
27            response.EnsureSuccessStatusCode();
28            return new ResponseOwnedStream(response);
29        });
30    }
31
32    archive.Save(sevenZipFile, new SevenZipArchiveSaveOptions() { CloseEntrySource = true });
33}

Compose CAB archive from HTTP response

This feature is particularly helpful for CAB composition. A plain HTTP response stream is usually non-seekable, so pre-opening remote streams is not always a practical approach. With a stream provider, the archive requests each stream only at the moment it needs that entry.

 1using System;
 2using System.IO;
 3using System.Net.Http;
 4using Aspose.Zip.Cab;
 5
 6string[] urls =
 7[
 8    "https://example.com/files/file1.txt",
 9    "https://example.com/files/file2.txt",
10    "https://example.com/files/file3.txt"
11];
12
13using (HttpClient httpClient = new HttpClient())
14using (FileStream cabFile = File.Open("result.cab", FileMode.Create))
15using (CabArchive archive = new CabArchive())
16{
17    foreach (string url in urls)
18    {
19        Uri uri = new Uri(url);
20        string entryName = Path.GetFileName(uri.AbsolutePath);
21
22        archive.CreateEntry(entryName, () =>
23        {
24            using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
25            HttpResponseMessage response = httpClient.Send(request, HttpCompletionOption.ResponseHeadersRead);
26            response.EnsureSuccessStatusCode();
27            return new ResponseOwnedStream(response);
28        });
29    }
30
31    archive.Save(cabFile, new CabSaveOptions() { CloseEntrySource = true });
32}

Practical notes

Subscribe to Aspose Product Updates

Get monthly newsletters & offers directly delivered to your mailbox.