Wednesday, May 16, 2012

Using System.IO.Packaging Package class to zip a file

Hello World,

I believe all of us who work with software that writes reports would have encountered scenarios when you had a huge report e..g one month of sales data, that required to be compressed to make it portable. I was faced with one such requirement and as always I started researching on what my options were.
               A google search came up with options like the popular 7-Zip library which is un-managed code, and DotNetZip, a codeplex open source library that seemed fit for the purpose. My mandate though (from the powers that be in an IT organization :)) was to try to avoid using a third party dll.
              Now, as a .NET programmer, I would love if the framework itself provided services to pacakge and compress files rather than having to look for a third party library: and the framework does not dissappoint in this regard. There are a couple of classes that provide packaging and compression:
  1. The System.IO.Compression GZipStream class
  2. The System.IO.Packaging Package class 
A requirement from the solution I was trying to devise was that the compression should produce a file that does not require additional software to decompress (unzip) it. Though the GZipStream class offers an elegant and efficient way to package and compress files, it requires a software that understands the gzip compression scheme. Such programmes are freely available, but require downloading and installing nevertheless! That ruled out GZipStream (I'll try to post sample code for that in another post)

Notice how I'm referring to "Package" and "compression" as the two steps involved here. A "Package" is a container that holds several objects in it. Consider a directory with several files in it - that is a container, although not very portable (not too straightforward to move across network from one machine to another). I know you'll say "I can use xcopy to copy over the directory anywhere I like!" you very well can, but wouldn't it be nice if there was some way of treating the directory as a single object that holds a relationship with it's content? Unix based systems have the "tar" utility (short for tape archive) that can package several resources (files) into a single file-like object that can be treated thus. This object then is a "Package".

Now, MSDN has an example showing how to use the Package class, but then, if all samples did everything you wanted, you wouldn't have a job : ). If you run the sample (from the downloaded solution), it would package everything nicely, but does not compress anything !!

So here is how I achived the task of packaging and compressing the file: First the class that calls the methods on Package class-



   1:      /// <summary>
   2:      /// TODO: Update summary.
   3:      /// </summary>
   4:      public class ZipPackageCompressor
   5:      {
   9:   
  10:          //  -------------------------- CreatePackage --------------------------
  11:          /// <summary>
  12:          /// Creates a package zip file containing specified
  13:          /// content and resource files.
  14:          /// </summary>
  15:          /// <param name="fileToZip">
               ///    The path to the file to zip. <example>E.g. C:\myfolder\thefileToxip.csv
               ///    </example></param>
  16:          /// <param name="pathToOutput">
               ///    The path where output(zipped file) will be placed. 
               ///    <example>E.g. D:\output</example>
               /// </param>
  17:          public void CreatePackage(string fileToZip, string pathToOutput)
  18:          {
  19:              // Convert system path and file names to Part URIs. In this example
  20:              string fileName = Path.GetFileName(fileToZip);
  21:              Uri partUriDocument = PackUriHelper.CreatePartUri(new Uri(fileName, UriKind.Relative));
  22:              string outputFileName = Path.Combine(pathToOutput, fileName + ".zip");
  23:   
  24:              // Create the Package
  25:              // If the package file already exists, FileMode.Create will automatically 
                   // delete it first before creating a new one.
  26:              using (var package = ZipPackage.Open(outputFileName, FileMode.Create, FileAccess.ReadWrite))
  27:              {
  28:                  // Add the Document part to the Package
  29:                  PackagePart packagePartDocument = package.CreatePart(
                                                                  partUriDocument, 
                                                                  System.Net.Mime.MediaTypeNames.Text.Plain, 
                                                                  CompressionOption.Maximum);
  30:                  
  31:                  // Copy the data to the Document Part
  32:                  using (FileStream fileStream = new FileStream(fileToZip, FileMode.Open, FileAccess.Read))
  33:                  {
  34:                      CopyStream(fileStream, packagePartDocument.GetStream());
  35:                  }
  36:              }
  37:          }
  38:   
  39:   
  40:          /// <summary>
  41:          /// Copies data from a source stream to a target stream.
  42:          /// </summary>
  43:          /// <param name="source">The source stream to copy from.</param>
  44:          /// <param name="target">The destination stream to copy to.</param>
  45:          private void CopyStream(Stream source, Stream target)
  46:          {
  47:              const int bufSize = 0x1000;
  48:              byte[] buf = new byte[bufSize];
  49:              int bytesRead = 0;
  50:              while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
  51:              {
  52:                  target.Write(buf, 0, bytesRead);
  53:              }
  54:          }
  55:      }

Notice the call to package.CreatePart on line 29 with the third parameter "CompressionOption.Maximum" this is what actually compresses the file. I believe the comments in the code make it pretty straight forward to understand what is going on.

To work with the System.IO.Packaging namespace, you would need a reference to the WindowsBase assembly:


To wind up, here is a test program that calls the CreatePackage above:


   1:      public class Program
   2:      {
   3:   
   4:          public static void Main()
   5:          {
   6:              // Path to directory of files to compress and decompress.
   7:              string dirPath = AssemblyDirectory;
   8:              var zipPackageCompressor = new ZipPackageCompressor();
   9:              zipPackageCompressor.CreatePackage(
                          @"D:\Sudhanshu\Projects\FileZipper\FileZipper\Contents\MyFile_bcce1c37.csv", 
                          AssemblyDirectory);
  10:          }
  11:   
  12:          /// <summary>
  13:          /// Gets the directory from where the (EXE) assembly is executing.
  14:          /// </summary>
  15:          public static string AssemblyDirectory
  16:          {
  17:              get
  18:              {
  19:                  string assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
  20:                  return assemblyPath;
  21:              }
  22:          }
  23:   
  24:      }
Happy Coding!

Thursday, April 19, 2012

C#: Watching an app.config file for changes (and responding)

Hello World,

We had this windows service that had configuration in it's app.config that governed it's behaviour. A requirement from this service was that it should watch the config file and respond to changes made without having to be re-started (a restart would re-load the app domain and re-read the config anyways)
Of several possible solutions, my approach was to watch the file for changes using .NET's FileSystemWatcher class and then consume it's OnChange event to refresh/reload config values.

Draft 1: A prototype console application that implements the above solution


   1:  class Program
   2:      {
   3:          private static bool _stopService = false;
   4:   
   5:          static void Main(string[] args)
   6:          {
   7:              Console.WriteLine("Starting Worker Process");
   8:              Thread myWorkerThread = new Thread(Run) { Name = "Run Worker Thread" };
   9:              myWorkerThread.Start();
  10:              Console.WriteLine("Press any key to stop...");
  11:              Console.ReadLine();
  12:              _stopService = true;
  13:          }
  14:   
  15:          static void Run()
  16:          {
  17:              string assemblyDirectory = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
  18:              NotifyFilters notifyFilters = NotifyFilters.LastWrite; 
  19:   
  20:              FileSystemWatcher fileSystemWatcher = new FileSystemWatcher() { 
										Path = assemblyDirectory, 
										NotifyFilter = notifyFilters, 
										Filter = "*.config" };
  21:              fileSystemWatcher.Changed += OnChanged;
  22:              fileSystemWatcher.EnableRaisingEvents = true;
  23:   
  24:              Console.WriteLine("Watching for changes...");
  25:              while (!_stopService)
  26:              {
  27:                  Thread.Sleep(5 * 1000);
  28:              }
  29:          }        
  30:   
  31:          static void OnChanged(object source, FileSystemEventArgs e)
  32:          {
  33:              Console.WriteLine("Change event handler invoked...");
  34:              Console.WriteLine("Value before refresh: {0}", ConfigurationManager.AppSettings["myKey"]);
  35:              ConfigurationManager.RefreshSection("appSettings");
  36:              Console.WriteLine("Value after change: {0}", ConfigurationManager.AppSettings["myKey"]);
  37:          }
  38:      }
 
 
The application config file looks like this:
   1:  <xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <appSettings>
   4:      <add key="myKey" value="testValue"/>
   5:    <appSettings>
   6:  <configuration>


Notes on the implementation:

  1. By launching the FileSystemWatcher on a worker thread, I am attempting to simulate the scenario in the actual service, so ignore those bits for now
  2. Line number 22 flags the FileSystemWatcher to raise events, needless to say, if you miss setting this flag, the events would never be raised
  3. You can get details of what has triggered the event in terms of full path of the object that was watched, change type (viz. changed, deleted, created etc.) through the FileSystemEventArgs
Issues:
When run, if you modify the app.config in your program's output directory and save it, you'll see the following output:





Did you see? The OnChanged event has been raised twice for a single change. I began researching this and found that this is a known behaviour (by design) as documented stackoverflow and here
Now, the resolution (at least the one I got to work):
Draft 2: In the OnChanged event, use a try finally block to set EnableRaisingEvents to false (in the try) and then set it back to true in the finally block:

   1:  class Program
   2:      {
   3:          private static bool _stopService = false;
   4:          private static FileSystemWatcher _fileSystemWatcher;
   5:   
   6:          static void Main(string[] args)
   7:          {
   8:              Console.WriteLine("Starting Worker Process");
   9:              Thread myWorkerThread = new Thread(Run) { Name = "Run Worker Thread" };
  10:              myWorkerThread.Start();
  11:              Console.WriteLine("Press any key to stop...");
  12:              Console.ReadLine();
  13:              _stopService = true;
  14:          }
  15:   
  16:          static void Run()
  17:          {
  18:              string assemblyDirectory = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
  19:              NotifyFilters notifyFilters = NotifyFilters.LastWrite; 
  20:   
  21:              _fileSystemWatcher = new FileSystemWatcher() { 
			Path = assemblyDirectory, 
			NotifyFilter = notifyFilters, 
			Filter = "*.config" };
  22:              _fileSystemWatcher.Changed += OnChanged;
  23:              _fileSystemWatcher.EnableRaisingEvents = true;
  24:   
  25:              Console.WriteLine("Watching for changes...");
  26:              while (!_stopService)
  27:              {
  28:                  Thread.Sleep(5 * 1000);
  29:              }
  30:          }        
  31:   
  32:          static void OnChanged(object source, FileSystemEventArgs e)
  33:          {
  34:              try
  35:              {
  36:                  _fileSystemWatcher.EnableRaisingEvents = false;
  37:                  Console.WriteLine("Change event handler invoked...");
  38:                  Console.WriteLine("Value before refresh: {0}", ConfigurationManager.AppSettings["myKey"]);
  39:                  ConfigurationManager.RefreshSection("appSettings");
  40:                  Console.WriteLine("Value after change: {0}", ConfigurationManager.AppSettings["myKey"]);
  41:              }
  42:              finally
  43:              {
  44:                  _fileSystemWatcher.EnableRaisingEvents = true;
  45:              }
  46:          }
  47:      }

So now the output looks like this:



Great! Another day saved!

Happy Coding!

PS: The code formatting above is powered by Manoli.net

Tuesday, March 27, 2012

TFS 2010 Destroying unwanted builds

Hello World,

If you've worked with TFS builds for any length of time, you would've surely had days when the build just would not do what it is supposed to do: build stuff!!
We had this issue the other day when tfs build was stuck and some of my builds did not complete, yet the version numbers got incremented. Now what I wanted is to delete those "bad" builds, reset the build number and then fire a build that would get me the expected build number. I fired up my build explorer for the build definition in question and then clicked on "Delete Build":
Next, reset the version number in the build configuration folder and queued up a new build. To my horror, TFS came back with this:
Build Failed: version 1.0.10.0 already exists!

So, I turned to the trusted Visual Studio command prompt to look up the TFSbuild tool's destroy command. MSDN describes this in detail. Unfortunately, the following command:

TFSBuild destroy /collection:http://tfs:8080/tfs/defaultcollection /BuildDefinition:"\Team Project\My Shiny Product" 1.0.10.0

Comes back with saying: No builds found for build specification '1.0.10.0'.

I tried the /daterange switch then:

TFSBuild destroy /collection:http://tfs:8080/tfs/defaultcollection /BuildDefinition:"\Team Project\My Shiny Product" /daterange:~2012-03-27

Note that this would come back with a confirmation for each build for the team project in the date range, something similar to:
Destroy build record for MyShinyProduct Production 2.0.43.0 permanently? (Yes/No/All)

Be very careful at what you respond 'cause you obviously do not want to delete somthing required!
I found the one I was interested in: 1.0.10.0 and confirmed the deletion. Then the usual reset of the build number and lo: everything was hunky dory again!

Happy Coding!!

Tuesday, March 20, 2012

Rollback a changeset in TFS 2010

Hello World,

I guess each one of us has this "bad hair day" when you just can't get things right. On one such day, I checked in some code to TFS just to realise while testing that I have broken stuff! Now, I came from a background in VSS and SVN as SCM (Software Configuration Management) systems and they both had an option to "Rollback" to the previous version so you could just undo any malaise you may have caused.
I was taken aback when I did not see this option in the context menu in TFS if I right clicked a changeset upto which things were working. One of my colleagues suggested the following:
  1. Do a "Get Specific" with the changeset upto which everything was fine
  2. Checkout all affected files
  3. Resolve conflicts (there should be some)
  4. Checkin the files
You effectively overwrite your changes with the previous changeset.
Some things to note though:
  • If project and solution files were modified, you may need additional checks to ensure they have been rolled back
  • Sometimes, you may need to make some changes so that TFS detects that the files have changed (e.g. add a space at the end of a codefile
Turns out, there is another way to do a rollback through the TF commandline tool (using VS command prompt) as detailed in MSDN although, as the comments at the bottom suggest, you may need to change directory to your workspace.

Happy Coding!

Monday, March 19, 2012

ASP.NET use jQuery post for ajax

Hello World,

I came through an interesting scenario which required us to change an ASP.NET form submit to ensure that the browser does not show this message:
Entire discussion and solution on this stackoverflow link
Happy Coding

Wednesday, March 14, 2012

Baseless merge in TFS 2010

Hello World,

              I was recently involved in a major rewrite of one of our products and needed to prep a release candidate. As most organisations do, our ALM (Application Lifecycle Managemet) practices require that changes be merged all the way up to the Main branch and then a branch be derived out of that with the "Release X.x" format. (if you haven't guessed already, X is the major version and x the minor one).
So, this was a major rewrite and it had to be a baseless merge. Now as per MSDN, a baseless merge is "process of merging items that are not directly branched from each other" [MSDN]; whilst my dev branch was related all the way up to Main, the reason I decided it had to be a baseless merge is because I did not want TFS to bother about the relationship of the files in the branches. For example, if I have renamed a file, this should be viewed as a deleted file and a new file be added in the branch.
            A baseless merge also requires more manual conflict resolutions than when you perform a normal merge but I was fine with that as I required more control in liu of the extra manual work.
The way things are, basless merge can only be done through command prompt so you need to fire up the Visual studio command prompt [for the heck of it, the VS command prompt just sets some environment variables so you don't have to type the entire path to VS executables]. The details are there in the MSDN link so I'll just mention my experiences:

  1. File deletions do not always get merged up (This blog suggests that the TFS server must be SP1 or higher), so to be 100% confident, always compare the source and target branches of merge after the merge
  2. If files in the target branch are locked (e.g. binary files are locked if some other user has checked them out for edit), you can use the TFS Sidekicks tool to undo the checkout. Caveat: you should have administrator access to the team project
Happy Coding!

Saturday, March 3, 2012

Hello World,

An excellent video by Jeffery Richter (CLR via C# fame) on designing and developing multi-threaded applications using .NET 4.0 features: Here

Happy coding