TFS 2010 - WorkItems Linking and Automation Workflow implementation for Closing Linked Workitems

Posted by: Subodh Sohoni , on 6/22/2009, in Category VSTS & TFS (Azure DevOps)
Views: 58644
Abstract: In my earlier article on “Spawn ‘Tasks’ in response to creation of ‘User Story’” we had seen how to use the Team Foundation Object Model in general and WorkItem Tracking APIs of TFS 2010 in particular.
TFS 2010 - WorkItems Linking and Automation Workflow implementation for Closing Linked Workitems
In my earlier article on “Spawn ‘Tasks’ in response to creation of ‘User Story’” we had seen how to use the Team Foundation Object Model in general and WorkItem Tracking APIs of TFS 2010 in particular. I cannot help in writing the other end of the workflow which was started in that article. We dealt with the creation and linking of workitems in that article. Let us now see how to close the workitems which are linked with parent – child links.
Taking a case which I feel is very interesting one, we will close the child workitems as well as set the state of parent to ‘Resolved’ under certain conditions. It is very natural that if a ‘Requirement’ or ‘User Story’ is dropped due to any reason then the orphaned tasks should also be closed. If they remain in the ‘Active’ state then some work may get done against them without the requirement being present, which is to say the least is waste of efforts. Another perspective to it is if all the ‘Tasks’ which implement a ‘Requirement’ or ‘User Story’ are closed then the parent workitem should reflect the actual state it is in which is ‘Resolved’. These activities can be done manually but that may lead to chance of omission. Not to leave anything to chance, I would like to do these things automatically as soon as some events take place. Now, what is better way of taking action on some events than to create some event handler!
We will implement two workflows in the same event handler since both are to be executed against the same event, ‘WorkItemChanged’. The first workflow is:
·         User closes a ‘Task’
·         Event handler gets the parent ‘User Story’ of the task – I firmly believe that there should not be any tasks lying around which cannot be linked to a ‘Requirement’ or ‘User Story’.
·         Event Handler now gets all the children ‘Tasks’ of this ‘User Story’
·         State of these children is checked and if all are closed then state of the parent ‘User Story’ is set to ‘Resolved’
Let us create some code for this in the ‘Notify’ method of the subscribing webservice. We will begin with converting the event related data passed as parameter of the type string into a XML document which can be queried. The XPath Queries that we will make first will filter out all events which do not have ‘Task’ being closed.
[SoapDocumentMethod(Action = "", RequestNamespace = ""), WebMethod(MessageName = "Notify")]
public void Notify(string eventXml, string tfsIdentityXml)
                  if (! (EventLog.SourceExists("TfsSource")))
                        EventLog.CreateEventSource("TfsSource", "TfsLogs");
                  EventLog ThisLog = new EventLog();
                  ThisLog.Source = "TfsSource";
                        XmlDocument eventDoc = new XmlDocument();
                        XmlNode node1 = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Work Item Type']/NewValue");
                        XmlNode node2 = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='State']/NewValue");
 if (node1.InnerText == "Task" && node2.InnerText == "Closed")
<SoapDocumentMethod(Action := "", RequestNamespace := ""), WebMethod(MessageName := "Notify")> _
Public Sub Notify(ByVal eventXml As String, ByVal tfsIdentityXml As String)
                  If (Not EventLog.SourceExists("TfsSource")) Then
                        EventLog.CreateEventSource("TfsSource", "TfsLogs")
                  End If
                  Dim ThisLog As New EventLog()
                  ThisLog.Source = "TfsSource"
                        Dim eventDoc As New XmlDocument()
                        Dim node1 As XmlNode = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Work Item Type']/NewValue")
                        Dim node2 As XmlNode = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='State']/NewValue")
 If node1.InnerText = "Task" AndAlso node2.InnerText = "Closed" Then
Now that we have the desired event data, we will create the objects for Team Foundation Server, WorkItem Store and the workitem which is passed to us that is the task being closed.
TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer("http://to-intra:8080/Tfs/DefaultCollection");
 WorkItemStore WIStore = new WorkItemStore(tfs);
 WorkItem ClosingTask = WIStore.GetWorkItem(Int32.Parse(eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/OldValue").InnerText));
Dim tfs As TeamFoundationServer = TeamFoundationServerFactory.GetServer("http://to-intra:8080/Tfs/DefaultCollection")
Dim WIStore As New WorkItemStore(tfs)
Dim ClosingTask As WorkItem = WIStore.GetWorkItem(Int32.Parse(eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/OldValue").InnerText))
Now we will get a collection of all the links from this task to other workitems. Workitems can have links to other workitems as well as to other artefacts. In the earlier versions of TFS we used to work with Linking Service which was not easy to manoeuvre. We now have a link type for workitems and another link type for all other artefacts. We will off course create the collection of workitem links. Once we have that collection then we can go through all the links to find the link to the parent workitem.
WorkItemLinkCollection AllLinksToClosingTask = ClosingTask.WorkItemLinks;WorkItemLink ParentLink = null;
 foreach (WorkItemLink link in AllLinksToClosingTask)
          if (link.LinkTypeEnd.Name == "Parent")
                   ParentLink = link;
Dim AllLinksToClosingTask As WorkItemLinkCollection = ClosingTask.WorkItemLinks
Dim ParentLink As WorkItemLink = Nothing
For Each link As WorkItemLink In AllLinksToClosingTask
             If link.LinkTypeEnd.Name = "Parent" Then
                           ParentLink = link
                           Exit For
             End If
Next link
We will now create the instance of the parent workitem and get the collection of links from it in turn. These links are to the children which are other tasks for the implementation of parent. We will then go through all of those to check the status of each of them and if we find that status of all of them is set to ‘Closed’ then we will set the status of the parent to ‘Resolved’ and save it.
WorkItem Parent = WIStore.GetWorkItem(ParentLink.TargetId);
WorkItemLinkCollection AllLinksToParent = Parent.WorkItemLinks;
bool AllChildrenWorkItemsClosed = true;
foreach (WorkItemLink child in AllLinksToParent)
      if (WIStore.GetWorkItem(child.TargetId).State != "Closed")
         AllChildrenWorkItemsClosed = false;
if (AllChildrenWorkItemsClosed)
        Parent.State = "Resolved";
Dim Parent As WorkItem = WIStore.GetWorkItem(ParentLink.TargetId)
Dim AllLinksToParent As WorkItemLinkCollection = Parent.WorkItemLinks
Dim AllChildrenWorkItemsClosed As Boolean = True
For Each child As WorkItemLink In AllLinksToParent
       If WIStore.GetWorkItem(child.TargetId).State <> "Closed" Then
             AllChildrenWorkItemsClosed = False
       End If
Next child
If AllChildrenWorkItemsClosed Then
            Parent.State = "Resolved"
End If
After creating code for first workflow let us now work on the second workflow.
·         User sets the status of the ‘User Story’ to ‘Closed’, may be because the requirement is dropped or is found to be not a requirement at all.
·         Event handler finds out the tasks which were created to implement the ‘User Story’
·         Status of all those tasks is set to ‘Closed’
This is a much simpler workflow to implement. We will first filter out all the events where event is not for ‘User Story’ with the status ‘Closed’. Then we will create the instance of Team Foundation Server, WorkItem Store and the workitem of the type ‘User Story’ which is passed in the parameter to the event handler.
else if (node1.InnerText == "User Story" && node2.InnerText == "Closed")
            TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer("http://to-intra:8080/Tfs/DefaultCollection");
            WorkItemStore WIStore = new WorkItemStore(tfs); //This is neat compared to GetService()
            WorkItem ClosingStory =   WIStore.GetWorkItem(Int32.Parse(eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/OldValue").InnerText));
Now we will find out all the links to children and one by one set the status of each of them to ‘Closed’.
WorkItemLinkCollection AllLinksToClosingStory = ClosingStory.WorkItemLinks;
 foreach (WorkItemLink link in AllLinksToClosingStory)
                        WorkItem RelatedTask = WIStore.GetWorkItem(link.TargetId);
                        RelatedTask.State = "Closed";
            catch (Exception ex)
               ThisLog.WriteEntry(ex.Message + ex.InnerException.Message);
ElseIf node1.InnerText = "User Story" AndAlso node2.InnerText = "Closed" Then
                  Dim tfs As TeamFoundationServer = TeamFoundationServerFactory.GetServer("http://to-intra:8080/Tfs/DefaultCollection")
                  Dim WIStore As New WorkItemStore(tfs) 'This is neat compared to GetService()
                  Dim ClosingStory As WorkItem = WIStore.GetWorkItem(Int32.Parse(eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/OldValue").InnerText))
Now we will find all the links to children and one by one set the status of each of them to ‘Closed’. WorkItemLinkCollection AllLinksToClosingStory = ClosingStory.WorkItemLinks
 For Each link As WorkItemLink In AllLinksToClosingStory
                                    Dim RelatedTask As WorkItem = WIStore.GetWorkItem(link.TargetId)
                                    RelatedTask.State = "Closed"
 Next link
End If
                  Catch ex As Exception
                     ThisLog.WriteEntry(ex.Message + ex.InnerException.Message)
                  End Try
That was the skeleton of the code for very interesting two workflows. You may have to flesh in the validations, setting other properties of workitems and error checking etc. but this can be a jumping board for creating your customized applications for the purpose. Some salient points are:
1.    Hierarchical workitems and links of different types (Parent – Child, Predecessor – Successor) are a new addition in TFS 2010.
2.    In TFS 2010 Beta 1 there is a bug due to which you will need to create code in .NET 2.0 only. An indirectly referenced assembly Microsoft.TeamFoundation.WorkItemTracking.Client.DataStore.dll is incompatible with .NET 4.0 so you have to build and host your webservice in .NET 2.0. This is tricky and takes lots of efforts in Team Suite 2010. Easiest way out that I can suggest is to create the webservice in VS / Team Suite 2008, giving the reference to TFS 2010 assemblies.
3. TFS 2010 assemblies which are needed ( Microsoft.TeamFoundation.Client.dll and Microsoft.TeamFoundation.WorkItemTracking.Client.dll) are now in the C:\Program Files\Microsoft Visual Studio 10\Common7\IDE\RefereceAssemblies and not in the PrivateAssemblies folder.
The entire source code of this article can be downloaded from here

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+


Subodh is a Trainer and consultant on Azure DevOps and Scrum. He has an experience of over 33 years in team management, training, consulting, sales, production, software development and deployment. He is an engineer from Pune University and has done his post-graduation from IIT, Madras. He is a Microsoft Most Valuable Professional (MVP) - Developer Technologies (Azure DevOps), Microsoft Certified Trainer (MCT), Microsoft Certified Azure DevOps Engineer Expert, Professional Scrum Developer and Professional Scrum Master (II). He has conducted more than 300 corporate trainings on Microsoft technologies in India, USA, Malaysia, Australia, New Zealand, Singapore, UAE, Philippines and Sri Lanka. He has also completed over 50 consulting assignments - some of which included entire Azure DevOps implementation for the organizations.

He has authored more than 85 tutorials on Azure DevOps, Scrum, TFS and VS ALM which are published on is a regular speaker at Microsoft events including Partner Leadership Conclave.You can connect with him on LinkedIn .

Page copy protected against web site content infringement 	by Copyscape

Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Priya on Friday, March 29, 2013 5:17 PM
Will this service be installed on the TFS server ? Can it be consumed during the workflow ? when the work item is being edited ?