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:
- HTTP responses
- cloud storage objects
- temporary files
- generated content that should exist only for a short time
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:
- all downloads must start before
Save()begins - several network streams may stay open longer than necessary
- some archive formats are stricter about the source stream capabilities
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
- The provider delegate is called during archive saving, not during entry registration.
- The delegate should return a fresh readable stream for that entry.
HttpCompletionOption.ResponseHeadersReadis useful for large downloads because it starts streaming the body immediately.- If the stream originates from
HttpResponseMessage, keep the response alive until the returned stream is closed. - If download of an entry fails while the archive is being saved, the exception propagates from
Save(). - For ZIP archives, if you need custom source-lifetime handling, you can also close
args.Entry.DataSourcemanually inEventsBag.EntryCompressed.