Spawn tasks to implement newly added 'Requirement' or 'User Story' in Team Foundation Server (TFS) 2010

Posted by: Subodh Sohoni , on 6/16/2009, in Category VSTS & TFS (Azure DevOps)
Views: 39345
Abstract: In this article let us delve into the case of workitems spawning. We will check on how we can create a set of children work items through an automated process when a workitem of certain type is added by the user.
Spawn tasks to implement newly added ‘Requirement’ or ‘User Story’ in Team Foundation Server (TFS) 2010
 
In this article let us delve into the case of workitems spawning. We will check on how we can create a set of children work items through an automated process when a workitem of certain type is added by the user. For example, when a Requirement or User Story is created, we will find out how the Tasks to fulfill that Requirement or to implement that User Story can be created in our program. Many organizations would like that kind of automation since it ensures that we are ensuring that we will implement that Requirement or User Story and avoid oversight.



We begin with conceptualizing the workflow.
·         A workitem is added by the user.
·         An event handler kicks in and checks that workitem is of the type “User Story” and it is newly created.
·         The event handler reads the details of the workitems to be spawned from the configuration file.
·         New workitems are spawned with the titles as read from configuration file.
·         Parent – Child links are created between the “User Story” and spawned “Tasks”
·         Workitems are saved.
To implement this workflow we will require a webservice to be created as an event handler and create a subscription for that webservice. I have handled the issue of ‘how to subscribe to the TFS2010 events’ in my earlier article (https://www.dotnetcurry.com/ShowArticle.aspx?ID=330).
The webservice that we create will work as a subscriber to the “WorkItemChangedEvent”. We begin by creating a webservice project.
Note: We should select .NET framework 2.0 since an indirectly referenced assembly Microsoft.TeamFoundation.WorkItemTracking.Client.DataStore.dll cannot work in .NET 4.0 environment (at least in VSTS 2010 Beta 1 and .NET 4.0 Beta 1).
Provide references to:
·         Microsoft.TeamFoundation.dll
·         Microsoft.TeamFoundation.Client.dll
·         Microsoft.TeamFoundation.WorkItemTracking.Client.dll
These assemblies are located in <system drive>\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies folder.
Next we add the ‘using’ or ‘Import’ statements as the selected language may be. We will require the namespaces:
·         System.Xml
·         System.Web.Configuration
·         System.Web.Services.Protocols
·         Microsoft.TeamFoundation.dll
·         Microsoft.TeamFoundation.Client.dll
·         Microsoft.TeamFoundation.WorkItemTracking.Client.dll.
We may change the class name and the name of the .asmx file. If you change the name of the .asmx file remember to change that in the CodeBehind tag in the file. We now will add the Notify method which will be our webmethod that will execute on event notification. The Notify method will take two parameters. The first one will store the event related data which will be sent by TFS with event notification and the second one will pass the TFS identity data.
Notify method will also have two attributes and the entire signature of the method is as follows:
C#
[SoapDocumentMethod(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", RequestNamespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
[WebMethod(MessageName = "Notify")]
public void Notify(string eventXml, string tfsIdentityXml)
{
}  
VB.NET
  <SoapDocumentMethod(Action:=_
"http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", RequestNamespace:="_
http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03"), _
   WebMethod(MessageName:="Notify")> _
    Public Sub Notify(ByVal eventXml As String, ByVal tfsIdentityXml As String)
    End Sub
We will now convert the event related data which is XML data converted into string, back to XML in a xml document
C#
XmlDocument eventDoc = new XmlDocument();
eventDoc.LoadXml(eventXml);
 
VB.NET
Dim eventDoc As New XmlDocument()
eventDoc.LoadXml(eventXml)
 
Our first task is to check that the notification is for the event that is for “addition of a new workitem of the type ‘User Story’. For this we will make use of the XPath query
·         To get the ‘NewValue’ node of the ‘Field’ node for which ‘Name’ node has a value of ‘Work Item Type’ and
·         To get the ‘OldValue’ node of the in the ‘Field’ node for which ‘Name’ node has a value of ‘ID’
We will check that the InnerText value of ‘NewValue’ is ‘User Story’ and that of the ‘OldValue’ node is 0.
C#
XmlNode node1 = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Work Item Type']/NewValue");
XmlNode node2 = eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/OldValue");
 if (node1.InnerText == "User Story" && Int32.Parse(node2.InnerText) == 0)
VB.NET
Dim node1 As XmlNode = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Work Item Type']/NewValue")
Dim node2 As XmlNode = eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/OldValue")
If node1.InnerText = "User Story" AndAlso Int32.Parse(node2.InnerText) = 0 Then
 
Now that we are sure that we have the correct notification, we will start the code to create tasks.
We will first connect to the Team Foundation Server. We can use either the GetServer method of TeamFoundationServerFactory class or we can use the default constructor of TeamFoundationServer class. Notice that now in TFS 2010, we have to provide the Project Collection name while creating the object of TFS. We then get the WorkItemStore and using that get the collection of all workitem types supported in our project.
C#
TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer("http://TfsName:8080/Tfs/DefaultCollection");
WorkItemStore WIStore = new WorkItemStore(tfs);
WorkItemTypeCollection WITCollection = WIStore.Projects["Marketing Management"].WorkItemTypes;
 
VB.NET
 
Dim tfs As TeamFoundationServer = TeamFoundationServerFactory.GetServer("http://TfsName:8080/Tfs/DefaultCollection")
Dim WIStore As New WorkItemStore(tfs)
Dim WITCollection As WorkItemTypeCollection = WIStore.Projects("Marketing Management").WorkItemTypes
We now can get the tasks (titles only) that we are going to create as workitems. These tasks may be pre-configured in Web.Config file and if not, will be provided by the event handling code.
C#
List<String> tasks = ReadTasks();
VB.NET
Dim tasks As List(Of String) = ReadTasks()
Code of the method ReadTasks is as follows:
C#
private List<string> ReadTasks()
{
    List<string> tasks = new List<string>();
    Configuration appSettingsRoot = WebConfigurationManager.OpenWebConfiguration(null);
    if (0 < appSettingsRoot.AppSettings.Settings.Count)
    {
        tasks.Add("Architect Solution");
        tasks.Add("Develop Solution");
        tasks.Add("Test Solution");
        tasks.Add("Deploy Solution");
    }
    else
    {
        tasks.Add(ConfigurationManager.AppSettings.Get("ArchTask"));
        tasks.Add(ConfigurationManager.AppSettings.Get("DevTask"));
        tasks.Add(ConfigurationManager.AppSettings.Get("TestTask"));
        tasks.Add(ConfigurationManager.AppSettings.Get("DeployTask"));
    }
    return tasks;
}
VB.NET
    Private Function ReadTasks() As List(Of String)
        Dim tasks As New List(Of String)()
        Dim appSettingsRoot As Configuration = WebConfigurationManager.OpenWebConfiguration(Nothing)
        If 0 < appSettingsRoot.AppSettings.Settings.Count Then
            tasks.Add("Architect Solution")
            tasks.Add("Develop Solution")
            tasks.Add("Test Solution")
            tasks.Add("Deploy Solution")
        Else
            tasks.Add(ConfigurationManager.AppSettings.Get("ArchTask"))
            tasks.Add(ConfigurationManager.AppSettings.Get("DevTask"))
            tasks.Add(ConfigurationManager.AppSettings.Get("TestTask"))
            tasks.Add(ConfigurationManager.AppSettings.Get("DeployTask"))
        End If
        Return tasks
    End Function
We will now take one task title at a time from the list and create workitems of the type task for those. We will assign those tasks right now to the person who has created the User Story. Those can be reassigned to other developers latter on. We will also set the title of each task with the suffix of the User Story for implementing which we are creating these tasks. Finally, we will link these tasks to the “User Story” where that user story will be “parent” and the tasks will be children.
C#
foreach (string task in tasks)
{
    WorkItem wi = new WorkItem(WITCollection["Task"]);
    wi.Title = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Title']/NewValue").InnerText + task;
    wi.Fields["System.AssignedTo"].Value = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Assigned To']/NewValue").InnerText;
    wi.Fields["System.AssignedTo"].Value.ToString());
    WorkItemLinkTypeEnd linkTypeEnd = WIStore.WorkItemLinkTypes.LinkTypeEnds["Parent"];
    wi.Links.Add(new RelatedLink(linkTypeEnd, Int32.Parse(eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/NewValue").InnerText)));
    wi.Save();
 }
VB.NET
For Each task As String In tasks
    Dim wi As New WorkItem(WITCollection("Task"))
      wi.Title = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Title']/NewValue").InnerText & task
      wi.Fields("System.AssignedTo").Value = eventDoc.DocumentElement.SelectSingleNode("//StringFields/Field[Name='Assigned To']/NewValue").InnerText
    wi.Fields("System.AssignedTo").Value.ToString())
    Dim linkTypeEnd As WorkItemLinkTypeEnd = WIStore.WorkItemLinkTypes.LinkTypeEnds("Parent")
      wi.Links.Add(New RelatedLink(linkTypeEnd, Int32.Parse(eventDoc.DocumentElement.SelectSingleNode("//IntegerFields/Field[Name='ID']/NewValue").InnerText)))
      wi.Save()
Next task
This webservice should be hosted within its own application pool and the identity of the pool should be same as the TFS application pool or any other normal account. Use <identity impersonate=”true” /> tag with username and password of the user who has permissions to create workitems in the selected team project. Now you can add a subscription to the event using BisSubscribe tool.
 This boilerplate code has given you idea about how the tasks can be spawned when a “User Story” or “Requirement” is added by the user. Taking this as a base you can do number of modifications to improve the reliability of the code with additional customizations to suite your requirements.  
While doing so please remember:
1.    Create the subscriber in .NET 2.0 only! I spent 4 hours playing with assembly created with .NET 4.0 to realize that there is an incompatibility.
2.    Assemblies to be reference are located in ReferenceAssemblies folder and not in PrivateAssemblies folder as was in TFS 2008.
3.    While creating the instance of TeamFoundationServer provide the Uri consisting of Project Collection.
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+

Author

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 www.dotnetcurry.com.Subodh 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 Daddy-p on Wednesday, June 15, 2011 2:20 AM
hello Subodh,

Can you give me a step by step guide how I can implement this solution into my project? I am not familiar with web services.

Thanks in advance