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
On my 64bit Vista machine, I'm trying to build a little application which makes use of a set of DLLs from the Team Foundation Server Client. The application builds fine. However, when I attempt to run the application in debug, I get the following error:
=== Pre-bind state information ===
LOG: User =
LOG: DisplayName = Microsoft.TeamFoundation.Client, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
(Fully-specified)
LOG: Appbase = file:///C:/Development/TFSImport/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : TFSImport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v2.0.50727\config\machine.config.
LOG: Post-policy reference: Microsoft.TeamFoundation.Client, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
LOG: Attempting download of new URL file:///C:/Development/TFSImport/bin/Debug/Microsoft.TeamFoundation.Client.DLL.
ERR: Failed to complete setup of assembly (hr = 0x8007000b). Probing terminated.
After a bit of head scratching I realised that the TFS Client DLLs are 32-Bit and the application was being built for a 64-Bit platform. To fix this problem I had to get the build targeting a 32bit platform. To do this, I opened up the Configuration Manager in Visual Studio:
From the Active Solution Platform, I chose the drop down menu item "<New...>".
A dialog appeared called "New Solution Platform".
In this dialog, I selected the item "x86" from the drop down list "Type or select the new platform:", then clicked OK.
You'll notice that the Configuration Manager has been updated with the new Platform details for the project.
I now click the "Close" button.
Now I'm able to build and run my application in debug.
a5e7de8a-f07d-48ce-bb19-24f15da894e4|2|5.0