Como zip um arquivo em C #, sem APIs de terceiros?

175

Eu tenho certeza que isso não é uma cópia, então tenha paciência comigo por apenas um minuto.

Como posso zipar programaticamente (C #) um arquivo (no Windows) sem usar bibliotecas de terceiros? Eu preciso de uma chamada nativa do Windows ou algo assim; Realmente não gosto da idéia de iniciar um processo, mas irei se for absolutamente necessário. Uma ligação PInovke seria muito melhor.

Caso contrário, deixe-me dizer o que realmente estou tentando realizar: preciso da capacidade de permitir que um usuário baixe uma coleção de documentos em uma única solicitação. Alguma idéia de como fazer isso?

Esteban Araya
fonte
1
@Chesso: Sim, a partir de uma página ASPX.
Esteban Araya
1
Eu encontrei este exemplo útil quando estava pesquisando a mesma coisa há algumas semanas: syntaxwarriors.com/2012/…
JensB
2
Se você estiver usando o 4.5 Framework, agora existem as classes ZipArchive e ZipFile.
GalacticJello
Alguém usou DotNetZip ??
Hot Licks

Respostas:

85

Você está usando o .NET 3.5? Você pode usar a ZipPackageclasse e classes relacionadas. É mais do que apenas compactar uma lista de arquivos, porque deseja um tipo MIME para cada arquivo adicionado. Pode fazer o que você quiser.

Atualmente, estou usando essas classes para um problema semelhante para arquivar vários arquivos relacionados em um único arquivo para download. Usamos uma extensão de arquivo para associar o arquivo de download ao nosso aplicativo de desktop. Um pequeno problema que encontramos foi que não é possível usar apenas uma ferramenta de terceiros como o 7-zip para criar os arquivos zip, porque o código do lado do cliente não pode abri-lo - o ZipPackage adiciona um arquivo oculto que descreve o tipo de conteúdo do arquivo zip. cada arquivo de componente e não pode abrir um arquivo zip se esse arquivo de tipo de conteúdo estiver ausente.

Brian Ensink
fonte
1
Oh, então, como eu te amo! Obrigado Brian; o yuo acabou de nos salvar muitas dores de cabeça e alguns $$$.
Esteban Araya
6
Observe que isso nem sempre funciona ao contrário. Alguns arquivos Zip não serão reidratados usando a classe ZipPackage. Os arquivos criados com o ZipPackage serão adequados para você.
Craig
Observe que o ZipPackage não pode ser anexado a um pacote compactado existente.
EgamegaMan 23/10
Suspiro: "O tipo ou espaço para nome" Packaging "não existe no espaço para nome" System.IO ".
Licks Hot
2
(Resposta ao "suspiro" acima: Abra "Referências" e adicione (ilogicamente o suficiente) "WindowsBase".)
Hot Licks
307

Como posso zipar programaticamente (C #) um arquivo (no Windows) sem usar bibliotecas de terceiros?

Se você estiver usando o 4.5+ Framework, agora existem as classes ZipArchive e ZipFile .

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

Você precisa adicionar referências a:

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

Para o .NET Core de destino net46, você precisa adicionar dependências para

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

Exemplo project.json:

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

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

Para o .NET Core 2.0, basta adicionar uma instrução simple using:

  • using System.IO.Compression;
GalacticJello
fonte
4
Como isso não ficou mais positivo? É a única resposta direta.
precisa saber é o seguinte
12
Porque a pergunta tem cinco anos, enquanto essa resposta tem apenas dois meses. Derp :-P
Heliac
3
@heliac ainda o thingie Stackoverflow deve ser uma pergunta e respostas repositório e no espírito a melhor resposta shoudl estar no topo ... (Droga, eu sabia que isso não funciona)
Offler
5
No caso de ajudar alguém, o segundo argumento é a entrada do arquivo. Este é o caminho para o qual o arquivo será extraído em relação à pasta descompactar. No Windows 7, descobri que, se a entrada do arquivo for um caminho completo, por exemplo, @ "D: \ Temp \ file1.pdf", o extrator nativo do Windows falhará. Você pode encontrar esse problema se simplesmente usar os nomes de arquivos resultantes de Directory.GetFiles (). É melhor extrair o nome do arquivo usando Path.GetFileName () para o argumento de entrada do arquivo.
Manish
2
Parece que não consigo encontrar isso no 4.5.2?
User3791372 2/16
11

Eu estava na mesma situação, desejando .NET em vez de uma biblioteca de terceiros. Como outro pôster mencionado acima, o simples uso da classe ZipPackage (introduzida no .NET 3.5) não é suficiente. Há um arquivo adicional que DEVE ser incluído no arquivo para que o ZipPackage funcione. Se esse arquivo for adicionado, o pacote ZIP resultante poderá ser aberto diretamente no Windows Explorer - não há problema.

Tudo o que você precisa fazer é adicionar o arquivo [Content_Types] .xml à raiz do arquivo morto com um nó "Padrão" para cada extensão de arquivo que você deseja incluir. Uma vez adicionado, eu poderia procurar o pacote no Windows Explorer ou descomprimir programaticamente e ler seu conteúdo.

Mais informações sobre o arquivo [Content_Types] .xml podem ser encontradas aqui: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Aqui está uma amostra do arquivo [Content_Types] .xml (deve ser nomeado exatamente):

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

E o C # para criar um arquivo 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
fonte
12
Boa amostra, mas não cria um arquivo ZIP. Descompacta um arquivo 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");
    }
FLICKER
fonte
Como obter sourceFileName quando estou dentro de um webapi, recebendo um HttpContext.Current.Request?
Olivertech
Comprimir mais um arquivo?
Kiquenet 20/03/19
1

Com base na resposta de Simon McKenzie a esta pergunta , sugiro usar um 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
fonte
0

Parece que o Windows pode permitir que você faça isso ...

Infelizmente, acho que você não conseguirá iniciar um processo separado, a menos que vá para um componente de terceiros.

Dave Swersky
fonte
0

Adicione estas 4 funções ao seu projeto:

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

E use-os assim:

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