Wie kann ich eine Datei in C # ohne APIs von Drittanbietern komprimieren?

175

Ich bin mir ziemlich sicher, dass dies kein Duplikat ist. Nehmen Sie sich also eine Minute Zeit für mich.

Wie kann ich eine Datei (unter Windows) programmgesteuert (C #) komprimieren, ohne Bibliotheken von Drittanbietern zu verwenden? Ich brauche einen nativen Windows-Aufruf oder ähnliches. Ich mag die Idee, einen Prozess zu starten, wirklich nicht, aber ich werde es tun, wenn ich es unbedingt muss. Ein PInovke-Anruf wäre viel besser.

Andernfalls möchte ich Ihnen sagen, was ich wirklich erreichen möchte: Ich muss die Möglichkeit haben, dass ein Benutzer eine Sammlung von Dokumenten in einer einzigen Anfrage herunterladen kann. Irgendwelche Ideen, wie dies erreicht werden kann?

Esteban Araya
quelle
1
@Chesso: Ja, von einer ASPX-Seite.
Esteban Araya
1
Ich fand dieses Beispiel nützlich, als ich vor ein paar Wochen nach dem gleichen Ding suchte: syntaxwarriors.com/2012/…
JensB
2
Wenn Sie das 4.5 Framework verwenden, gibt es jetzt die Klassen ZipArchive und ZipFile.
GalacticJello
Hat jemand DotNetZip benutzt ??
Hot Licks

Antworten:

85

Verwenden Sie .NET 3.5? Sie können die ZipPackageKlasse und verwandte Klassen verwenden. Es ist mehr als nur das Komprimieren einer Dateiliste, da für jede hinzugefügte Datei ein MIME-Typ gewünscht wird. Es könnte tun, was Sie wollen.

Ich verwende diese Klassen derzeit für ein ähnliches Problem, um mehrere verwandte Dateien in einer einzigen Datei zum Download zu archivieren. Wir verwenden eine Dateierweiterung, um die Download-Datei mit unserer Desktop-App zu verknüpfen. Ein kleines Problem, auf das wir gestoßen sind, war, dass es nicht möglich ist, nur ein Drittanbieter-Tool wie 7-zip zum Erstellen der Zip-Dateien zu verwenden, da der clientseitige Code sie nicht öffnen kann. ZipPackage fügt eine versteckte Datei hinzu, die den Inhaltstyp von beschreibt jede Komponentendatei und kann keine Zip-Datei öffnen, wenn diese Inhaltstypdatei fehlt.

Brian Ensink
quelle
1
Oh so, wie ich dich liebe! Danke Brian; Du hast uns gerade eine Menge Kopfschmerzen und ein paar Dollar gespart.
Esteban Araya
6
Beachten Sie, dass dies nicht immer umgekehrt funktioniert. Einige Zip-Dateien werden mit der ZipPackage-Klasse nicht rehydriert. Mit ZipPackage erstellte Dateien werden also gut sein.
Craig
Beachten Sie, dass ZipPackage nicht an ein vorhandenes komprimiertes Paket angehängt werden kann.
ΩmegaMan
Seufz: "Der Typ oder Namespace" Packaging "existiert nicht im Namespace" System.IO ".
Hot Licks
2
(Antwort auf das obige "Seufzen": Öffnen Sie "Referenzen" und fügen Sie (unlogischerweise) "WindowsBase" hinzu.)
Hot Licks
307

Wie kann ich eine Datei (unter Windows) programmgesteuert (C #) komprimieren, ohne Bibliotheken von Drittanbietern zu verwenden?

Wenn Sie das 4.5+ Framework verwenden, gibt es jetzt die Klassen ZipArchive und ZipFile .

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

Sie müssen Verweise hinzufügen auf:

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

Für .NET Core Targeting Net46 müssen Sie Abhängigkeiten für hinzufügen

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

Beispiel project.json:

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

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

Für .NET Core 2.0 ist lediglich das Hinzufügen einer einfachen using-Anweisung erforderlich:

  • using System.IO.Compression;
GalacticJello
quelle
4
Wie hat das nicht mehr positive Stimmen bekommen? Es ist die einzige direkte Antwort.
Matt Cashatt
12
Weil die Frage fünf Jahre alt ist, während diese Antwort nur zwei Monate alt ist. Derp :-P
Heliac
3
@heliac immer noch sollte das Stackoverflow-Ding ein Frage- und Antwort-Repository sein und im Geiste sollte die beste Antwort oben sein ... (verdammt, ich wusste, dass dies nicht funktioniert)
Offler
5
Nur für den Fall, dass es jemandem hilft, ist das zweite Argument der Dateieintrag. Dies ist der Pfad, in den die Datei relativ zum Entpackungsordner extrahiert wird. In Windows 7 stellte ich fest, dass der native Windows-Extraktor fehlschlägt, wenn der Dateieintrag ein vollständiger Pfad ist, z. B. @ "D: \ Temp \ file1.pdf". Dieses Problem kann auftreten, wenn Sie einfach die aus Directory.GetFiles () resultierenden Dateinamen verwenden. Am besten extrahieren Sie den Dateinamen mit Path.GetFileName () für das Dateieintragsargument.
Manish
2
Ich scheine nicht in der Lage zu sein, dies in 4.5.2 zu finden?
user3791372
11

Ich war in der gleichen Situation und wollte .NET anstelle einer Bibliothek eines Drittanbieters. Wie bereits erwähnt, reicht es nicht aus, nur die ZipPackage-Klasse (eingeführt in .NET 3.5) zu verwenden. Es gibt eine zusätzliche Datei, die im Archiv enthalten sein muss, damit das ZipPackage funktioniert. Wenn diese Datei hinzugefügt wird, kann das resultierende ZIP-Paket direkt aus dem Windows Explorer geöffnet werden - kein Problem.

Sie müssen lediglich die XML-Datei [Content_Types] im Stammverzeichnis des Archivs mit einem "Standard" -Knoten für jede Dateierweiterung hinzufügen, die Sie einschließen möchten. Nach dem Hinzufügen konnte ich das Paket im Windows Explorer durchsuchen oder programmgesteuert dekomprimieren und dessen Inhalt lesen.

Weitere Informationen zur XML-Datei [Content_Types] finden Sie hier: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Hier ist ein Beispiel für die XML-Datei [Content_Types] (muss genau benannt werden):

<?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>

Und das C # zum Erstellen einer ZIP-Datei:

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)); 
            } 
        } 
    } 

Hinweis:

Joshua
quelle
12
Schönes Beispiel, aber es wird keine ZIP-Datei erstellt. Es entpackt eine vorhandene Datei.
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");
    }
FLACKERN
quelle
Wie kann ich sourceFileName abrufen, wenn ich mich in einem Webapi befinde und eine HttpContext.Current.Request erhalte?
Olivertech
Mehr als eine Datei komprimieren?
Kiquenet
1

Basierend auf Simon McKenzies Antwort auf diese Frage würde ich vorschlagen, zwei Methoden wie diese zu verwenden:

    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
quelle
0

Sieht aus wie Windows könnte nur können Sie tun dies ...

Leider glaube ich nicht, dass Sie einen separaten Prozess starten können, wenn Sie nicht zu einer Komponente eines Drittanbieters wechseln.

Dave Swersky
quelle
0

Fügen Sie Ihrem Projekt diese 4 Funktionen hinzu:

        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{}
        }
    }

Und benutze sie so:

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