If you have started playing with one of the TFS 2010 betas then you have probably noticed the new snazzy connect dialog:

ss4

For those of you new to TFS 2010, the pane on the left above shows the list of “Team Project Collections” that exist on the TFS Instance that you have connected to.  If you want a deeper explanation of what a team project collection is then check out Brian Harry’s post on the subject.  Recently, a few people have asked what the proper way is to retrieve that list of team project collections using the TFS 2010 client APIs so I figured it was worth a blog post since this seems to be a fairly popular question.

Disclaimer: The code presented in this blog post assumes you are using the RTM version of TFS.  Some of these APIs were refactored between TFS 2010 Beta 2 and TFS 2010 RTM.  Please see the section at the end of the post to see how the new API names map to those in the Beta 1 and Beta 2 versions of TFS 2010.

If you are new to the TFS 2010 OM you may want to start with this post which introduces the TfsConnection, TfsConfigurationServer and TfsTeamProjectCollection objects.

In order to use the code from the examples in this post you will need the following include statements:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;

First, let’s talk about how not to get this list of team project collections.

So, if you are at home (or at work) digging through the new TFS 2010 APIs trying to find out where to get the list of team project collections you may come across the new TeamProjectCollectionService, seen that it has a GetCollections method and think “Great! I have found what I am looking for”.  Unfortunately, this is not the case.  The TeamProjectCollectionService can be thought of more as a management related service than a user related service.  It offers methods to create, delete and edit team project collections.  While it also does offer the previously mentioned method to get the list of team project collections, the data returned by that function call has too much information for the normal user to see.  For example, it contains the connection string to the database that stores the data for each team project collection and exposing this information to a normal user is not acceptable.

While we are on the topic though, if you are writing a management tool, here is how you would use the TeamProjectCollectionService to get a list of team project collections along with some of their configuration details:

Uri configurationServerUri = new Uri("http://your-server:8080/tfs");
TfsConfigurationServer configurationServer = 
        TfsConfigurationServerFactory.GetConfigurationServer(configurationServerUri);

ITeamProjectCollectionService tpcService = configurationServer.GetService<ITeamProjectCollectionService>();

foreach (TeamProjectCollection tpc in tpcService.GetCollections())
{
    // Do your work for each TeamProjectCollection object here.
}

The GetCollections() method called here is guarded by the “Edit server-level information” permission which by default is only granted to server administrators.  That means, if a regular user were to call this method then that user would see the message:

“Access Denied: DOMAIN\NormalUser needs the following permission(s) to perform this action: Edit instance-level information”

So, what is the proper way to get the list of team project collections for a normal user then?

The answer to that question relies on a new technology introduced in TFS 2010 called the catalog service.  The goal of the new catalog service is to provide a publishing and discovery service for any resources used in a TFS deployment.  For example, team project collections, team projects, SharePoint resources and Reporting Service resources among many other things are represented in the catalog.  In order to represent such diverse objects and the relationships between them in a single store the catalog has many different concepts, some of which are fairly complicated.  I am planning an in-depth look at the catalog for a future post so for the purpose of this discussion we will try to stay on the surface.

Each entity that is represented in the catalog exists as a CatalogResource.  Each CatalogResource has a type (team project collection, team project, etc.) and some properties that better define the object that it represents.  The catalog stores how each CatalogResource relates to other CatalogResources in a hierarchical fashion.  Currently the catalog has two well-defined trees for representing relationships: the Infrastructure tree and the Organizational tree.  The Infrastructure tree is used to represent the hardware in your TFS deployment and can answer queries like “show me all of the machines that I have TFS Web Applications installed on”.  The Organizational tree is used to represent the relationships between the TFS resources such as the team project collections and team projects in your deployment. 

In order to be represented in one of the catalog trees mentioned above a CatalogResource must be associated with a CatalogNode.  A CatalogNode represents a place in the catalog tree and thus conveys how each resource relates to each other.  For example, there is a CatalogResource for the TFS configuration server associated with a CatalogNode under the root of the Organizational tree.  As children of the configuration server node, there is a CatalogNode associated with each of the CatalogResources that represent team project collections.  Continuing, there is a CatalogNode associated with each of the CatalogResources that represent a team project under the given team project collection node that that team project belongs to.  Hopefully with that background information, the following example on retrieving the list of team project collections from the catalog will make sense:

Uri configurationServerUri = new Uri("http://your-server:8080/tfs");
TfsConfigurationServer configurationServer =
        TfsConfigurationServerFactory.GetConfigurationServer(configurationServerUri);

CatalogNode configurationServerNode = configurationServer.CatalogNode;

// Query the children of the configuration server node for all of the team project collection nodes
ReadOnlyCollection<CatalogNode> tpcNodes = configurationServerNode.QueryChildren(
        new Guid[] { CatalogResourceTypes.ProjectCollection }, 
        false,                                                 
        CatalogQueryOptions.None);

foreach (CatalogNode tpcNode in tpcNodes)
{
    // Use tpcNode.Resource to get the details for each team project collection.
}

Let’s go into this example with a little more detail now.  The CatalogNode for any TfsConnection object is stored as a property called “CatalogNode”.  From this we can call the QueryChildren method to find out information about what children it has.  In the first argument to this call we specify that we only want to see children that are of type “ProjectCollection”.  In the second argument, we specify the value “false” for recurse since we know that a team project collection node will only exist as a child of our node.  If it were possible that a team project collection node could exist as a grandchild (or deeper) of our node then we would need to pass true here.  Finally, we pass CatalogQueryOptions.None as the last parameter because we don’t need any details about the parent nodes or dependencies filled out.  Don’t worry about dependencies for now, those will be covered in a later post.  So, with that query, you now have a list of all of the catalog nodes that represent team project collections that you are allowed to see.

Great, so what exactly can I learn or do with these CatalogResource objects that represent the team project collections?

Good question.  For starters, accessing the following properties will give you relevant information about the team project collection:

// Get the team project collection name.
String displayName = tpcNode.Resource.DisplayName;

// Get the team project collection description.
String description = tpcNode.Resource.Description;

// Get other dynamic properties that describe this collection
foreach (KeyValuePair<String, String> property in tpcNode.Resource.Properties)
{
    String propertyKey = property.Key;
    String propertyValue = property.Value;
}

Also, using the information in the ServiceReferences property you can get the url that is used to actually connect to the team project collection:

// Get the ServiceDefinition for the team project collection from the resource.
ServiceDefinition tpcServiceDefinition = tpcNode.Resource.ServiceReferences["Location"];

ILocationService configLocationService = configurationServer.GetService<ILocationService>();
Uri tpcUri = new Uri(configLocationService.LocationForCurrentConnection(tpcServiceDefinition));

// Actually connect to the team project collection
TfsTeamProjectCollection tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(tpcUri);

// Perform operations against the team project collection object here

Hopefully that should be enough of an example to get you started with working with the catalog to determine the list of team project collections.

Cool, anything else I should know about using the catalog service?

I will dive deeper into the catalog service at a later point but here is a good tip: the catalog service is not backward compatible and will not work against a TFS 2005 or TFS 2008 server.  In order to determine if you are talking to a TFS 2010 server from your client application, check out this post.  If there is anything specific that you would like to know about the catalog service, feel free to mention it in the comments or shoot me an email and I will make sure to address it.

If you are using TFS 2010 Beta 1 or Beta 2 then everything you just read is still true but some of the APIs have different names.  Here are the mappings that you will need to make this work in one of the betas:

TfsTeamProjectCollection is equivalent to TeamFoundationServer.  TfsConfigurationServer is equivalent to TeamFoundationApplicationInstance.  TfsConnection is equivalent to TeamFoundationServerBase.  For more information on this see Grant Holliday's blog post.