Apr 9, 2008

VS TFS extensibility – a custom changeset viewer

I started working on a new project that uses Visual Studio 2005 Team Foundation Server for managing the entire project lifecycle (source control, bug tracking, reporting etc.). This is quite new to me and I tried to acquaint myself with the console commands to carry out the various source control operations. While doing so, I found that the command to view a change set is tf changeset #num. This command pops up a dialog with the list of files that got changed in the given change set. The dialog is shown below…

clip_image002[6]

Unfortunately, if we would like to view the changes done in the change set, we’ll have to click on each file and wait for a second or two for the diff to show up. This, you would agree, is a definite productivity killer and so I wanted to solve the problem.

A little googling revealed the VS TFS extensibility APIs which can be used to write our custom client tools that talk to the team foundation server back-end. I used the APIs to capture the list of files in the change set, download the latest as well as the previous versions of each file and bulk-open them in windiff. The main piece of code is shown below…



// get the changeset for which we want to show the bulk-diff
TFVC.Changeset currentChangeset = VCS.GetChangeset(ChangesetId);

// extract all the item ids that correspond to the files that got changed in the change set
int[] itemIds = Array.ConvertAll<Change,int>(currentChangeset.Changes, delegate(Change c) { return c.Item.ItemId; });

// bulk retreive all the changed items
Item[] items = VCS.GetItems(itemIds, currentChangeset.ChangesetId);


// for each changed item, get the previous version of that item
Item[] olderItems = new Item[items.Length];
for (int i = 0; i < items.Length; ++i)
{
Change currentChange = currentChangeset.Changes[i];
Item currentItem = items[i];

// get the previous version only if it is an edit or merge
if (currentChange.ChangeType == ChangeType.Edit || currentChange.ChangeType == ChangeType.Merge)
{
// query history of the item to get the previous changeset and hence the previous version of the item
foreach (TFVC.Changeset previousChangeset in VCS.QueryHistory(currentItem.ServerItem, new ChangesetVersionSpec(currentItem.ChangesetId),
currentItem.DeletionId, RecursionType.None, null, null, new ChangesetVersionSpec(currentItem.ChangesetId - 1), 1, true, false, false))
{
if (previousChangeset.Changes != null && previousChangeset.Changes.Length > 0)
{
Change previousChange = previousChangeset.Changes[0];
olderItems[i] = VCS.GetItem(previousChange.Item.ItemId, previousChange.Item.ChangesetId);
break;
}
}
}
}

// TempFilesManager is an abstraction to manage temporary files and automatically delete them during the call to Dispose()
using (TempFilesManager tempFiles = new TempFilesManager())
{
// download the latest/older version of the files and also create the description file for windiff
string descriptionFile = tempFiles.GetNewTempFile();
using (StreamWriter sw = new StreamWriter(descriptionFile))
{
const string FormatForSingleFile = "\"{0}\"";
const string FormatForTwoFiles = "\"{0}\" \"{1}\"";

for(int i = 0; i < items.Length; ++i)
{
if (items[i].ItemType == ItemType.File)
{
string olderVersion = null;
if (olderItems[i] != null)
{
olderVersion = tempFiles.GetNewTempFile();
olderItems[i].DownloadFile(olderVersion);
}

string currentVersion = tempFiles.GetNewTempFile();
items[i].DownloadFile(currentVersion);

if (!String.IsNullOrEmpty(olderVersion))
{
sw.WriteLine(FormatForTwoFiles, olderVersion, currentVersion);
}
else
{
sw.WriteLine(FormatForSingleFile, currentVersion);
}
}
}
}

// launch windiff
LaunchWindiff(descriptionFile);
}



The fully working project is attached to this post. Hope you find it useful.


Mar 12, 2008

XML Cruncher

Aligning with my previous post, I want to talk about removing the unnecessary flab from XML here.  I work on a heavyweight web 2.0 app that does a lot of its HTML rendering by XSL transforms on the client.  For this purpose, I had to push down the XSL and XML as JavaScript strings to the client.  Now here is the problem: I had detailed comments in the XSL and XML which resulted in huge JavaScript strings emitted to the client.  Similar to the CSS minification, I wanted to minify the XML here.  Unlike CSS, luckily .Net has great support for XML that helped me achieve this fairly easily.

Below is the code that achieves this (self explanatory)...

public class XmlCrunch
{
private static XmlReaderSettings _crunchReaderSettings;

//this xml reader setting configures the XmlReader to ignore comments and whitespace
//thereby eliminating the flab altogether
private static XmlReaderSettings CrunchReaderSettings
{
get
{
if (_crunchReaderSettings == null)
{
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.IgnoreComments = true;
readerSettings.IgnoreWhitespace = true;
_crunchReaderSettings = readerSettings;
}
return _crunchReaderSettings;
}
}

public static string Crunch(string xml)
{
// create a new XmlReader from the given xml using the crunch settings
XmlReader xmlReader = XmlReader.Create(new StringReader(xml), CrunchReaderSettings);

// just load the xml doc via the XmlReader and retrieve the outer xml
// the XmlReader would take care of eliminating the flab
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
return xmlDoc.OuterXml;
}
}


Sample Input
<?xml version="1.0" encoding="utf-8" ?>
<
siteMap>
<!--
home page of the web-site -->
<
siteMapNode title="Home" description="Home" url="~/default.aspx">
<!--
products page of the web-site -->
<
siteMapNode title="Products" description="Our products"
url="~/Products.aspx">
<!--
this page lists the software products sold through the web-site -->
<
siteMapNode title="Software" description="Software choices"
url="~/Software.aspx" />
</
siteMapNode>
</
siteMapNode>
</
siteMap>

Corresponding Output
<?xml version="1.0" encoding="utf-8"?><siteMap><siteMapNode title="Home" description="Home" url="~/default.aspx"><siteMapNode title="Products" description="Our products" url="~/Products.aspx"><siteMapNode title="Software" description="Software choices" url="~/Software.aspx" /></siteMapNode></siteMapNode></siteMap>


This can be used to clean up the XML before you transfer over the network or store in DB, as it'll save you precious time and storage.  Hope you find it helpful.

kick it on DotNetKicks.com

Mar 11, 2008

CSS Minifier : A simplified and performant version

Today I happened to read about a CSS minimization solution here.  While it does the job well, there is a big problem... the solution ends up creating way too many temporary strings.  Given the fact that the input CSS string would be typically of the order of 10kb, avoiding the temporary strings would make a difference.  I have been using a CSS minimizer in my project over a year now and thought of sharing it with you.  I'm not sure whether it handles all possible scenarios but it has been working well for me...

// helper class to minify CSS
public static class CSSCruncher
{
//regex to identify flab in the CSS
private static Regex _cssCruncher = new Regex(@"(?<comment>\s*/[*](([^*]*)|([*]+[^*/]))*[*]/\s*)|(\s*(?<token>\{|\}|:|;|,)\s*)|(?<trim>(\A\s+|\z\s+))", RegexOptions.Compiled);

//utility to minify the CSS
public static string GetCrunchedCss(string cssContent)
{
return _cssCruncher.Replace(cssContent, delegate(Match m)
{
// default replacement string is String.Empty
string replacement = String.Empty;

// if the captured group is a token group when replacement string is the token value
if (m.Groups["token"].Success)
{
replacement = m.Groups["token"].Value;
}

return replacement;
});
}
}



The <comment> group in the reg-ex captures the multi-line comments with starting and trailing whitespaces.  The <token> group captures special tokens in CSS like {, }, :, ; and comma with starting and trailing white-spaces.  The <trim> group captures whitespaces at the start and end of the string.  The replacer-delegate replaces the <comment> and <trim> groups with empty string and the <token> group with only the token value without the white-spaces.



Hope you find it useful.



Update: Upon running a benchmark of this solution against the solution by Zack, to my surprise, I found that Zack's solution performs faster.  Seems that the way in which the reg-ex in my solution is organized is the culprit.  Modifying it a bit improves the speed but I'm going to leave it as it is and point you to Zack for your CSS minifier. :-)




kick it on DotNetKicks.com

Mar 6, 2008

Hidden Gem: Singleton Factory in C#

Today while browsing through new projects in codeplex I found this gem.  I haven't seen it before and so felt that I should blog about it.

Almost all of us would be aware of the singleton pattern and where it comes handy.  While I love it and use it wherever applicable, there is also a bit of uneasiness when I had to implement the double locking syntax for each singleton class... I wondered whether something can be done to simplify this but I got nowhere.

Now, I've found a solution for this problem in this project.  I'm giving below a simplified version of the Singleton factory found in the project, with example...

namespace Test
{
// this is the class for which I want to maintain a single instance
public class MyClass
{
private MyClass()
{
// private constructor ensures that callers cannot instantiate an object using new()
}
}

// Singleton factory implementation
public static class Singleton<T> where T : class
{
// static constructor, runtime ensures thread safety
static Singleton()
{
// create the single instance of the type T using reflection
Instance = (T)Activator.CreateInstance(typeof(T), true);
}

// serve the single instance to callers
public static T Instance { private set; get; }
}

class Program
{
public static void Main()
{
// test
Console.WriteLine(Object.ReferenceEquals(Singleton<MyClass>.Instance, Singleton<MyClass>.Instance));
}
}
}



The code is self explanatory.  Now, whenever I want a singleton instance of a class, all I have to do is Singleton<ClassTypeName>.Instance and the magic happens.  Isn't this a beauty?  Full credit to Tan Chun Mun.




kick it on DotNetKicks.com

My previous blog

Hi again, I would like to point to you my previous blog. It wasn't a very active one but contains a couple of interesting posts that might be worth your visit. Catch you again soon...

Mar 5, 2008

Hello World!

Hey, thanks for visiting my blog. I'm a passionate software developer with a few years of experience in both client and server side programming. I'm starting this blog primarily to help myself to contemplate and consolidate on my thoughts and view points on anything related to computers. In the course of it, I hope to be of help to knowledge seekers like you.

I plan to keep this blog active and so catch you here again soon...