I had a need of a way to create ZIP files containing both files and folders but I didn't want to introduce a third party control or excessive amounts of code that would result in a difficult to maintain solution for a client.
After a quick check to see what was in the .Net framework I came across GZipStream in System.IO.Compression. Unfortunately this is limited to Zipping only one stream or file at a time, so this wasn't going to help me. I then had a look on the Interweb and came across several solutions all of which involved the use of either a closed third party control of a chunky open source project. Again neither of these solutions were what I wanted. I turned again to the .Net Framework and wondered if it would be possible to get the GZipStream to bend to my will, but then I discovered System.IO.Packaging.
System.IO.Packaging contains classes that support the storage of multiple objects in a single container called a Package. This Package can be used to hierarchically organise objects like files and folders into a single entity such as a ZIP file. Each physical item or file is represented by a PackagePart.
It’s also possible to define relationships between PackageParts contained in a Package, apply a PackageDigitalSignature to identify the originator and validate that the signed parts and relationships contained in the Package have not been tampered with. Packages also support Digital Rights Management allowing Package Parts to be encrypted with specific access rights granted to authorized users.
This is exactly what I wanted, no third party controls and pure .Net Framework. The code examples below were created based on the following MSDN samples:
The following code section illustrates the method I used to create a Zip Package:
/// <summary>
/// This method will creates a Zip package file containing specified
/// set of files and folders.
/// </summary>
/// <param name="packagePath">
/// This is the path and file name of the Zip package to be created.
/// </param>
/// <param name="pathsToPackage">
/// This is a string array representing the full paths and file or folder
/// names to be included in the Zip Package.
/// </param>
public void CreatePackage(string packagePath, string[] pathsToPackage)
{
// Create the Package
// (If the package file already exists, FileMode.Create will
// automatically delete it first before creating a new one.
// The 'using' statement insures that 'package' is
// closed and disposed when it goes out of scope.)
using (Package package = Package.Open(packagePath, FileMode.Create))
{
for (int index = 0; index < pathsToPackage.Length; index++)
{
// Get the path to the parent of the source item. This will be
// removed from the file paths leaving any relative folder paths
// to the items to be added as PackageParts.
string pathRoot = Directory.GetParent(pathsToPackage[index]).FullName;
// Recursively, get all the paths to files in a specified path.
string[] filePathsToPackage = GetAllFilesInPath(pathsToPackage[index]);
// Create Package Parts for each file and add to the Package.
for (int fileIndex = 0; fileIndex < filePathsToPackage.Length; fileIndex++)
{
// Convert system path and file names to Part URIs.
Uri partUriFile = PackUriHelper.CreatePartUri(new Uri(filePathsToPackage[fileIndex].Replace(pathRoot, ""), UriKind.Relative));
// Add the file part to the Package, with maximum compression
PackagePart packagePart = package.CreatePart(partUriFile, ContentType(filePathsToPackage[index]), CompressionOption.Maximum);
// Copy the data to the Package Part
using (FileStream fileStream = new FileStream(filePathsToPackage[fileIndex], FileMode.Open, FileAccess.Read))
{
CopyStream(fileStream, packagePart.GetStream());
}
}
}
}
}
In order to extract the contents of the package I use the following methods to extract to a target folder:
/// <summary>
/// Extracts content and resource parts from a given Package
/// zip file to a specified target directory.</summary>
/// <param name="packagePath">
/// The relative path and filename of the Package zip file.</param>
/// <param name="targetDirectory">
/// The relative path from the current directory to the target folder.
/// </param>
public void ExtractPackage(string packagePath, string targetDirectory)
{
// Open the Package.
using (Package package = Package.Open(packagePath, FileMode.Open, FileAccess.Read))
{
// Get a reference to the collection of the Package Parts in the package.
PackagePartCollection packagePartCollection = package.GetParts();
// For each Part in the collection Extract to the Target Dir
foreach (PackagePart part in packagePartCollection)
{
ExtractPart(part, targetDirectory);
}
}
}
/// <summary>
/// Extracts a specified package part to a target folder.
/// </summary>
/// <param name="packagePart">
/// The package part to extract.
/// </param>
/// <param name="targetDirectory">
/// The absolute path to the targer folder.
/// </param>
protected void ExtractPart(PackagePart packagePart, string targetDirectory)
{
// Create a string with the full path to the target directory.
string pathToTarget = targetDirectory;
// Remove leading slash from the Part Uri, and make a new Uri from the result
string stringPart = packagePart.Uri.ToString().TrimStart('/');
Uri partUri = new Uri(stringPart, UriKind.Relative);
// Create a full Uri to the Part based on the Package Uri
Uri uriFullPartPath = new Uri(new Uri(pathToTarget, UriKind.Absolute), partUri);
// Create the necessary Directories based on the Full Part Path
Directory.CreateDirectory(Path.GetDirectoryName(uriFullPartPath.LocalPath));
// Create the file with the Part content
using (FileStream fileStream = new FileStream(uriFullPartPath.LocalPath, FileMode.Create))
{
CopyStream(packagePart.GetStream(), fileStream);
}
}
Check out the attached file for the complete sample.
ZipSample.txt (8.74 kb)
UPDATED: Turns out this is a waste of time, it looks like a ZIP file, it smells like a ZIP file but it doesn't taste like one. If you need access to the code for a ZIP solution, then you should really have a look at DotNetZip on CodePlex. There are other solutions, but this is nice and easy to use.
62cbf3bc-db2c-4048-9864-e45f82c88cfc|0|.0