Sunday, September 28, 2008

How to deal with areas or iterations using API

One relatively obscure area of TFS object model is area/iteration manipulation. Prompted by the question on how to delete area (btw, did you know how to use DeleteBranches for that) I decided to do a quick primer on area/iteration API and its usage.

First, how does one get areas/iteration list given the project (or parent node)? If you even started thinking about the areas or iterations, you will need to use ICommonStructureService service. This service encapsulates functionality related to TFS artifacts such as projects, areas and iterations (those are the common structures in its name).

Another important thing to know about this service is that most of its methods require artifact URIs as parameters. URI would consist of protocol (vstfs), type and unique identifier for the artifact; for example, for area it would be similar to "vstfs:///Classification/Node/[Guid]" (where Guid is unique ID for that area).

Let's start with a simple task of getting project's areas and iterations:

Dictionary<string, string> GetProjectAreas(string projectName)
{
    ICommonStructureService commonStructure = 
        _teamFoundationServer.GetService(typeof(ICommonStructureService)) 
            as ICommonStructureService;
    ProjectInfo project = commonStructure.GetProjectFromName(projectName);
    Dictionary<string, string> results = new Dictionary<string, string>();
 
    foreach (NodeInfo node in commonStructure.ListStructures(project.Uri))
    {
        // here will be more code
    }
    return results;
}

Note several aspects: creation of ICommonStructureService, conversion from project name to project URI and call to ListStructures method. Most important of those is ListStructures method – given project URI, it returns array of NodeInfo structures that holds all top level areas and iterations defined. NodeInfo itself provides host of properties such as node name, node path, node URI, parent URI and node structure type. Using the latter one may distinguish area node from iteration node (area StructureType is “ProjectModelHierarchy”, whereas for iteration it is “ProjectLifecycle”).

Common Structure Service (CSS) provides couple of handy methods to get NodeInfo structure either by node path (GetNodeFromPath) or by node URI (GetNode). However, most frequent task is to get subtree of nodes (areas of iterations) given project root node. For the top level nodes it can be easily done by using ListStructures; but to drill down into the tree one must use different approach, namely GetNodesXml method of CSS.

Code example below iterates over all root areas in project, and calls recursive function to retrieve all child areas and put them into dictionary of pairs (<area path>, <area URI>). First, I will change GetProjectAreas function code to include new function:

    foreach (NodeInfo node in commonStructure.ListStructures(project.Uri))
    {
        // more code
        if (node.StructureType != "ProjectModelHierarchy")
            continue;
        XmlElement nodeElement = 
            commonStructure.GetNodesXml(new string[] { node.Uri }, true);
        AddChildNodes(
            node.Name, nodeElement.ChildNodes[0], results);
    }

And now, iterate over the results recursively:

static void AddChildNodes(
    string parentPath, XmlNode parentNode, Dictionary<string, string> results)
{            
    results.Add(parentPath, parentNode.Attributes["NodeID"].Value);
 
    if (parentNode.ChildNodes[0] == null)
        return;
 
    foreach (XmlNode node in parentNode.ChildNodes[0].ChildNodes)
    {
        string nodePath = node.Attributes["Path"].Value;
        AddChildNodes(nodePath, node, results);                
    }
}

The example shows most features of GetNodesXml. It takes two parameters: array of root node URIs and boolean specifying whether to retrieve data for child nodes.

Then GetNodesXml returns hierarchical XML document, with every node in the return result represented as separate child “Node” XmlElement, with all properties as XML attributes (the attributes names and values correspond to NodeInfo properties). Raw XML for for area “Area 0” in project “Test Project” node will look similar to this (I removed actual GUID values for clarity):

<Node
  NodeID="vstfs:///Classification/Node/[guid 1]"
  Name="Area 0"
  ParentID="vstfs:///Classification/Node/[guid 2]"
  Path="\Test Project\Area\Area 0"
  ProjectID="vstfs:///Classification/TeamProject/[guid 3]"
  StructureType="ProjectModelHierarchy" />

Hierarchical means that if “Area 0”, for example, has any child areas, they will be contained in its Node XmlElement. Thus with the help of GetNodersXml it is possible to get the whole areas/iterations tree for specific project.

And to finalize review of CSS functionality that is relevant to areas/iterations, let’s have a look at other parts of C[R]UD.

How to create area/iteration? There is method CreateNode (that takes parent node URI and new node name); additionally, there is ImportBranch method that takes new node in the form of XmlElement  (XML should conform to CSS format, same as used by GetNodesXml above).

How to update area/iteration? For the purpose of rename one may use RenameNode method (supplying node URI and new name); to move the node (and all child nodes) one may use MoveBranch method (supplying node and parent URIs)

How to delete area (or iteration)? First, you need to get area/iteration URI (and that is covered in the beginning of the post). Next, you need to call method DeleteBranches of ICommonStructureService.


Sound simple, eh? However, the method takes two parameters: while first is easy to understand –  the array of node URI to delete, the second (named reclassifyUri) is far less obvious.

To understand how that parameter works, it would be helpful to refer to one of my past posts that talks about deleting area/iteration in Team Explorer UI.

Now, reclassifyUri is exactly that - the URI of the node to use in reassigning all work items associated with areas/iterations that are being deleted (or as MSDN succinctly puts it "URI of the node to which artifacts are reclassified").

In that post I tried to give quick survey of basics related to areas/iterations API. It is worth noting that CSS can be used for another important purpose of listing projects (for example, as demonstrated in James Manning blog post).

To conclude, some related links that may be of interest (mostly tools with the source code):

3 comments:

Андрей said...

Евгений, хотел бы попросить Вас о помощи. С TFS работаю недавно и не могу программно на C# .Net 3.5 скопировать файл в папку Documents в Team Explorer. Не подскажите, как это можно сделать?

eugenez said...

Andrey,

That would require automatic Sharepoint - the documents folder on TFS is shortcut to Sharepoint document library. So adding documents there would really not require any specific expertise on TFS, but only knowledge of Sharepoint API.

Does that make sense?

Vladimir said...

You are the man!
Your code helped me a lot!
I am creating a custom winfowm and wanted to allow a user to select iteration and areas manually. And i have found nothing about it in MSDN and other sources!
Your code is great and it is something i really needed!

Спасибо большое! :)