¿Cómo comprimo un archivo en C #, sin utilizar API de terceros?

175

Estoy bastante seguro de que esto no es un duplicado, así que tengan paciencia conmigo por solo un minuto.

¿Cómo puedo programar (C #) ZIP un archivo (en Windows) sin usar bibliotecas de terceros? Necesito una llamada nativa de Windows o algo así; Realmente no me gusta la idea de comenzar un proceso, pero lo haré si es absolutamente necesario. Una llamada de PInovke sería mucho mejor.

De lo contrario, déjame decirte lo que realmente estoy tratando de lograr: necesito la capacidad de permitir que un usuario descargue una colección de documentos en una sola solicitud. ¿Alguna idea sobre cómo lograr esto?

Esteban Araya
fuente
1
@Chesso: Sí, desde una página ASPX.
Esteban Araya
1
Encontré este ejemplo útil cuando estaba buscando lo mismo hace unas semanas: syntaxwarriors.com/2012/…
JensB
2
Si usa el Marco 4.5, ahora están las clases ZipArchive y ZipFile.
GalacticJello
¿Alguien usó DotNetZip?
Hot Licks

Respuestas:

85

¿Estás usando .NET 3.5? Podrías usar la ZipPackageclase y las clases relacionadas. Es más que simplemente comprimir una lista de archivos porque quiere un tipo MIME para cada archivo que agregue. Podría hacer lo que quieras.

Actualmente estoy usando estas clases para un problema similar al archivar varios archivos relacionados en un solo archivo para descargar. Usamos una extensión de archivo para asociar el archivo de descarga con nuestra aplicación de escritorio. Un pequeño problema con el que nos encontramos fue que no es posible usar una herramienta de terceros como 7-zip para crear los archivos zip porque el código del lado del cliente no puede abrirlo: ZipPackage agrega un archivo oculto que describe el tipo de contenido de cada archivo componente y no puede abrir un archivo zip si falta ese archivo de tipo de contenido.

Brian Ensink
fuente
1
¡Oh, cómo te amo! Gracias Brian; nos ahorró muchos dolores de cabeza y algo de dinero.
Esteban Araya
66
Tenga en cuenta que esto no siempre funciona a la inversa. Algunos archivos Zip no se rehidratarán usando la clase ZipPackage. Los archivos creados con ZipPackage lo harán bien.
Craig
Tenga en cuenta que ZipPackage no puede agregarse a un paquete comprimido existente.
ΩmegaMan
Suspiro: "El tipo o espacio de nombres" Empaquetado "no existe en el espacio de nombres" System.IO ".
Hot Licks
2
(Responda al "suspiro" anterior: abra "Referencias" y agregue (ilógicamente) "WindowsBase".)
Hot Licks
307

¿Cómo puedo programar (C #) ZIP un archivo (en Windows) sin usar bibliotecas de terceros?

Si se utiliza el marco 4.5+, ahora hay los ZipArchive y ZipFile clases.

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

Necesita agregar referencias a:

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

Para .NET Core apuntando a net46, necesita agregar dependencias para

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

Ejemplo project.json:

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

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

Para .NET Core 2.0, todo lo que se necesita es agregar una simple declaración de uso:

  • usando System.IO.Compression;
GalacticJello
fuente
44
¿Cómo no ha recibido más votos a favor? Es la única respuesta directa.
Matt Cashatt
12
Porque la pregunta tiene cinco años, mientras que esta respuesta tiene solo dos meses. Derp
:-P
3
@heliac sigue siendo el Stackoverflow thingie debería ser un repositorio de preguntas y respuestas y, en el espíritu, la mejor respuesta debería estar en la cima ... (maldición, sabía que esto no funciona)
Offler
55
En caso de que ayude a alguien, el segundo argumento es la entrada del archivo. Esta es la ruta a la que se extraerá el archivo en relación con la carpeta de descompresión. En Windows 7, descubrí que si la entrada del archivo es una ruta completa, por ejemplo, @ "D: \ Temp \ file1.pdf", el extractor nativo de Windows falla. Puede encontrarse con este problema si simplemente usa los nombres de archivo resultantes de Directory.GetFiles (). Lo mejor es extraer el nombre del archivo usando Path.GetFileName () para el argumento de entrada del archivo.
Manish
2
¿Parece que no puedo encontrar esto en 4.5.2?
user3791372
11

Estaba en la misma situación, queriendo .NET en lugar de una biblioteca de terceros. Como otro póster mencionado anteriormente, simplemente usando la clase ZipPackage (introducida en .NET 3.5) no es suficiente. Hay un archivo adicional que DEBE incluirse en el archivo para que funcione ZipPackage. Si se agrega este archivo, el paquete ZIP resultante se puede abrir directamente desde el Explorador de Windows, no hay problema.

Todo lo que tiene que hacer es agregar el archivo [Content_Types] .xml a la raíz del archivo con un nodo "Predeterminado" para cada extensión de archivo que desee incluir. Una vez agregado, podría examinar el paquete desde el Explorador de Windows o descomprimirlo mediante programación y leer su contenido.

Puede encontrar más información sobre el archivo [Content_Types] .xml aquí: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Aquí hay una muestra del archivo [Content_Types] .xml (debe nombrarse exactamente):

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

Y el C # para crear un archivo 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)); 
            } 
        } 
    } 

Nota:

Joshua
fuente
12
Buena muestra, pero no crea un archivo ZIP. Descomprime un archivo existente.
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");
    }
PARPADEO
fuente
¿Cómo puedo obtener sourceFileName cuando estoy dentro de un webapi, recibiendo un HttpContext.Current.Request?
Olivertech
Comprimir más un archivo?
Kiquenet
1

Basado en la respuesta de Simon McKenzie a esta pregunta , sugeriría usar un par de métodos como este:

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

Parece que Windows podría dejarte hacer esto ...

Desafortunadamente, no creo que vaya a comenzar un proceso separado a menos que vaya a un componente de terceros.

Dave Swersky
fuente
0

Agregue estas 4 funciones a su proyecto:

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

Y úsalos así:

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