Comment puis-je compresser un fichier en C # sans utiliser d'API tierces?

175

Je suis à peu près sûr que ce n'est pas un double, alors patientez avec moi juste une minute.

Comment puis-je compresser par programme (C #) un fichier (sous Windows) sans utiliser de bibliothèques tierces? J'ai besoin d'un appel Windows natif ou quelque chose comme ça; Je n'aime vraiment pas l'idée de lancer un processus, mais je le ferai si je le dois absolument. Un appel PInovke serait bien mieux.

À défaut, laissez-moi vous dire ce que j'essaie vraiment d'accomplir: j'ai besoin de la possibilité de permettre à un utilisateur de télécharger une collection de documents en une seule demande. Des idées pour y parvenir?

Esteban Araya
la source
1
@Chesso: Oui, à partir d'une page ASPX.
Esteban Araya le
1
J'ai trouvé cet exemple utile lorsque je cherchais la même chose il y a quelques semaines: syntaxwarriors.com/2012/…
JensB
2
Si vous utilisez le Framework 4.5, il existe désormais les classes ZipArchive et ZipFile.
GalacticJello
N'importe qui a utilisé DotNetZip ??
Hot Licks

Réponses:

85

Utilisez-vous .NET 3.5? Vous pouvez utiliser la ZipPackageclasse et les classes associées. Il ne s'agit pas simplement de compresser une liste de fichiers, car il veut un type MIME pour chaque fichier que vous ajoutez. Cela pourrait faire ce que vous voulez.

J'utilise actuellement ces classes pour un problème similaire pour archiver plusieurs fichiers associés dans un seul fichier à télécharger. Nous utilisons une extension de fichier pour associer le fichier téléchargé à notre application de bureau. Un petit problème que nous avons rencontré est qu'il n'est pas possible d'utiliser simplement un outil tiers comme 7-zip pour créer les fichiers zip car le code côté client ne peut pas l'ouvrir - ZipPackage ajoute un fichier caché décrivant le type de contenu de chaque fichier de composant et ne peut pas ouvrir un fichier zip si ce fichier de type de contenu est manquant.

Brian Ensink
la source
1
Oh SO, comme je t'aime! Merci Brian; vous venez de nous éviter beaucoup de maux de tête et quelques $$$.
Esteban Araya
6
Notez que cela ne fonctionne pas toujours en sens inverse. Certains fichiers Zip ne se réhydrateront pas à l'aide de la classe ZipPackage. Les fichiers créés avec ZipPackage le seront donc vous devriez être bon.
Craig le
Notez que ZipPackage ne peut pas s'ajouter à un package zippé existant.
ΩmegaMan
Soupir: "Le type ou l'espace de noms" Packaging "n'existe pas dans l'espace de noms" System.IO ".
Hot Licks
2
(Réponse au "soupir" ci-dessus: Ouvrez "Références" et ajoutez (assez illogiquement) "WindowsBase".)
Hot Licks
307

Comment puis-je compresser par programme (C #) un fichier (sous Windows) sans utiliser de bibliothèques tierces?

Si vous utilisez le Framework 4.5+, il existe désormais les classes ZipArchive et ZipFile .

using (ZipArchive zip = ZipFile.Open("test.zip", ZipArchiveMode.Create))
{
    zip.CreateEntryFromFile(@"c:\something.txt", "data/path/something.txt");
}

Vous devez ajouter des références à:

  • Compression System.IO
  • System.IO.Compression.FileSystem

Pour .NET Core ciblant net46, vous devez ajouter des dépendances pour

  • Compression System.IO
  • System.IO.Compression.ZipFile

Exemple project.json:

"dependencies": {
  "System.IO.Compression": "4.1.0",
  "System.IO.Compression.ZipFile": "4.0.1"
},

"frameworks": {
  "net46": {}
}

Pour .NET Core 2.0, il suffit d'ajouter une simple instruction using:

  • en utilisant System.IO.Compression;
GalacticJello
la source
4
Comment cela n'a-t-il pas obtenu plus de votes positifs? C'est la seule réponse directe.
Matt Cashatt
12
Parce que la question a cinq ans, alors que cette réponse n'a que deux mois. Derp :-P
Riegardt Steyn
3
@heliac toujours le truc Stackoverflow devrait être un référentiel de questions et réponses et dans l'esprit, la meilleure réponse devrait être au sommet ... (putain, je savais que cela ne fonctionne pas)
Offler
5
Juste au cas où cela aiderait quelqu'un, le deuxième argument est l'entrée de fichier. Il s'agit du chemin vers lequel le fichier sera extrait par rapport au dossier de décompression. Dans Windows 7, j'ai trouvé que si l'entrée de fichier est un chemin complet, par exemple @ "D: \ Temp \ file1.pdf", l'extracteur Windows natif échoue. Vous pouvez rencontrer ce problème si vous utilisez simplement les noms de fichiers résultant de Directory.GetFiles (). Il est préférable d'extraire le nom de fichier en utilisant Path.GetFileName () pour l'argument d'entrée de fichier.
Manish
2
Je ne semble pas être en mesure de trouver cela dans 4.5.2?
user3791372
11

J'étais dans la même situation, voulant utiliser .NET au lieu d'une bibliothèque tierce. Comme une autre affiche mentionnée ci-dessus, la simple utilisation de la classe ZipPackage (introduite dans .NET 3.5) ne suffit pas. Il y a un fichier supplémentaire qui DOIT être inclus dans l'archive pour que le ZipPackage fonctionne. Si ce fichier est ajouté, le package ZIP résultant peut être ouvert directement à partir de l'Explorateur Windows - pas de problème.

Tout ce que vous avez à faire est d'ajouter le fichier [Content_Types] .xml à la racine de l'archive avec un nœud "Par défaut" pour chaque extension de fichier que vous souhaitez inclure. Une fois ajouté, je pouvais parcourir le package à partir de l'Explorateur Windows ou décompresser et lire son contenu par programme.

Vous trouverez plus d'informations sur le fichier [Content_Types] .xml ici: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Voici un exemple du fichier [Content_Types] .xml (doit être nommé exactement):

<?xml version="1.0" encoding="utf-8" ?>
<Types xmlns=
    "http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="xml" ContentType="text/xml" /> 
  <Default Extension="htm" ContentType="text/html" /> 
  <Default Extension="html" ContentType="text/html" /> 
  <Default Extension="rels" ContentType=
    "application/vnd.openxmlformats-package.relationships+xml" /> 
  <Default Extension="jpg" ContentType="image/jpeg" /> 
  <Default Extension="png" ContentType="image/png" /> 
  <Default Extension="css" ContentType="text/css" /> 
</Types>

Et le C # pour créer un fichier ZIP:

var zipFilePath = "c:\\myfile.zip"; 
var tempFolderPath = "c:\\unzipped"; 

    using (Package package = ZipPackage.Open(zipFilePath, FileMode.Open, FileAccess.Read)) 
    { 
        foreach (PackagePart part in package.GetParts()) 
        { 
            var target = Path.GetFullPath(Path.Combine(tempFolderPath, part.Uri.OriginalString.TrimStart('/'))); 
            var targetDir = target.Remove(target.LastIndexOf('\\')); 

            if (!Directory.Exists(targetDir)) 
                Directory.CreateDirectory(targetDir); 

            using (Stream source = part.GetStream(FileMode.Open, FileAccess.Read)) 
            { 
                source.CopyTo(File.OpenWrite(target)); 
            } 
        } 
    } 

Remarque:

Joshua
la source
12
Bel exemple, mais il ne crée pas de fichier ZIP. Il décompresse un fichier existant.
Matt Varblow
9
    private static string CompressFile(string sourceFileName)
    {
        using (ZipArchive archive = ZipFile.Open(Path.ChangeExtension(sourceFileName, ".zip"), ZipArchiveMode.Create))
        {
            archive.CreateEntryFromFile(sourceFileName, Path.GetFileName(sourceFileName));
        }
        return Path.ChangeExtension(sourceFileName, ".zip");
    }
VACILLER
la source
Comment puis-je obtenir sourceFileName lorsque je suis dans une webapi et que je reçois un HttpContext.Current.Request?
Olivertech
Compresser plusieurs fichiers?
Kiquenet
1

Sur la base de la réponse de Simon McKenzie à cette question , je suggère d'utiliser une paire de méthodes comme celle-ci:

    public static void ZipFolder(string sourceFolder, string zipFile)
    {
        if (!System.IO.Directory.Exists(sourceFolder))
            throw new ArgumentException("sourceDirectory");

        byte[] zipHeader = new byte[] { 80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        using (System.IO.FileStream fs = System.IO.File.Create(zipFile))
        {
            fs.Write(zipHeader, 0, zipHeader.Length);
        }

        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic source = shellApplication.NameSpace(sourceFolder);
        dynamic destination = shellApplication.NameSpace(zipFile);

        destination.CopyHere(source.Items(), 20);
    }

    public static void UnzipFile(string zipFile, string targetFolder)
    {
        if (!System.IO.Directory.Exists(targetFolder))
            System.IO.Directory.CreateDirectory(targetFolder);

        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic compressedFolderContents = shellApplication.NameSpace(zipFile).Items;
        dynamic destinationFolder = shellApplication.NameSpace(targetFolder);

        destinationFolder.CopyHere(compressedFolderContents);
    }
}
mccdyl001
la source
0

On dirait que Windows pourrait vous laisser faire cela ...

Malheureusement, je ne pense pas que vous allez commencer un processus distinct à moins que vous n'utilisiez un composant tiers.

Dave Swersky
la source
0

Ajoutez ces 4 fonctions à votre projet:

        public const long BUFFER_SIZE = 4096;
    public static void AddFileToZip(string zipFilename, string fileToAdd)
    {
        using (Package zip = global::System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
        {
            string destFilename = ".\\" + Path.GetFileName(fileToAdd);
            Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
            PackagePart part = zip.CreatePart(uri, "", CompressionOption.Normal);
            using (FileStream fileStream = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read))
            {
                using (Stream dest = part.GetStream())
                {
                    CopyStream(fileStream, dest);
                }
            }
        }
    }
    public static void CopyStream(global::System.IO.FileStream inputStream, global::System.IO.Stream outputStream)
    {
        long bufferSize = inputStream.Length < BUFFER_SIZE ? inputStream.Length : BUFFER_SIZE;
        byte[] buffer = new byte[bufferSize];
        int bytesRead = 0;
        long bytesWritten = 0;
        while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            outputStream.Write(buffer, 0, bytesRead);
            bytesWritten += bytesRead;
        }
    }
    public static void RemoveFileFromZip(string zipFilename, string fileToRemove)
    {
        using (Package zip = global::System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
        {
            string destFilename = ".\\" + fileToRemove;
            Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
        }
    }
    public static void Remove_Content_Types_FromZip(string zipFileName)
    {
        string contents;
        using (ZipFile zipFile = new ZipFile(File.Open(zipFileName, FileMode.Open)))
        {
            /*
            ZipEntry startPartEntry = zipFile.GetEntry("[Content_Types].xml");
            using (StreamReader reader = new StreamReader(zipFile.GetInputStream(startPartEntry)))
            {
                contents = reader.ReadToEnd();
            }
            XElement contentTypes = XElement.Parse(contents);
            XNamespace xs = contentTypes.GetDefaultNamespace();
            XElement newDefExt = new XElement(xs + "Default", new XAttribute("Extension", "sab"), new XAttribute("ContentType", @"application/binary; modeler=Acis; version=18.0.2application/binary; modeler=Acis; version=18.0.2"));
            contentTypes.Add(newDefExt);
            contentTypes.Save("[Content_Types].xml");
            zipFile.BeginUpdate();
            zipFile.Add("[Content_Types].xml");
            zipFile.CommitUpdate();
            File.Delete("[Content_Types].xml");
            */
            zipFile.BeginUpdate();
            try
            {
                zipFile.Delete("[Content_Types].xml");
                zipFile.CommitUpdate();
            }
            catch{}
        }
    }

Et utilisez-les comme ceci:

foreach (string f in UnitZipList)
{
    AddFileToZip(zipFile, f);
    System.IO.File.Delete(f);
}
Remove_Content_Types_FromZip(zipFile);
Teemo
la source