August 2012
Volume 27 Number 08
ALM Rangers - Using the Team Foundation Server Client Object Model
By Willy-Peter Schaub, Brian Blackman | August 2012
In this article we’ll introduce the Visual Studio Team Foundation Server (TFS) client object model and create the foundation for a new series of Visual Studio ALM Rangers articles, focused on practical guidance and common coding scenarios with TFS.
To recap, the ALM Rangers are a group of experts who promote collaboration among the Visual Studio product group, Microsoft Services and the Microsoft Most Valuable Professional (MVP) community by addressing missing functionality, removing adoption blockers, and publishing best practices and guidance based on real-world experiences.
Visual Studio TFS is all about application lifecycle management (ALM). It’s about enabling teams to collaborate, plan, track, design, develop, build and store their solutions effectively and securely. It’s about productivity and bringing ALM within the reach of professional software developers, whether they’re from a small, midsize or large organization, and whether they’re using a formal, agile or custom process, or working as virtual or tightly coupled teams. In this article we’ll explore the basic architecture concepts you need to understand to work with TFS and, more important, to extend its features.
Why Extensibility Is Important
TFS includes hooks and mechanisms for expanding and enhancing the product with new features. These new features enable you to customize the TFS ALM solution to suit your environment and to extend TFS with custom features and behaviors not built into the standard product.
TFS is a multitier solution, which can be programmatically accessed by using the appropriate object models. As shown in Figure 1, the object model includes a client and a server object model, which can be used to extend the client or the application tier, respectively. In this article we’ll focus on the client object model.
Figure 1 Visual Studio Team Foundation Server Architecture
Although it’s technically possible to access the Web services or the data tier directly, it’s highly recommended to access the TFS features and data through the relevant object models, as shown in Figure 1.
Let’s briefly take a step back and explore the idea of the TFS object model and its namespaces, which we’ll be referring to in both this article and its accompanying sample code download.
The object model is the set of classes logically decomposed into namespaces and physically distributed over a set of assemblies. The TFS client object model is a general term that refers to all public classes available in Microsoft.TeamFoundation.*.dll assemblies.
Namespaces organize TFS classes and features into similar items, which could be compared with using a filing cabinet. Each drawer represents an object model. Each folder within each drawer represents a namespace, and the content of each folder represents the classes. Just like a filing cabinet, the namespace allows you to categorize and subsequently find relevant classes easier and more quickly.
We’ll focus on getting started and creating a client using the TFS object model to connect to the TFS Web services. The TFS services support specific features such as version control, work item tracking and events.
Setting up a Dev Environment
We’ll use the C# language and the Visual Studio IDE. You also can work with the object model with other languages and environments, such as Windows PowerShell.
Use of the TFS client object model currently requires a client SKU—such as Team Explorer—installation on the development machine. In the future, the TFS client object model will be available as a standalone client installation.
Team Explorer is included with all editions of Visual Studio except Express. When you have Visual Studio and TFS installed, start Visual Studio, connect to a TFS server and confirm that you have create, read, update and delete (CRUD) permissions for work items and source control.
For this article and most development work, we used a public virtual machine (VM) provided by Brian Keller from the ALM Developer Evangelism Team at Microsoft (available at bit.ly/VS2010RTMVHD for the Visual Studio 2010 version and aka.ms/VS11ALMVM for the Visual Studio 2012 version). If you download the same VM, the code samples should just work. If you use the code against your own server, it might require some changes.
To work with the sample code in this article, you’ll need references to the TFS .NET Framework assemblies listed later. You get these assemblies when you install Team Explorer.
Best Practices and Guidelines
When you develop client applications using the TFS object model, use the following guidelines.
Use the TFS Client API even though the API calls the Web services. Accessing the Web service interfaces directly is not officially supported. The TFS client object model helps with authentication, compatibility with multiple server versions, impersonation, two-factor authentication using client credentials and a handful of other things in addition to providing clean, high-level abstractions over the Web service interfaces. For some clients (for example, a Ruby on Rails app running on *nix), the Web service layer is the only option. If you fall within this category, it’s worth checking out the Java SDK (see bit.ly/irHxgm).
Where possible, perform recursive operations instead of enumerating each individual item. For example, delete the folder instead of deleting each individual file. This guideline brings up something that we learned from Don Box when we attended his session at TechEd in New Orleans back when he had very long hair and didn’t work for Microsoft: “Network traffic is evil.” We attended his session on remote procedure calls. Back then, C code was used, but what has not changed is the importance of his statement. Network traffic is evil! Some developers haven’t yet learned this, or they’re learning about it the hard way with customers and consultants complaining about poor performance over the network.
To adhere to the “network traffic is evil” mantra, try to batch non-recursive operations and only request the download URLs you need when you need them. Some of the accompanying sample code will ignore this guideline in attempts to demonstrate features of the API.
When passing a path to an API, use the server path instead of the local path. Do this because the latter needs to be translated into the server path. This translation requires that the workspace of the caller be identified, the mapping be loaded, the proper mapping be determined and so on, before finally getting the server path.
Use an optimistic pattern with your operations. Just like with databases, assume that you’ll be successful at a write operation and catch the exception. For example, in operations that use the version control service, don’t spend valuable time checking the item before operating on it, because the file could be deleted, and therefore your need to handle exceptions would be, too. So assume the best and prepare for the worst.
Another useful tool for figuring out where there’s evil network traffic is Microsoft Network Monitor. Don’t code against the network or debug the network without it. No packet can hide from this tool, and the truth will be revealed. You can download Microsoft Network Monitor from Microsoft downloads (bit.ly/mMmieH).
When you’re in a bind and don’t know why, or how or for how long things are working, turn on tracing for Web methods. Tracing will show you what Web methods are called by the API. It provides details such as what was called, the duration of the call and the SOAP response. Tracing is turned on through modification of your application .config file in the system.diagnostics section, as shown in Figure 2. Sample output that you find in your log file is in Figure 3. However, if you want to look at the SOAP payload, you’ll need to use a tool such as Microsoft Network Monitor.
Figure 2 Enabling Tracing in Your Configuration File
<?xml version ="1.0"?>
<configuration>
<system.diagnostics>
<switches>
<add name="TeamFoundationSoapProxy" value="4" />
<add name="VersionControl" value="4" />
</switches>
<trace autoflush="true" indentsize="3">
<listeners>
<add name="rangerListener"
type="Microsoft.TeamFoundation.TeamFoundationTextWriterTraceListener,
Microsoft.TeamFoundation.Common, Version=10.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
initializeData="c:\almrangertracing.log" />
<add name="performanceListener"
type="Microsoft.TeamFoundation.Client.PerfTraceListener,
Microsoft.TeamFoundation.Client, Version=10.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
Figure 3 Web Method Tracing with Performance Data
03/17/2012 12:10:14 (pid 2936, tid 4820, 11636 ms) Web method response: [https://servername/tfs/myCollection/VersionControl/v1.0/repository.asmx] QueryWorkspace[VersionControl] 3 ms
03/17/2012 12:10:14 (pid 2936, tid 4820, 11637 ms) CreateWebRequest() -- Uri: https://servername:8080/tfs/myCollection/VersionControl/v1.0/repository.asmx
03/17/2012 12:10:14 (pid 2936, tid 4820, 11637 ms) request.AutomaticDecompression: GZip
03/17/2012 12:10:14 (pid 2936, tid 4820, 11637 ms) Web method running:
https:// servername:8080/tfs/myCollection/VersionControl/v1.0/repository.asmx] UpdateWorkspace[VersionControl]
03/17/2012 12:10:14 (pid 2936, tid 4820, 11658 ms) HTTP headers:
Content-Length: 896
Cache-Control: private, max-age=0
Content-Type: application/soap+xml; charset=utf-8
Date: Sat, 17 Mar 2012 16:10:12 GMT
Server: Microsoft-IIS/7.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Understanding the Assemblies and Namespaces
Many assemblies and namespaces are exposed by the TFS object model. Not all assemblies will be covered in this series of articles, just the most important and most frequently used namespaces. In addition to common TFS namespaces, we’ll list .NET core namespaces where the use of the TFS object model has dependencies.
A common pattern will be connecting to Team Foundation Configuration Server or a collection and listing the registered servers or listing the projects. These common client tasks require the following references:
// Needed for TfsConfigurationServer
using Microsoft.TeamFoundation.Client;
// Needed for Catalog Resources such as types
using Microsoft.TeamFoundation.Framework.Common;
// Needed for other Catalog Resouces such as nodes
using Microsoft.TeamFoundation.Framework.Client;
// Needed for use of the Version Control service
using Microsoft.TeamFoundation.VersionControl.Client;
Use of each of the available services will also require the same references, such as Microsoft.TeamFoundation.VersionControl.Client for the Version Control Service. Working with work items could require references to:
- Microsoft.TeamFoundation.WorkItemTracking.Client
- Microsoft.TeamFoundation.WorkItemTracking.Common
- Microsoft.TeamFoundation.WorkItemTracking.Proxy
Figure 4 shows the main assemblies included with the TFS SDK, which are found in Program Files\Microsoft Visual Studio 10.0\Common7\IDE under ReferenceAssemblies\v2.0 and Private-Assemblies in terms of TFS 2010, or in Program Files (X86)\ ... on a 64-bit OS.
Figure 4 TFS SDK Assemblies
Namespace o = Microsoft.TeamFoundation |
Description |
o.client | The namespace and associated assemblies provide the interfaces to connect to TFS and to access data relating to team projects and team project collections. |
o.framework.client | The namespace and associated assemblies expose APIs for viewing and manage the contents of the TFS registry, job schedules, generic property store, event subscriptions, security namespaces, services, team project collections, catalog and Lab Management objects. |
o.framework.common | The namespace and associated assemblies define common objects such as permissions, exceptions and constants. |
(Note: The default code sample we provided is built and tested with the released TFS 2010 object model, retrieving the referenced assemblies from …\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0. To use the new TFS 11 beta object model, simply remove the Microsoft.TeamFoundation.* assembly references and re-add them from …Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ReferenceAssemblies\v2.0 and rebuild the sample applications. You can use either object model to connect to TFS 2010 and 2012 servers using the default samples.)
If you’re looking for the complete sample solution and more code samples from the Visual Studio ALM Rangers, or if you’d like to contribute samples yourself, please visit us at bit.ly/M0oyQt.
Our First Programming Adventure
Remember, you’ll need the following references to connect to a TFS server:
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Client;
You could also have a dependency on the System.Net namespace for passing network credentials to some overloaded methods, as shown in the following code:
NetworkCredential myNetCredentials =
new NetworkCredential("Administrator", "P2ssw0rd");
ICredentials myCredentials = (ICredentials)myNetCredentials;
NetworkCredentials are useful if you’re using the public TFS VM. After you have the references, you can connect to a TFS server via several different approaches. You can connect to the configuration server, where you can get a list of collections passing in your server URI and credentials:
Uri tfsUri = @"https://servername:8080/tfs";
TfsConfigurationServer configurationServer =
new TfsConfigurationServer(tfsUri, myCredentials);
A better approach is to prompt the user for credentials by passing in UICredentialsProvider, as shown here:
TfsConfigurationServer configurationServer =
new TfsConfigurationServer(tfsUri, new UICredentialsProvider());
Second Approach—Connecting to a New Configuration Server
Here’s an alternate approach when you’re running the process under the proper credentials and want to avoid having to connect to a new configuration server:
TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
// Once you have your connection then you can
// get a list of collections on the server
// Get the catalog of team project collections
ReadOnlyCollection<CatalogNode> collectionNodes;
collectionNodes = configurationServer.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.ProjectCollection },
false, CatalogQueryOptions.None);
foreach (CatalogNode collectionNode in collectionNodes)
{
// Add each collection to the combo box
cbTPC.Items.Add(collectionNode.Resource.DisplayName);
}
The previous example requires that you type a URI in an edit control on the dialog. In the next example, we’ll read the list of registered servers on the client. Registered servers are the TFS servers that were added to Visual Studio through its Connect to Team Foundation Server dialog box, as shown in Figure 5.
Figure 5 Registered Team Foundation Server Dialog Box
To read the registered servers on the client, you’ll need a reference to Microsoft.TeamFoundation.Client. In the following code, the registered servers on the client are read and added to a combo box (shown in Figure 6):
List<RegisteredProjectCollection> registeredTPCs;
// Get all registered collections on this machine
registeredTPCs = new
List<RegisteredProjectCollection>((
RegisteredTfsConnections.GetProjectCollections()));
foreach (var projectCollection in registeredTPCs)
{
// Add each registered team project collection to the combo box
cbRegColl.Items.Add(projectCollection.Uri);
}
Figure 6 Reading Registered Servers
Third Approach—Connecting to a Known Collection URI
In addition to connecting to a server, if you know the URI to the collection, you can connect to it directly. Figure 7 shows this third approach—connecting to a known collection URI.
Figure 7 Connecting to a Known Collection URI
NetworkCredential myNetCredentials =
new NetworkCredential("Administrator", "P2ssw0rd");
ICredentials myCredentials = (ICredentials)myNetCredentials;
// Connect to the tpc hosting the version-control repository
TfsTeamProjectCollection tpc =
new TfsTeamProjectCollection(new Uri(cb.Text),
myNetCredentials);
// Get the version control service
vcServer = tpc.GetService<VersionControlServer>();
var teamProjects =
new List<TeamProject>(vcServer.GetAllTeamProjects(false));
// List the team projects in the collection and add to list box
foreach (TeamProject projectNode in teamProjects)
{
// Add list or team projects to list box
lbProjects.Items.Add(projectNode.Name);
}
The next examples demonstrate using some of the version control service features. When you have the references to the server and a reference to the version control service (see Figure 7), you can use some of the APIs, such as getting a list of files on the server or pending changes. The following code uses the version control server reference to ask for all XAML files on the server and adds them to a list box:
// List all of the XAML files on the server
ItemSet items = vcServer.GetItems("$/*.xaml", RecursionType.Full);
foreach (Item item in items.Items)
{
// Add each file to the list box
lbAll.Items.Add(item.ToString());
}
The first parameter to the GetItems method is the path to the folder. In the sample code, the path is the root, and all XAML files will be returned in all folders. In Figure 8, you see that file details are shown when you select one of these files.
Figure 8 Get All XAML Files and Show File Details
If you want to see a list of files with pending changes and add those to a list box, you would use the code in Figure 9. In this code, the API QueryPendingSets is overloaded, and this example uses the method overload that allows you to query based on ItemSpecs, which is an array that represents what to query. In this case, we’re asking for pending changes for only one folder on the server. We’ll also filter on pending changes for a particular workspace and user. The last parameter instructs the API to provide us the information so that we can download the file. If we have no plans to use that information, then consider the mantra “network traffic is evil” and pass false.
Figure 9 Get PendingChanges
ItemSpec[] itemSpecs = new ItemSpec[1];
// You can filter the path to a project and folder
// using $/ProjectName/Folder...
ItemSpecs[0] =
new ItemSpec(@"$/Tailspin Toys/Development/Iteration 2",
RecursionType.Full);
// Your options for filtering are the Workspace Name,
// User Name and the previous path in the ItemSpec
String qryWSName = @"WIN-GS9GMUJITS8";
String queryUserName = @"administrator";
bool includeDownloadInfo = true;
PendingSet[] pendingSet = vcServer.QueryPendingSets(
itemSpecs, qryWSName, qryUserName, includeDownloadInfo);
foreach (PendingSet ps in pendingSet)
{
// Add pending changes to list box
lbPendingChanges.Items.Add(ps.ToString());
}
The version control service offers many more features that we haven’t covered in this article. We tried to whet your appetite and get you started on connecting to the server and using the version control service.
In future articles we’ll continue to investigate the TFS client object model and introduce different programming scenarios such as working with work items, version control, builds and deeper integration through the Visual Studio TFS server object model.
Brian Blackman is a principal consultant with the Microsoft Services Partner ISV team, focusing on affecting ISV partners’ success in engineering and the marketplace. He has an MBA and is a CSM, CSP, MCSD (C++), MCTS and Visual Studio ALM Core Ranger. He spends his time writing code, creating and delivering workshops, and consulting in various concentrations and all things ALM.
Willy-Peter Schaub is a senior program manager with the Visual Studio ALM Rangers at the Microsoft Canada Development Center. Since the mid-’80s, he’s been striving for simplicity and maintainability in software engineering. Read his blog at blogs.msdn.com/b/willy-peter_schaub and follow him on Twitter at twitter.com/wpschaub.
Thanks to the following technical experts for reviewing this article: Jeff Bramwell, Bill Essary, Mike Fourie, Bijan Javidi, Jim Lamb and Patricia Wagner