Jak spakować plik w języku C # bez użycia interfejsów API innych firm?

175

Jestem prawie pewien, że to nie jest duplikat, więc wytrzymaj ze mną przez minutę.

Jak programowo (C #) skompresować plik (w systemie Windows) bez korzystania z bibliotek innych firm? Potrzebuję natywnego wywołania systemu Windows lub czegoś takiego; Naprawdę nie podoba mi się pomysł rozpoczęcia procesu, ale zrobię to, jeśli będę musiał. Połączenie z PInovke byłoby znacznie lepsze.

Jeśli to się nie powiedzie, powiem ci, co naprawdę próbuję osiągnąć: potrzebuję możliwości, aby użytkownik mógł pobrać zbiór dokumentów w jednym żądaniu. Jakieś pomysły, jak to osiągnąć?

Esteban Araya
źródło
1
@Chesso: Tak, ze strony ASPX.
Esteban Araya
1
Ten przykład okazał się przydatny, gdy kilka tygodni temu szukałem
JensB
2
Jeśli korzystasz z 4.5 Framework, są teraz klasy ZipArchive i ZipFile.
GalacticJello
Ktoś używał DotNetZip?
Hot Licks

Odpowiedzi:

85

Czy używasz platformy .NET 3.5? Możesz użyć ZipPackageklasy i klas pokrewnych. To coś więcej niż tylko spakowanie listy plików, ponieważ wymaga typu MIME dla każdego dodawanego pliku. Może zrobić, co chcesz.

Obecnie używam tych klas do podobnego problemu, aby zarchiwizować kilka powiązanych plików w jednym pliku do pobrania. Używamy rozszerzenia pliku, aby powiązać pobrany plik z naszą aplikacją komputerową. Jednym małym problemem, na który natknęliśmy się, było to, że nie można po prostu użyć narzędzia innej firmy, takiego jak 7-zip, do utworzenia plików zip, ponieważ kod po stronie klienta nie może go otworzyć - ZipPackage dodaje ukryty plik opisujący typ zawartości każdego pliku składnika i nie można otworzyć pliku zip, jeśli brakuje tego pliku typu zawartości.

Brian Ensink
źródło
1
O tak, jak ja cię kocham! Dzięki Brian; właśnie zaoszczędził nam wielu bólów głowy i trochę pieniędzy.
Esteban Araya
6
Zauważ, że to nie zawsze działa odwrotnie. Niektóre pliki ZIP nie będą ponownie nawadniać przy użyciu klasy ZipPackage. Pliki utworzone za pomocą ZipPackage będą, więc powinieneś być dobry.
Craig
Zauważ, że ZipPackage nie może dołączyć do istniejącego spakowanego pakietu.
ΩmegaMan,
Westchnienie: „Typ lub przestrzeń nazw„ Opakowanie ”nie istnieje w przestrzeni nazw„ System.IO ”.
Hot Licks
2
(Odpowiedź na powyższe „westchnienie”: otwórz „Referencje” i dodaj (nielogicznie) „WindowsBase”.)
Hot Licks
307

Jak programowo (C #) skompresować plik (w systemie Windows) bez korzystania z bibliotek innych firm?

Jeśli używasz wersji 4.5+ Framework, są teraz klasy ZipArchive i ZipFile .

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

Musisz dodać odniesienia do:

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

W przypadku platformy .NET Core przeznaczonej dla sieci Net46 należy dodać zależności dla platformy

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

Przykładowy project.json:

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

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

W przypadku .NET Core 2.0 wystarczy dodać prostą instrukcję using:

  • using System.IO.Compression;
GalacticJello
źródło
4
Jak to nie zyskało większej liczby głosów pozytywnych? To jedyna bezpośrednia odpowiedź.
Matt Cashatt
12
Ponieważ pytanie ma pięć lat, podczas gdy ta odpowiedź ma dopiero dwa miesiące. Derp :-P
Heliac
3
@heliac nadal rzecz ze Stackoverflow powinna być repozytorium pytań i odpowiedzi iw duchu najlepsza odpowiedź powinna być na górze ... (cholera, wiedziałem, że to nie działa)
Offler
5
Na wypadek, gdyby to komuś pomogło, drugim argumentem jest wpis do pliku. To jest ścieżka, do której plik zostanie wyodrębniony w stosunku do folderu rozpakowania. W systemie Windows 7 stwierdziłem, że jeśli wpis pliku jest pełną ścieżką, np. @ "D: \ Temp \ plik1.pdf", natywny ekstraktor systemu Windows nie działa. Możesz napotkać ten problem, jeśli po prostu użyjesz nazw plików wynikających z Directory.GetFiles (). Najlepiej wyodrębnić nazwę pliku za pomocą funkcji Path.GetFileName () jako argumentu pozycji pliku.
Manish
2
Wydaje się, że nie mogę tego znaleźć w 4.5.2?
user3791372
11

Byłem w tej samej sytuacji, chciałem .NET zamiast biblioteki innej firmy. Jak na innym plakacie wspomniano powyżej, samo użycie klasy ZipPackage (wprowadzonej w .NET 3.5) nie wystarczy. Istnieje dodatkowy plik, który MUSI zostać dołączony do archiwum, aby ZipPackage działał. Po dodaniu tego pliku wynikowy pakiet ZIP można otworzyć bezpośrednio z Eksploratora Windows - nie ma problemu.

Wszystko, co musisz zrobić, to dodać plik [Content_Types] .xml do katalogu głównego archiwum z węzłem „Default” dla każdego rozszerzenia pliku, które chcesz dołączyć. Po dodaniu mogłem przeglądać pakiet z Eksploratora Windows lub programowo zdekompresować i przeczytać jego zawartość.

Więcej informacji na temat pliku [Content_Types] .xml można znaleźć tutaj: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Oto przykład pliku [Content_Types] .xml (musi mieć dokładnie taką nazwę):

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

Oraz C # do tworzenia pliku 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)); 
            } 
        } 
    } 

Uwaga:

Joshua
źródło
12
Niezła próbka, ale nie tworzy pliku ZIP. Rozpakowuje istniejący plik.
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");
    }
MIGOTANIE
źródło
Jak mogę uzyskać sourceFileName, gdy jestem wewnątrz webapi i odbieram HttpContext.Current.Request?
Olivertech
Skompresować więcej jednego pliku?
Kiquenet
1

Opierając się na odpowiedzi Simona McKenziego na to pytanie , proponuję zastosować parę metod takich jak ta:

    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
źródło
0

Wygląda jak Windows może po prostu pozwalają zrobić to ...

Niestety nie sądzę, abyś mógł rozpocząć oddzielny proces, chyba że przejdziesz do komponentu strony trzeciej.

Dave Swersky
źródło
0

Dodaj te 4 funkcje do swojego projektu:

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

I używaj ich w ten sposób:

foreach (string f in UnitZipList)
{
    AddFileToZip(zipFile, f);
    System.IO.File.Delete(f);
}
Remove_Content_Types_FromZip(zipFile);
Teemo
źródło