Application Compatibility with TFS 2005 and TFS 2008

August 5, 2009

Although I usually blog about cross-platform aspects of Team Foundation Server, sometimes you run into interesting issues when writing third-party apps strictly against Microsoft's TFS SDK. I thought I'd mention one of the problems we ran into writing Remote Accelerator1 - working with both the TFS 2005 and TFS 2008 client assemblies.

When writing code against the TFS SDK, you need to specify the version of the SDK to bind to: for TFS 2005, this you bind to version 8.0; for TFS 2008, you bind to version 9.0. In theory, you're expected to compile one version of your tool to talk to TFS 2005 and another to talk to TFS 2008. But if you happen to be calling simple methods2 that have identical signatures between the versions, you can use a little loader trickery to bind against both.

When compiling your application, link against the version 9.0 SDK. When those aren't found -- when the target platform only has the TFS 2005 client installed -- .NET will fire a library resolve event, giving you a second chance to load the DLLs. Then you can simply load the 2005 libraries and keep running your application.

When your application starts, hook up an AssemblyResolve event handler:

AppDomain.CurrentDomain.AssemblyResolve +=
    new ResolveEventHandler(ResolveAssembly);

Your ResolveAssembly method should look like this:

public static Assembly ResolveAssembly(object sender,
    ResolveEventArgs args)
{
    String[] arguments = args.Name.Split(new string[] { ", ", "," }, 
        StringSplitOptions.RemoveEmptyEntries);

    if (arguments == null || arguments.Length == 0)
    {
        return null;
    }

    String assemblyName = arguments[0];
    String versionRequested = null;
    String cultureRequested = "neutral";
    String pkTokenRequested = "b03f5f7f11d50a3a";

    for (int i = 1; i < arguments.Length; i++)
    {
        if (arguments[i].StartsWith("Version=", 
            StringComparison.CurrentCultureIgnoreCase))
        {
            versionRequested = arguments[i].Substring(8);
        }
        else if (arguments[i].StartsWith("Culture=", 
            StringComparison.CurrentCultureIgnoreCase))
        {
            cultureRequested = arguments[i].Substring(8);
        }
        else if (arguments[i].StartsWith("PublicKeyToken=", 
            StringComparison.CurrentCultureIgnoreCase))
        {
            pkTokenRequested = arguments[i].Substring(15);
        }
    }

    if(
        assemblyName.StartsWith("Microsoft.TeamFoundation.", 
            StringComparison.CurrentCultureIgnoreCase) &&
        versionRequested.Equals("9.0.0.0")
    )
    {
        return Assembly.Load(assemblyName +
            ", Version=8.0.0.0, Culture=" + cultureRequested + 
            ", PublicKeyToken=" + pkTokenRequested);
    }

    return null;
}

And with a few lines of code, you've got an application that works with either TFS 2005 or TFS 2008.

Footnotes

  1. What's Remote Accelerator you ask? It's a single-user version control proxy for Team Foundation Server. It's perfect for branch offices and telecommuters who want faster access to TFS.

  2. It's important to remind you that this trickery will only work with methods that have identical signatures between the 2005 and 2008 assemblies. Otherwise, you'll get some nasty class loading exceptions.