Compartilhar via


Writing Code for Different Types of Work Items by Using the Client Object Model for Team Foundation

When you write code for different team projects, you can perform the same function on different types of work items that serve similar purposes. These types might be different because they come from different process templates, or a team might have customized them for a specific team project. For example, you might want to perform the same function on user stories and requirements, both of which should represent what customers require and value. Instead of writing separate code for each type of work item, you can generalize your code to work for both. In addition, one team might have customized the work item definition for user stories so that team members can estimate work in hours instead of in story points. You can avoid duplicating your own effort by adjusting your code to handle both the default and the customized strategies.

In this topic, you can find a code sample that performs a task on a specific type of work item that has not been customized, and then you can learn how to refactor that code to support certain kinds of customizations. For more information about how to customize types of work items, see Customizing Team Projects and Processes.

In this topic

  • Print Trees of User Stories with Estimates in Story Points

  • Print Trees of User Stories with Estimates in Either Story Points or Baseline Work

  • Print Trees of Work Items That Are Used as Requirements with Estimates

Print Trees of User Stories with Estimates in Story Points

This code sample prints trees of all user stories in each team project on a server and includes estimates for each story that has no children. To use this sample, you create a console application, add references to the following assemblies, and then replace the contents of Program.cs with the following code.

using System;
using System.Text;
using System.Collections.ObjectModel;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace Microsoft.TeamFoundation.SDK
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // Connect to Team Foundation Server. The form of the url is http://server:port/vpath.
                //     Server - the name of the server that is running the application tier for Team Foundation.
                //     port - the port that Team Foundation uses. The default port is 8080.
                //     vpath - the virtual path to the Team Foundation application. The default path is tfs.
                TfsConfigurationServer configurationServer =
                    TfsConfigurationServerFactory.GetConfigurationServer(new Uri("https://Server:8080/tfs"));
               
                // Get the catalog of team project collections
                CatalogNode catalogNode = configurationServer.CatalogNode;
                ReadOnlyCollection<CatalogNode> tpcNodes = catalogNode.QueryChildren(
                    new Guid[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None);

                // Process each team project collection
                foreach (CatalogNode tpcNode in tpcNodes)
                {
                    // Use the InstanceId property to get the team project collection
                    Guid tpcId = new Guid(tpcNode.Resource.Properties["InstanceId"]);
                    TfsTeamProjectCollection tpc = configurationServer.GetTeamProjectCollection(tpcId);

                    // Get the work item store
                    WorkItemStore wiStore = tpc.GetService<WorkItemStore>();

                    // Query for the trees of active user stories in the team project collection
                    StringBuilder queryString = new StringBuilder("SELECT [System.Id] FROM WorkItemLinks WHERE ");
                    queryString.Append("([Source].[System.WorkItemType] = 'User Story' AND [Source].[System.State] = 'Active') AND ");
                    queryString.Append("([System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward') And ");
                    queryString.Append("([Target].[System.WorkItemType] = 'User Story' AND [Target].[System.State] = 'Active') ORDER BY [System.Id] mode(Recursive)");
                    Query wiQuery = new Query(wiStore, queryString.ToString());
                    WorkItemLinkInfo[] wiTrees = wiQuery.RunLinkQuery();

                    // Print the trees of user stories, with the estimated sizes of each leaf
                    PrintTrees(wiStore, wiTrees, "    ", 0, 0);
                }
            }

            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // Each WorkItemLinkInfo structure in the collection contains the IDs of the linked work items.
        // In this case, the sourceId is the ID of the user story that is on the parent side of the link, and
        // the targetId is the ID of the user story that is on the child side of the link. The links
        // are returned in depth-first order. This function recursively traverses the collection
        // and the title of each user story. If the user story has no children, its estimation is also printed.
        static int PrintTrees(WorkItemStore wiStore, WorkItemLinkInfo[] wiTrees, string prefix, int sourceId, int iThis)
        {
            int iNext = 0;

            // Get the parent of this user story, if it has one
            WorkItem source = null;
            if (sourceId != 0)
            {
                source = wiStore.GetWorkItem(wiTrees[iThis].SourceId);
            }

            // Process the items in the list that have the same parent as this user story
            while (iThis < wiTrees.Length && wiTrees[iThis].SourceId == sourceId)
            {
                // Get this user story
                WorkItem target = wiStore.GetWorkItem(wiTrees[iThis].TargetId);
                Console.Write(prefix);
                Console.Write(target.Type.Name);
                Console.Write(": ");
                Console.Write(target.Fields["Title"].Value);
                if (iThis < wiTrees.Length - 1)
                {
                    if (wiTrees[iThis].TargetId == wiTrees[iThis + 1].SourceId)
                    {
                        // The next item is this user story's child. Process the children
                        Console.WriteLine();
                        iNext = PrintTrees(wiStore, wiTrees, prefix + "    ", wiTrees[iThis + 1].SourceId, iThis + 1);
                    }
                    else
                    {
                        // The next item is not this user story's child.
                        Console.Write("; estimate = ");
                        Console.WriteLine(target.Fields["Story Points"].Value);
                        iNext = iThis + 1;
                    }
                }
                else
                {
                    // This user story is the last one.
                    iNext = iThis + 1;
                }

                iThis = iNext;
            }

            return iNext;
        }
    }
}

Print Trees of User Stories with Estimates in Either Story Points or Baseline Work

This sample modifies the PrintTrees method to determine whether each work item uses the story points field or the baseline work field and to print the estimate from that field. To use this sample, you replace the line Console.WriteLine(target.Fields["Story Points"].Value); in the PrintTrees method with the following code.

// Determine which estimation field is present
string fieldName = "Story Points";
if (target.Type.FieldDefinitions.TryGetByName(fieldName) == null) 
{
    fieldName = "Baseline Work";
}
Console.WriteLine(target.Fields[fieldName].Value); 

You can group into categories different types of work items that serve the same or similar purposes in different team projects. You can write code to perform the same function on all work items that belong to a category. This sample prints estimates for any type of work item in the requirements category, such as user stories, requirements, and product backlog items.

To use this sample, replace the Main function from the previous sample with the following code.

static void Main(string[] args) 
{
    try
    {
        // Connect to Team Foundation Server. The form of the url is http://server:port/vpath.
        //     server - the name of the server that is running the application tier for Team Foundation.
        //     port - the port that Team Foundation uses. The default ort is 8080.
        //     vpath - the virtual path to the Team Foundation application. The default path is tfs. 
        TfsConfigurationServer configurationServer =
            TfsConfigurationServerFactory.GetConfigurationServer(new Uri("https://server:8080/tfs"));

        // Get the catalog of team project collections
        CatalogNode catalogNode = configurationServer.CatalogNode; 
        ReadOnlyCollection<CatalogNode> tpcNodes = catalogNode.QueryChildren(
            new Guid[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None); 

        // Process each team project collection
        foreach (CatalogNode tpcNode in tpcNodes) 
        {
            // Use the InstanceId property to get the team project collection
            Guid tpcId = new Guid(tpcNode.Resource.Properties["InstanceId"]);
            TfsTeamProjectCollection tpc = configurationServer.GetTeamProjectCollection(tpcId); 

            // Get the work item store
            WorkItemStore wiStore = tpc.GetService<WorkItemStore>();
            foreach (Project project in wiStore.Projects)
            {
                Console.Write("Project: ");
                Console.WriteLine(project.Name); 

                // Get the type of work item to use
                CategoryCollection categories = wiStore.Projects[project.Name].Categories;
                string wiType = categories["Requirement Category"].DefaultWorkItemType.Name;

                // Query for the trees of active user stories in the team project collection
                StringBuilder queryString = new StringBuilder("SELECT [System.Id] FROM WorkItemLinks WHERE ");
                queryString.Append("([Source].[System.WorkItemType] = '");
                queryString.Append(wiType);
                queryString.Append("' AND [Source].[System.TeamProject] = '");
                queryString.Append(project.Name);
                queryString.Append("') AND ");
                queryString.Append("([System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward') And ");
                queryString.Append("([Target].[System.WorkItemType] = 'User Story'  AND  ");
                queryString.Append("[Target].[System.State] = 'Active') ORDER BY [System.Id] mode(Recursive)"); 
                Query wiQuery = new Query(wiStore, queryString.ToString());
                WorkItemLinkInfo[] wiTrees = wiQuery.RunLinkQuery();

                // Print the trees of user stories and requirements, with the estimated size of each leaf

                PrintTrees(wiStore, wiTrees, "    ", 0, 0); 
            }
        }
    }

    catch (Exception e) 
    {
        Console.WriteLine(e.Message); 
    }
}