Comprimere e decomprimere file e cartelle in formato ZIP

Se ritieni utile questo articolo, considera la possibilità di effettuare una donazione (il cui importo è a tua completa discrezione) tramite PayPal. Grazie.

Spesso le nostre applicazioni .NET necessitano di comprimere (e decomprimere) file e cartelle in formato ZIP. Il framework non contempla, almeno fino alle versione disponibile alla stesura di questo articolo (2.0 Beta 2), nessuna classe per la gestione di archivi compressi. Fortunatamente esiste un ottimo progetto Open Source - SharpZipLib - che implementa gli algoritmi necessari allo scopo. A noi non resta che creare una semplice classe helper che esponga in modo semplice e diretto le funzionalità per creare uno zip da un file, da una cartella e per decomprimere un archivio esistente (ovvero i task normalmente più richiesti).

Interfaccia della classe Zip

Il class diagram seguente mostra l'interfaccia esposta dalla classe Zip:

Zip - Class Diagram

Vengono esposti 3 metodi (statici) pubblici:

  • ZipFile(string ZipFilePath, string OriginalFilePath) che ci consente di comprime il contenuto di un file

  • ZipFolder(string ZipFilePath, string OriginalFolderPath) che ci consente di comprime il contenuto di una cartella in formato ZIP, ricorsivamente e preservandone la struttura

  • UnZip(string ZipFilePath, string DestinationPath) per decomprime un file compresso ZIP nella cartella di destinazione specificata

Internamente la classe utilizza poi due metodi privati che gestiscono l'inserimento di file e cartelle nell'archivio (ZipEntry)

Prerequisiti

Come accennato nell'introduzione utilizzeremo gli algoritmi di compressione forniti dal progetto Open Source SharpZipLib. Il progetto, comprensivo di codice sorgente, procedura di setup, documentazione ed esempi di utilizzo, è liberamente scaricabile da http://www.icsharpcode.net/OpenSource/SharpZipLib.
Una volta scaricata la libreria non dobbiamo far altro che aggiungere nel nostro progetto un riferimento alla DLL (ICSharpCode.SharpZipLib.dll).

Note sull'installazione

  • Nel nostro esempio facciamo riferimento a SharpZipLib nella versione 0.83, per cui, utilizzando una versione differente della libreria, dovrete verificare che non siano variate le interfacce esposte.

  • Con la libreria compilata viene distribuito anche un file batch (installGAC.bat) per la registrazione nella Global Assembly Cache del framework, così da non dover creare una reference a SharpZipLib in ogni singolo progetto.

Implementazione C# della classe Zip

A questo possiamo (finalmente!) iniziare a scrivere un po' di codice, implementando la classe helper per lo zip / unzip:

using System;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Checksums;

namespace Guru4Net
{
    /// <summary>
    /// Zip
    /// </summary>
    public class Zip
    {
        #region public methods

        /// <summary>
        /// Comprime il contenuto di un file in formato ZIP
        /// </summary>
        /// <param name="ZipFilePath">Path del file compresso da creare</param>
        /// <param name="OriginalFilePath">Path del file da comprimere</param>
        public static void ZipFile(string ZipFilePath, string OriginalFilePath)
        {
            try
            {
                FileInfo fi = new FileInfo(OriginalFilePath);
                ZipOutputStream zip = new ZipOutputStream(File.Create(ZipFilePath));
                zip.SetLevel(6);    // 0 - store only to 9 - means best compression
                AddFile2Zip(zip, fi, "");
                zip.Finish();
                zip.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Comprime il contenuto di una cartella in formato ZIP, ricorsivamente e preservandone la struttura
        /// </summary>
        /// <param name="ZipFilePath">Path del file compresso da creare</param>
        /// <param name="OriginalFolderPath">Path della cartella da comprimere</param>
        public static void ZipFolder(string ZipFilePath, string OriginalFolderPath)
        {
            try
            {
                DirectoryInfo di = new DirectoryInfo(OriginalFolderPath);
                ZipOutputStream zip = new ZipOutputStream(File.Create(ZipFilePath));
                zip.SetLevel(6);    // 0 - store only to 9 - means best compression
                AddFolder2Zip(zip, di, "");
                zip.Finish();
                zip.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Decomprime un file compresso ZIP nella cartella di destinazione specificata
        /// </summary>
        /// <param name="ZipFilePath">Path del file ZIP da decomprimere</param>
        /// <param name="DestinationPath">Cartella di destinazione dell'archivio decompresso</param>
        public static void UnZip(string ZipFilePath, string DestinationPath)
        {
            string dp = (DestinationPath.EndsWith("\\")) ? DestinationPath : DestinationPath + @"\";
            ZipInputStream zip = new ZipInputStream(File.OpenRead(ZipFilePath));
            ZipEntry entry;
            while ((entry = zip.GetNextEntry()) != null)
            {
                if (entry.IsDirectory)
                    Directory.CreateDirectory(dp + entry.Name);
                else
                {
                    FileStream streamWriter = File.Create(dp + entry.Name);
                    int size = 2048;
                    byte[] data = new byte[2048];
                    while (true)
                    {
                        size = zip.Read(data, 0, data.Length);
                        if (size > 0)
                            streamWriter.Write(data, 0, size);
                        else
                            break;
                    }
                    streamWriter.Close();
                }
            }
            zip.Close();
        }

        #endregion

        #region private methods

        private static void AddFolder2Zip(ZipOutputStream zip, DirectoryInfo di, string internalzippath)
        {
            string izp = internalzippath + di.Name + "/";    // A directory is determined by an entry name with a trailing slash "/"
            Crc32 crc = new Crc32();
            ZipEntry entry = new ZipEntry(izp);
            entry.Crc = crc.Value;
            zip.PutNextEntry(entry);
            foreach (FileInfo fi in di.GetFiles())
                AddFile2Zip(zip, fi, izp);
            foreach (DirectoryInfo sdi in di.GetDirectories())
                AddFolder2Zip(zip, sdi, izp);
        }
        private static void AddFile2Zip(ZipOutputStream zip, FileInfo fi, string internalzippath)
        {
            Crc32 crc = new Crc32();
            FileStream fs = File.OpenRead(fi.FullName);
            byte[] buffer = new byte[fs.Length];
            fs.Read(buffer, 0, buffer.Length);
            ZipEntry entry = new ZipEntry(internalzippath + fi.Name);
            entry.DateTime = DateTime.Now;
            entry.Size = fs.Length;
            fs.Close();
            crc.Reset();
            crc.Update(buffer);
            entry.Crc = crc.Value;
            zip.PutNextEntry(entry);
            zip.Write(buffer, 0, buffer.Length);
        }

        #endregion
    }
}

Estensioni

La classe che abbiamo creato può essere ampliata in molti modi. A titolo esemplificativo delle possibili evoluzioni citiamo:

  • possibilità di definire il livello di compressione desiderato

  • possibilità di filtrare i file da inserire nell'archivio compresso mediante la definizione di un pattern

  • supporto di algoritmi di compressione diversi da Zip (Tar, GZip, ecc.)

  • ...tutto quello che vi viene in mente o che vi possa servire!