in

Foo Theory

Partners in Community - serving up some ice cold Kool-Aid!
Welcome to footheory.com.  The bloggers and contributing members on this site are consultants, project/program managers and software architects working across the US.  Our community will focus on Microsoft technologies, .NET architecture, software patterns & practices and just plain stream of consciousness.

Bennie's Weblog

Team Foundation Server Extensibility - Navigating the Object Model

Introduction

In my previous post I introduced Team Foundation Server's customization and extensibility features. I mentioned how the TFS core functionality is exposed by means of the Team Foundation Core Services (TFCS), which are essentially a set of five different Web Services. The functionality of these web services is encapsulated by the Team Foundation Server Object Model (TFSOM), which is what Microsoft recommends you use to implement a Team Foundation extension.

The Team Foundation Object Model is implemented in a series of assemblies, some of which we will take a detailed look at in this post. We will explore some of the core objects in this object model, and we will build a small wrapper class library, called FooTheory.TFS.TFSConnectivity. We will use this class library as our foundation to create the following custom extensibility clients:

  1. A PowerShell PSDrive provider called FooTheory.TFS.TFSProvider. This provider will allows us to treat the TFS repository as a logical drive, so we will be able to use our favorite commands such as  dir, cd, copy, new (or their "offical"cmdlet names: Get-ChildItems, Set-Location, Copy-Item, New-Item)  to manipulate TFS artifacts such as Work Items, Stored Queries, Team Projects, Reports etc.
  2. An event listener called FooTheory.TFS.EventListener. This is a WCF-based service, which can subscribe to TFS events, and receive these events through HTTP. The other clients, such as the PSDrive provider and the WPF client will be able to host this service, enabling them to be informed of any changes to their TFS artifacts of interest.
  3. A rich WPF-based client called FooTheory.TFS.TFSExplorer. This client will take full advantage of the graphical expressiveness of Windows Presentation foundation to allow the user to navigate the complete TFS object model, including Work Items, Reports, Source Code trees etc.

An overview of the assemblies is shown in the figure below:

MainLayers

To start out, we will take a look at the TFSOM client assemblies which need to reference in our class library in order to implement our functionality.

TFSOM Client Assemblies

To leverage the functionality of the TFSOM, we need references to the following assemblies:

DllOverview

 

If you have Team Explorer installed on your client, these assemblies should be located in the C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies location, together with all of the TFSOM assemblies. (If you have Orcas installed, you need to substitute "Visual Studio 9" for "Visual Studio 8" in the above path). Also, if you have Team Foundation Server 2008 installed, these assemblies will be the client assemblies for the Team TFS 2008, but these assemblies are 100% backwards compatible with TFS 2005 object model, so you can use the same assemblies to access both TFS versions.

In the next section we will take a look at the high-level design of our FooTheory.TFs.TFSConnnectivity custom class library.

The FooTheory.TFS.TFSConnectivity assembly

The main goal behind creating this assembly is to provide a simple API that provides access to some of the core functionality that we need to implement, for example:

  • Connecting and authenticating to a Team Foundation Server Instance.
  • Retrieving a list of all Team Projects .
  • Retrieve the Process Template of a given Team Project instance.
  • Retrieving the Microsoft Project mapping properties for a Team Project instance.
  • Subscribing and unsubscribing to a TFS Event Type.
  • Retrieving all stored Work Item Queries for a given project.
  • Executing a stored query, retrieving the resulting Work Items in the process.

The main class in our assembly is the FooTheory.TFS.TFSConnectivity.TFSServer class. An overview class diagram of this class and its main relationships is shown in the figure below:

Overview

Note: The FooTheory.TFS.TFSConnectivity.dll assembly uses the Patterns & Practices Enterprise Library 3.1. Specifically, it leverages the Logging and Exception Handling blocks to enhance the robustness of the assembly. We'll have a few words to say about our usage of the Enterprise library at the end of this post.

Next, we will take a more detailed look at the class that is at the center of the Team Foundation Server Object Model, the Microsoft.TeamFoundation.Client.TeamFoundationServer class.

After we have a better understanding of this class, we will use our understanding of the TeamFoundationServer class to design and implement our TFSConnectivity assembly.

The TeamFoundationServer Object

The TeamFoundationServer object is the core object of the TFSOM. It contains the basic attributes of the Team Foundation Server that it is connected to. In addition, It provides access to the Team Foundation Core Services, as well as any other services that have been registered with Team Foundation Server, such as:

  • Version Control
  • Work Item Tracking
  • Project Templates
  • Event subscriptions

The TeamFoundationServer object is defined in the Microsoft.TeamFoundation.Client namespace, so you will need to have a reference to the Microsoft.TeamFoundation.Client.dll if your want to use it in your code.

You can get a reference to a TeamFoundationServer instance in one of two ways:

  • You can use the TeamFoundationFactory.GetServer() method. This is the most commonly used strategy to create a TeamFoundationServer instance. The GetServer() method of the TeamFoundationServerFactory class supports the following overloads:
    • You can only pass in the name of the server:
public static TeamFoundationServer GetServer(string name);
You can pass in the name of the TFS server and an object that implements the ICredentialsProvider  interface:
public static TeamFoundationServer GetServer(
            string name, ICredentialsProvider fallbackCredentialsProvider)
  • You can also choose to call the TeamFoundationServer constructor directly. The different overloads are shown below:
public TeamFoundationServer(String name);
public TeamFoundationServer(
    string name,
    ICredentials credentials);
public TeamFoundationServer(
    string name,
    ICredentialsProvider credentialsProvider);
public TeamFoundationServer(
    string name,
    ICredentials credentials,
    ICredentialsProvider credentialsProvider);
The main difference between these two approaches is the fact that the Factory method caches the instance by the Uri of the server. The first time you call GetServer() with a specific server Uri, the factory will create a new TeamFoundationServer instance, add  it to cache (indexed by the server Uri) and return it to the caller. The next time you call GetServer() with the same server Uri (regardless of the ICredentialsProvider that is passed in), you will get exactly the same Uri instance. 

The TeamFoundationServerFactory class was initially created to support the behavior of Team Explorer. In Team Explorer, you will notice that you will get the authentication dialog the first time your bring up the Explorer, but afterwards you are no longer prompted although behind the scenes, the team explorer code might have to call the Factory's GetServer() method multiple times. But, since the code is calling the GetServer() method with the same server Uri each time, you will NOT be prompted again.

When you call the TeamFoundationServer constructor directly, you are obviously creating a new instance each time.

As far as passing in credentials, you have basically two choices:

  • You can pass in a reference to an object that implements the ICredentialsProvider interface. While you can pass in any instance that implements this interface, in a UI-based scenario you probably would want to use Microsoft.TeamFoundation.Client.UICredentialsProvider class. When you create a new instance of this class, it will automatically pop up the authentication dialog. So the following code:
                // Create an instance of the UICredentialsProvider.
                // Note that creating this instance will automatically
                // popup the TFS authentication dialog
                ICredentialsProvider provider = new UICredentialsProvider();

Will popup the following dialog:

TFSLogindialog

  • You can pass in any NetworkCredential instance, for example:
                // Create our Network Credentials
                NetworkCredential credentials = new NetworkCredential(TFSUserName, TFSPassword);

Implementation in the FooTheory.TFS.TFSConnectivity Library

Connecting to Team Foundation Server

The FooTheory.TFS.TFSConnectivity.TFSServer class contains an embedded instance of a TeamFoundationServer instance in the m_tfsServer field, and exposed through the TeamFoundationServer property:

        // This is our Team Foundation Server instance
        private TeamFoundationServer m_tfsServer;

        /// <summary>
        /// This read-only property returns our current
        /// TeamFoundationServer instance
        /// </summary>
        public TeamFoundationServer TeamFoundatonServer
        {
            get { return m_tfsServer; }
        }

The TFSServer class offers three overloads for the Connect() method, as shown below:

        public bool Connect(string serverName)
        public bool Connect(string serverName, NetworkCredential credentials)
        public bool Connect(string serverName, ICredentialsProvider credentialsProvider)

The first overload does not take any type of credentials, and will use the UICredentialsProvider to pop up the connect dialog shown in the previous section. The other overloads either take a NetworkCredential or an object that implements ICredentialsProvider. After successfully connecting and authenticating, a reference to the TeamFoundationServer object instance is stored in the m_tfsServer field.

The full implementations of the different Connect methods is shown below:

   1: /// <summary>
   2: /// This overload of the Connect Method will prompt
   3: /// the users for a set of credentials, by means of the
   4: /// UICredentialsProvider. It then calls our "generic"
   5: /// connect method, which takes an ICredentialsProvider
   6: /// as its second argument
   7: /// </summary>
   8: /// <param name="serverName"></param>
   9: /// <returns></returns>
  10: public bool Connect(string serverName)
  11: {
  12:     using (new Tracer(LoggingCategoryConstant.TFS_CONNECTIVITY_CONNECT))
  13:     {
  14:         //  Create an instance of our Creditials by means of the
  15:         //  UICredentialsProvider. Since we are using the 
  16:         //  UICredentialsProvider here, this  method will popup 
  17:         //  an authentication dialog for us and create a set 
  18:         //  of Credentials for us, which are passed to our other 
  19:         //  Connect overload that takes a generic 
  20:         //  ICredentialsProvider argument
  21:         // Create an instance of the UICredentialsProvider.
  22:         // Note that creating this instance will automatically
  23:         // popup the TFS authentication dialog
  24:         ICredentialsProvider provider = new UICredentialsProvider();
  25:         return Connect(serverName, new UICredentialsProvider());
  26:     }
  27: } // method Connect
  28:  
  29: /// <summary>
  30: /// This overload of the Connect Method takes a NetworkCredential.
  31: /// This method is typically used from a server client, which does
  32: /// not have the possibility to prompt the client for a username/password
  33: /// </summary>
  34: /// <param name="serverName"></param>
  35: /// <param name="credentials"></param>
  36: /// <returns></returns>
  37: public bool Connect(string serverName, NetworkCredential credentials)
  38: {
  39:     using (new Tracer(LoggingCategoryConstant.TFS_CONNECTIVITY_CONNECT))
  40:     {
  41:         //  Log our parameters
  42:         addLogEntry(
  43:             LoggingCategoryConstant.TFS_CONNECTIVITY_TRACE,
  44:             "Connect method with ServerName and Credentials Called",
  45:             EventIdentifier.ConnectToTfs,
  46:             EventPriority.StandardPriority,
  47:             TraceEventType.Verbose,
  48:             createCredentialsContextInfo(serverName, credentials));
  49:  
  50:         // Create our Network Credentials
  51:         NetworkCredential credentials = new NetworkCredential(TFSUserName, TFSPassword);
  52:  
  53:         // Here, we can pass the network credentials directly to the
  54:         // TeamFoundationServer constructor, so we have no need to used
  55:         // the factory here. You cannot use the factory methods with any 
  56:         // object instance that implements ICreditials, but it is supported
  57:         // in the standard constructor, as we are using it here
  58:         try
  59:         {
  60:             m_tfsServer = new TeamFoundationServer(serverName, credentials);
  61:             m_tfsServer.Authenticate();
  62:  
  63:             //  If we succcessfully authenticated, we can go ahead an extract
  64:             //  some reference to some commonly used service interfaces
  65:             if (m_tfsServer.HasAuthenticated)
  66:             {
  67:                 extractServiceInterfaces();
  68:             }
  69:         }
  70:         catch (Exception ex)
  71:         {
  72:             bool rethrow = ExceptionPolicy.HandleException(ex, ExceptionPolicyName.EXTERNAL_LIBRARY_POLICY);
  73:             if (rethrow)
  74:             {
  75:                 throw;
  76:             }
  77:         }
  78:     }
  79:  
  80:     // Return the authentication status
  81:     return m_tfsServer.HasAuthenticated;
  82:  
  83: } // method Conection
  84:  
  85: /// <summary>
  86: /// This is our "generic" Connect method. This method takes any object 
  87: /// instance that implements ICredentialsProvider. It uses the 
  88: /// TeamFoundationServerFactory GetServer method to retrieve the 
  89: /// TeamFoundationServer implementation
  90: /// </summary>
  91: /// <param name="serverName"></param>
  92: /// <param name="credentialsProvider"></param>
  93: /// <returns></returns>
  94: public bool Connect(string serverName, ICredentialsProvider credentialsProvider)
  95: {
  96:     using (new Tracer(LoggingCategoryConstant.TFS_CONNECTIVITY_CONNECT))
  97:     {
  98:         //  Log our parameters
  99:         addLogEntry(
 100:             LoggingCategoryConstant.TFS_CONNECTIVITY_TRACE,
 101:             "Connect method with ServerName and Credentials Called",
 102:             EventIdentifier.ConnectToTfs,
 103:             EventPriority.StandardPriority,
 104:             TraceEventType.Verbose,
 105:             createCredentialsContextInfo(serverName, credentialsProvider));
 106:  
 107:         // Create a TeamFoundationServer instance through the factory,
 108:         // and authenticate
 109:         try
 110:         {
 111:             m_tfsServer =
 112:                 TeamFoundationServerFactory.GetServer(serverName, credentialsProvider);
 113:             m_tfsServer.Authenticate();
 114:  
 115:             //  If we succcessfully authenticated, we can go ahead an extract
 116:             //  some reference to some commonly used service interfaces
 117:             if (m_tfsServer.HasAuthenticated)
 118:             {
 119:                 extractServiceInterfaces();
 120:             }
 121:         }
 122:         catch (Exception ex)
 123:         {
 124:             bool rethrow = ExceptionPolicy.HandleException(ex, ExceptionPolicyName.EXTERNAL_LIBRARY_POLICY);
 125:             if (rethrow)
 126:             {
 127:                 throw;
 128:             }
 129:         }
 130:  
 131:     }
 132:     // Return the authentication status
 133:     return m_tfsServer.HasAuthenticated;
 134:  
 135: } // method Connect

The first connect method uses the UICredentialsProvider class to retrieve the credentials from the user, and then calls the third overload of the Connect() method, which uses the TeamFoundationFactory.GetServer() method to connect to the Server, and the TeamFoundationServer's Authenticate() method to authenticate with the server.

The second Connect() overload accepts a NetworkCredential. Since the factory does not have an overload that takes an object that implement ICredentials, this method directly calls the TeamFoundationServer constructor, which does have an overload that accepts an ICredentials object instance.

Also, note that after successfully connecting and authenticating to the TFS server, all overloads call the extractServiceInterfaces() private method. This method extracts all relevant service interfaces from the TeamFoundationServer instance, and cache them as fields of the class, as is shown below:

        /// <summary>
        /// This method extract our most commonly used service interface
        /// from the TFS Server instance, and caches them in our private
        /// fields
        /// </summary>
        private void extractServiceInterfaces()
        {
            if (m_tfsServer != null)
            {
                m_commonStructureSvc = (ICommonStructureService)m_tfsServer.GetService(typeof(ICommonStructureService));
                m_workItemStore = (WorkItemStore)m_tfsServer.GetService(typeof(WorkItemStore));
                m_processTemplateSvc = (IProcessTemplates)m_tfsServer.GetService(typeof(IProcessTemplates));
                m_eventService = (IEventService)m_tfsServer.GetService(typeof(IEventService));
            }
        } // method extractServiceInterfaces

Note how we use the GetService(typeof(xxx)) pattern to retrieve each interface and/or object references (only the WorkItemStore is a object reference, all the others are interface references).

Retrieving Project Information

... coming soon ...

Comments

 

if ( ! blogClogged ) said:

I'm very interested to see what this potential PSDrive provider for the TFS repository does and looks

December 30, 2007 11:50 AM

Leave a Comment

(required)  
(optional)
(required)  
Add

About bennie

I work for a Microsoft Gold Partner, Statera SouthWest as a Strategic Partner and a Solutions Architect
Copyright ASIQS Corporation © 2006, All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems