Xceed Real-Time Zip for .NET Documentation
Welcome to Xceed Real-Time Zip for .NET, .NET Standard & Xamarin / Basic Concepts / The ZipWriter class

In This Topic
    The ZipWriter class
    In This Topic

    The ZipWriter class lets an application create a Zip archive without resorting to intermediate storage (either memory or disk). Furthermore, the creation of the Zip archive starts as soon as the ZipWriter object has data to compress: the resulting Zip archive is immediately available for processing, for example by an application using a ZipReader object receiving the archive over an FTP connection. The target Zip archive is passed to the ZipWriter's class constructor as a stream of any type.

    The main methods of the ZipWriter class used to create Zip archives are WriteItemLocalHeader (in conjunction with a ZipItemLocalHeader object), WriteItemData, and CloseZipFile. The class also provides a ByteProgression event to monitor the progression of the Zip creation operation. For details on the events used by Xceed Real-Time Zip for .NET / .NET CF, see Events.

    Using the ZipWriter Class

    The following diagram will help to illustrate the relationship between the structure of a Zip archive and the class methods you must use to create a Zip archive.

    Red boxes: Local header
    Blue boxes: Compressed data
    Green boxes: Data descriptor
    Grey boxes: Central headers that make up the central directory

    Interoperability vs. Size

    The Zip file format can store item sizes and offsets in either 4-byte fields or 8-byte fields. This yields limits of 4GB and about 18 million terabytes (more precisely, 2^64 - 1 bytes), respectively. Using 8-byte fields is known as Zip64 extensions to the Zip file format. The original and most widely implemented specification, version 2.0, of the Zip format only supports 4-byte fields. Zip64 extensions were introduced in version 4.5 of the specification.

    Most modern Zip utilities support Zip64 extensions seamlessly. But a few old or broken Zip implementations still do not. For the sake of interoperability, the ZipWriter class uses 4-byte fields by default. Zip64 extensions can still be enabled when constructing a ZipWriter instance.

    This differs from previous versions of Xceed Real-Time Zip for .NET. Versions 4.2 and below always used Zip64 extensions. In more recent versions, Zip64 extensions are only used when explicitly enabled in the constructor.

    An automatic mode where Zip64 extensions would be used when a 4GB+ item is detected isn't possible because that would cause the ZipWriter to seek in the target stream and correct some fields in a local header that's already been written.

    WinRar, Zip tools on MacOS, and the built-in Zip support in Windows XP are known to lack support for the Zip64 extensions. 

    So before creating a Zip archive, a decision must be made as to whether that archive will require Zip64 extensions or not. Here are the limits for the default behavior:

    If these limits are likely to be broken or you want to be able to create Zip archives in every scenario, enable Zip64 extensions by setting the allowZip64Extensions parameter to true in the ZipWriter constructor.

    For additional details concerning the format used for Zip archives, see the ZIP file format specification.

    When creating a Zip archive, a stream representing the target archive is first passed to the ZipWriter constructor along with a boolean value indicating whether Zip64 extensions are to be used. A local header is then created for the first item to be archived and is written to the archive (red boxes in the diagram), followed by the item's data (blue boxes). This process is repeated until all of the items have been processed. Finally, the Zip archive is closed.

    An item's local header is represented by a ZipItemLocalHeader object, which is written to the Zip archive using the WriteItemLocalHeader method. The encryption method/password, and compression level/method can optionally be specified when creating a ZipItemLocalHeader. See The ZipItemLocalHeader class for more details.

    The current item's data is written by calling WriteItemData until all of its data has been written to the archive. The WriteItemData method is very similar to the Write method of the Stream class: the data to be written is passed to it as an array of bytes, with an offset indicating at what point to begin copying in the buffer and a count indicating the number of bytes to write from the offset.

    There are several flavors of WriteItemData to accommodate different data scenarios and make efficient reuse of data buffers.

    Another way to write data is to expose the item's data as a write-only Stream object with the GetItemDataStream method. Each call to the stream's Write method will call WriteItemData. This makes it possible to integrate ZipWriter with other classes that use the Stream class interface without the need for "glue code."

    Once an item's data has been written, calling WriteItemLocalHeader with the header for the next item automatically causes the data descriptor (green boxes) of the previous item to be written.

    Once the local headers and data of all the items have been written, CloseZipFile must be called to cause the data descriptor of the final item to be written, as well as the central directory (grey boxes) of the Zip archive.

    The ZipWriter class writes all items with the UTF8Filename and UTF8Comment extra headers, but only if non-ASCII characters are used in the text. This corresponds to the UnicodeUsagePolicy.NonASCIIOnly value in Xceed Zip .NET.

    Note that it doesn't write the old Xceed Unicode extra header, as it is deprecated.

    Xceed Real-Time Zip for .NET does not support the creation of split/spanned or self-extracting archives

     Example: Creating a Zip archive using ZipWriter on desktop environments

    The following example demonstrates how to create a Zip archive locally using the files in a test directory.

    static void ZipWriterExample()
    {
      string zipFilePath = @"D:\RealTimeZipExamples\MyZipFile.zip";
      // NOTE: The trailing backslash is important, we use it below to build the paths in the zip archive
      string sourceFolder = @"D:\ToZip\";
    
      // Create a stream for a new zip file
      using( FileStream zipFileStream = new FileStream( zipFilePath, FileMode.Create, FileAccess.Write, FileShare.None ) )
      {
        /* Wrapping the ZipWriter object in a 'using' block will make sure the archive is closed
         * properly after we're done adding content. If you use ZipWriter outside a 'using' block,
         * make sure you call ZipWriter.CloseZipFile() to flush the archive's meta data
         * to the output stream. */
    
        // Create the ZipWriter object around the stream
        using( ZipWriter zipWriter = new ZipWriter( zipFileStream ) )
        {
          int index;
          FileInfo sourceFile;
          string sourceFilenameInZip;
    
          /* As a convenience, ZipWriter offers the option to automatically close the output stream when
             ZipWriter is closed. The behavior is disabled by default.
         
             In this example, we will not enable it because we are already closing the zip
             file automatically in the 'using' block above. */
          //zipWriter.AllowOutputStreamClosure = true;
    
          /* Since we'll be adding many files in the archive, we'll create a work buffer
             ahead of time and use it over and over again to avoid creating a new buffer
             each time we read from a source file. */
          int bufferSize = 64 * 1024;
          byte[] buffer = new byte[ bufferSize ];
    
          /* Create a local header object that will be used to specify the path/filename in
             the zip file and other options like compression and encryption. */
          ZipItemLocalHeader localHeader = new ZipItemLocalHeader();
    
          /* Options can be set "globally" before zipping starts.
             We will set the file name for each file inside the loop. The other 
             options have default values that satisfy us:
             The compression method is Deflated at a the 'Normal' compression level
             No encryption.
             No comment. */
          //localHeader.CompressionMethod = CompressionMethod.Deflated;
          //localHeader.CompressionLevel = CompressionLevel.Normal;
          //localHeader.EncryptionMethod = EncryptionMethod.WinZipAes;
          //localHeader.EncryptionPassword = "password";
    
          // Compute the index where the base path ends, minus the trailing backslash
          index = sourceFolder.Length - 1;
    
          // Go through all the files in the source folder and its sub-folders
          foreach( string sourcePath in Directory.EnumerateFiles( sourceFolder, "*", SearchOption.AllDirectories ) )
          {
            // Get file information
            sourceFile = new FileInfo( sourcePath );
    
            // Compute a relative path for the file consisting of the full path minus the base path in 'sourceFolder'
            sourceFilenameInZip = sourcePath.Substring( index );
    
            // Assign the filename in zip in the header
            localHeader.FileName = sourceFilenameInZip;
    
            // Optional. Set the last write date/time in the header
            localHeader.LastWriteDateTime = sourceFile.LastWriteTime;
    
            try
            {
              // Open the current file for sequential reading
              using( Stream sourceFileStream = new FileStream( sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan ) )
              {
                /* If we make it here, the source file has been opened successfully. We can
                   safely add the item to the zip archive. */
    
                // Write the local header for the file in the archive
                zipWriter.WriteItemLocalHeader( localHeader );
    
                // Write the entire stream's content to the archive, using the work buffer we created
                zipWriter.WriteItemData( sourceFileStream, buffer, 0, bufferSize );
              }
            }
            catch( FileNotFoundException )
            {
              /* We're unable to open the source file. We will simply skip it and continue to
                 the next one. */
    
            }
            catch( System.Security.SecurityException )
            {
              /* We're unable to open the source file. We will simply skip it and continue to
                 the next one. */
            }
            catch( DirectoryNotFoundException )
            {
              /* We're unable to open the source file. We will simply skip it and continue to
                 the next one. */
            }
            catch( UnauthorizedAccessException )
            {
              /* We're unable to open the source file. We will simply skip it and continue to
                 the next one. */
            }
            catch( PathTooLongException )
            {
              /* We're unable to open the source file. We will simply skip it and continue to
                 the next one. */
            }
          }
    
          // OPTIONAL: A global comment for the zip archive can be set when closing the archive. By default, there is no comment.
          //ZipEndHeader endHeader = new ZipEndHeader( "Dynamically generated in <unknown> seconds" );
          //zipWriter.CloseZipFile( endHeader );
    
          /* Because we are in a 'using' block, ZipWriter.Dispose() will take care of calling
             ZipWriter.CloseZipFile() for us if we don't do it here.
    
             When using ZipWrite outside of a 'using' block, ZipWriter.CloseZipFile() MUST be
             called explicitly or the zip file will be incomplete and unusable. */
        }
      }
    }
        Private Shared Sub ZipWriterExample()
          Dim zipFilePath As String = "D:\RealTimeZipExamples\MyZipFile.zip"
          ' NOTE: The trailing backslash is important, we use it below to build the paths in the zip archive
          Dim sourceFolder As String = "D:\ToZip\"
    
          ' Create a stream for a new zip file
          Using zipFileStream As New FileStream(zipFilePath, FileMode.Create, FileAccess.Write, FileShare.None)
    '         Wrapping the ZipWriter object in a 'using' block will make sure the archive is closed
    '         * properly after we're done adding content. If you use ZipWriter outside a 'using' block,
    '         * make sure you call ZipWriter.CloseZipFile() to flush the archive's meta data
    '         * to the output stream. 
    
            ' Create the ZipWriter object around the stream
            Using zipWriter As New ZipWriter(zipFileStream)
              Dim index As Integer
              Dim sourceFile As FileInfo
              Dim sourceFilenameInZip As String
    
    '           As a convenience, ZipWriter offers the option to automatically close the output stream when
    '             ZipWriter is closed. The behavior is disabled by default.
    '         
    '             In this example, we will not enable it because we are already closing the zip
    '             file automatically in the 'using' block above. 
              'zipWriter.AllowOutputStreamClosure = true;
    
    '           Since we'll be adding many files in the archive, we'll create a work buffer
    '             ahead of time and use it over and over again to avoid creating a new buffer
    '             each time we read from a source file. 
              Dim bufferSize As Integer = 64 * 1024
              Dim buffer(bufferSize - 1) As Byte
    
    '           Create a local header object that will be used to specify the path/filename in
    '             the zip file and other options like compression and encryption. 
              Dim localHeader As New ZipItemLocalHeader()
    
    '           Options can be set "globally" before zipping starts.
    '             We will set the file name for each file inside the loop. The other 
    '             options have default values that satisfy us:
    '             The compression method is Deflated at a the 'Normal' compression level
    '             No encryption.
    '             No comment. 
              'localHeader.CompressionMethod = CompressionMethod.Deflated;
              'localHeader.CompressionLevel = CompressionLevel.Normal;
              'localHeader.EncryptionMethod = EncryptionMethod.WinZipAes;
              'localHeader.EncryptionPassword = "password";
    
              ' Compute the index where the base path ends, minus the trailing backslash
              index = sourceFolder.Length - 1
    
              ' Go through all the files in the source folder and its sub-folders
              For Each sourcePath As String In Directory.EnumerateFiles(sourceFolder, "*", SearchOption.AllDirectories)
                ' Get file information
                sourceFile = New FileInfo(sourcePath)
    
                ' Compute a relative path for the file consisting of the full path minus the base path in 'sourceFolder'
                sourceFilenameInZip = sourcePath.Substring(index)
    
                ' Assign the filename in zip in the header
                localHeader.FileName = sourceFilenameInZip
    
                ' Optional. Set the last write date/time in the header
                localHeader.LastWriteDateTime = sourceFile.LastWriteTime
    
                Try
                  ' Open the current file for sequential reading
                  Using sourceFileStream As Stream = New FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)
    '                 If we make it here, the source file has been opened successfully. We can
    '                   safely add the item to the zip archive. 
    
                    ' Write the local header for the file in the archive
                    zipWriter.WriteItemLocalHeader(localHeader)
    
                    ' Write the entire stream's content to the archive, using the work buffer we created
                    zipWriter.WriteItemData(sourceFileStream, buffer, 0, bufferSize)
                  End Using
                Catch e1 As FileNotFoundException
    '               We're unable to open the source file. We will simply skip it and continue to
    '                 the next one. 
    
                Catch e2 As System.Security.SecurityException
    '               We're unable to open the source file. We will simply skip it and continue to
    '                 the next one. 
                Catch e3 As DirectoryNotFoundException
    '               We're unable to open the source file. We will simply skip it and continue to
    '                 the next one. 
                Catch e4 As UnauthorizedAccessException
    '               We're unable to open the source file. We will simply skip it and continue to
    '                 the next one. 
                Catch e5 As PathTooLongException
    '               We're unable to open the source file. We will simply skip it and continue to
    '                 the next one. 
                End Try
              Next sourcePath
              
              ' OPTIONAL: A global comment for the zip archive can be set when closing the archive. By default, there is no comment.
              'Dim endHeader As New ZipEndHeader("Dynamically generated in <unknown> seconds")
              'zipWriter.CloseZipFile(endHeader)
    
              ' Because we are in a 'using' block, ZipWriter.Dispose() will take care of calling
              ' ZipWriter.CloseZipFile() for us if we don't do it here.
    
              '  When using ZipWrite outside of a 'using' block, ZipWriter.CloseZipFile() MUST be
              '  called explicitly or the zip file will be incomplete and unusable.
            End Using
          End Using
        End Sub
     Example: Creating a Zip archive using ZipWriter on Xamarin (Android and iOS)

    The following example demonstrates how to create a Zip archive using MemoryStream objects. The usage is the same as it would be on desktop .NET. MemoryStream objects are used to maintain focus on the ZipWriter API.

    The MemoryStream objects can be changed to any stream objects that derive from the System.IO.Stream. NetworkStream, FileStream, etc.

    static Stream ZipWriterExampleXamarin()
    {
      /* NOTE: The zip file can be any type of stream you need. A network stream, a file stream, etc
         The component will never call Stream.Seek() on the stream and will only write to it. */
    
      // Create a writable stream for the zip file
      Stream zipFileStream = new MemoryStream();
    
      /* Wrapping the ZipWriter object in a 'using' block will make sure the archive is closed
         properly after we're done adding content. If you use ZipWriter outside a 'using' block,
         make sure you call ZipWriter.CloseZipFile() to flush the archive's meta data
         to the output stream. */
    
      // Create the ZipWriter object around the stream
      using( ZipWriter zipWriter = new ZipWriter( zipFileStream ) )
      {
        /* As a convenience, ZipWriter offers the option to automatically close the output stream when
           ZipWriter is closed. The behavior is disabled by default.
         
           In this example, we will not enable it as we want to manipulate the zip file
           after it has been written. */
        //zipWriter.AllowOutputStreamClosure = true;
    
        /* If we'll be adding many items in the archive, we'll create a work buffer
           ahead of time and use it over and over again to avoid creating a new buffer
           each time we read from a source file.
          
           This example doesn't add many items but still shows the technique. */
        int bufferSize = 64 * 1024;
        byte[] buffer = new byte[ bufferSize ];
    
        /* Create a local header object that will be used to specify the path/filename in
           the zip file and other options like compression and encryption. */
        ZipItemLocalHeader localHeader = new ZipItemLocalHeader();
    
        /* Options can be set "globally" before zipping starts.
           We will set the file name for each file inside the loop. The other 
           options have default values that satisfy us:
           The compression method is Deflated at a the 'Normal' compression level
           No encryption.
           No comment. */
        //localHeader.CompressionMethod = CompressionMethod.Deflated;
        //localHeader.CompressionLevel = CompressionLevel.Normal;
        //localHeader.EncryptionMethod = EncryptionMethod.WinZipAes;
        //localHeader.EncryptionPassword = "password";
    
        // Assign the filename in zip in the header
        localHeader.FileName = "MyFile1.dat";
    
        // Optional. Set the last write date/time in the header
        localHeader.LastWriteDateTime = DateTime.Now;
    
        /* NOTE: The source data can be any type of stream you need. A network stream, a file stream, etc
        The component will never call Stream.Seek() on the stream and will only read from it.
       
        This example will use memory data to keep it simple and on point. */
    
        // Create some source data
        byte[] dataToZip = System.Text.Encoding.UTF8.GetBytes( "The quick brown fox jumps over the lazy dog" );
    
        // Create a stream around the source data
        using( Stream sourceDataStream = new MemoryStream( dataToZip, false ) )
        {
          // Write the local header for the file in the archive
          zipWriter.WriteItemLocalHeader( localHeader );
    
          // Write the entire stream's content to the archive, using the work buffer we created
          zipWriter.WriteItemData( sourceDataStream, buffer, 0, bufferSize );
        }
      }
    
      /* At this point, 'zipFileStream' contains a zip file. You can store it, upload it, etc as you
         see fit. Because it is a stream, remember to change the current position to the start of the
         stream if you wish to read the data. */
    
      zipFileStream.Seek( 0, SeekOrigin.Begin );
    
      /* TODO: Send/store/whatever the zip file */
    
      return zipFileStream;
    }
        Private Shared Function ZipWriterExampleXamarin() As Stream
    '       NOTE: The zip file can be any type of stream you need. A network stream, a file stream, etc
    '         The component will never call Stream.Seek() on the stream and will only write to it. 
    
          ' Create a writable stream for the zip file
          Dim zipFileStream As Stream = New MemoryStream()
    
    '       Wrapping the ZipWriter object in a 'using' block will make sure the archive is closed
    '         properly after we're done adding content. If you use ZipWriter outside a 'using' block,
    '         make sure you call ZipWriter.CloseZipFile() to flush the archive's meta data
    '         to the output stream. 
    
          ' Create the ZipWriter object around the stream
          Using zipWriter As New ZipWriter(zipFileStream)
    '         As a convenience, ZipWriter offers the option to automatically close the output stream when
    '           ZipWriter is closed. The behavior is disabled by default.
    '         
    '           In this example, we will not enable it as we want to manipulate the zip file
    '           after it has been written. 
            'zipWriter.AllowOutputStreamClosure = true;
    
    '         If we'll be adding many items in the archive, we'll create a work buffer
    '           ahead of time and use it over and over again to avoid creating a new buffer
    '           each time we read from a source file.
    '          
    '           This example doesn't add many items but still shows the technique. 
            Dim bufferSize As Integer = 64 * 1024
            Dim buffer(bufferSize - 1) As Byte
    
    '         Create a local header object that will be used to specify the path/filename in
    '           the zip file and other options like compression and encryption. 
            Dim localHeader As New ZipItemLocalHeader()
    
    '         Options can be set "globally" before zipping starts.
    '           We will set the file name for each file inside the loop. The other 
    '           options have default values that satisfy us:
    '           The compression method is Deflated at a the 'Normal' compression level
    '           No encryption.
    '           No comment. 
            'localHeader.CompressionMethod = CompressionMethod.Deflated;
            'localHeader.CompressionLevel = CompressionLevel.Normal;
            'localHeader.EncryptionMethod = EncryptionMethod.WinZipAes;
            'localHeader.EncryptionPassword = "password";
    
            ' Assign the filename in zip in the header
            localHeader.FileName = "MyFile1.dat"
    
            ' Optional. Set the last write date/time in the header
            localHeader.LastWriteDateTime = DateTime.Now
    
    '         NOTE: The source data can be any type of stream you need. A network stream, a file stream, etc
    '        The component will never call Stream.Seek() on the stream and will only read from it.
    '       
    '        This example will use memory data to keep it simple and on point. 
    
            ' Create some source data
            Dim dataToZip() As Byte = System.Text.Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog")
    
            ' Create a stream around the source data
            Using sourceDataStream As Stream = New MemoryStream(dataToZip, False)
              ' Write the local header for the file in the archive
              zipWriter.WriteItemLocalHeader(localHeader)
    
              ' Write the entire stream's content to the archive, using the work buffer we created
              zipWriter.WriteItemData(sourceDataStream, buffer, 0, bufferSize)
            End Using
          End Using
    
    '       At this point, 'zipFileStream' contains a zip file. You can store it, upload it, etc as you
    '         see fit. Because it is a stream, remember to change the current position to the start of the
    '         stream if you wish to read the data. 
    
          zipFileStream.Seek(0, SeekOrigin.Begin)
    
          ' TODO: Send/store/whatever the zip file 
    
          Return zipFileStream
        End Function
    See Also