TFS online as a part of Visual Studio Online ( VSO ) stays more and more popular solution for source control , backlog items management ( in the context of scrum and kanban ) , build system etc. Often software companies need to integrate work items management with other systems ( project management, billing etc.)
In the series of several blogs I will try to share my experience how to manage Visual Studio Programmatically.
In this article we will learn how to start to manage programmatically work items in Visual Studio Online. To do that you can use the VSO REST API calling it’s methods from any platform/language . Sometimes it is overhead to create own library to work with Visual Studio Online REST API. In this situation if you are using .NET / C# the best solution is just to use Visual Studio Online REST API in codeplex .
Visual Studio Online REST API is a C# library for using TFS REST API in your .NET applications. Current version supports:
- Projects and teams
- Areas and iterations
- Work item
- Query and query folder
- Version Control
- Git
- Build
This post is mainly focused on how to work with work items:
- Create / update / read work items
- Add / remove work item links and attachments
- Create / update / delete tags
Before to start manage work items using Visual Studio Online REST API you need to do some preparations:
- Prerequisites:
Authentication for the REST APIs:
These APIs support OAuth for authorization and you should plan to use that. With Oauth your users don’t have to provide their Visual Studio Online credentials to use when the APIs are called. To get started on your app, though, you can authenticate using basic authentication. You’ll have to enable alternate credentials to work with basic auth.
1. Open your profile
2. Enable alternate credentials on the credentials tab
3. Set your credentials
One appropriate place to do that are the application settings. It is possible of course to have a custom approach, but the samples from this blog assumes that you set there your credentials.
Managing work items in Visual Studio Online
We will cover the main functionalities in the context of , regarding to the Visual Studio Online REST API
- Get a team project description
One of the main features is to get the information for the VSO Team project, The code below shows how to do that,
1: VsoClient _vsoClient;
2: IVsoProject _client;
3:
4: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
5: _client = _vsoClient.GetService<IVsoProject>();
6:
7: var projects = _client.GetTeamProjects().Result;
8: var project = _client.GetTeamProject(projects[0].Id.ToString(), true).Result;
9: Console.Write("Project=" + project.ToString());
10: Console.Write("\n");
11: var descr = project.Description;
12: Console.Write("Project Descr=" + descr);
The sample app is a console application. You can see the result screen below.
- Getting the types of all work items
Another useful feature is how to get all the types of the work items. It is possible for each item using the GetWorkItemType() method.
1: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
2: _clientWit = _vsoClient.GetService<IVsoWit>();
3:
4: var workItemTypes = _clientWit.GetWorkItemTypes(Properties.Settings.Default.ProjectName).Result;
5: Console.Write("workItemTypes=" + workItemTypes.ToString());
6: Console.Write("\n");
7: for(int i = 0; i < workItemTypes.Count; i++)
8: {
9: var workItemTypeEl = _clientWit.GetWorkItemType(Properties.Settings.Default.ProjectName, workItemTypes.Items[i].Name).Result;
10: Console.Write("workItemType["+i+"]=" + workItemTypeEl.ToString());
11: Console.Write("\n");
12: }
13: var workItemType = _clientWit.GetWorkItemType(Properties.Settings.Default.ProjectName, workItemTypes.Items[0].Name).Result;
14: Console.Write("workItemType[0]=" + workItemType.ToString());
15: Console.Write("\n");
Below you can se the result in the console application:
- Get a field from a specified work item:
1: VsoClient _vsoClient;
2: IVsoWit _clientWit;
3:
4: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
5: _clientWit = _vsoClient.GetService<IVsoWit>();
6:
7: var fields = _clientWit.GetFields().Result;
8: Console.Write("fields=" + fields.ToString());
9: var field = _clientWit.GetField(fields.Items[0].ReferenceName).Result;
10: Console.Write("field[0]=" + fields.ToString());
- Get a specified backlog item:
It is possible to get a specified work item by work item id using GetWorkItem(workItemId) method
1: VsoClient _vsoClient;
2: IVsoWit _clientWit;
3:
4: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
5: _clientWit = _vsoClient.GetService<IVsoWit>();
6:
7: var workItem = _clientWit.GetWorkItem(170);
8: Console.Write("workItem=" + workItem.ToString());
9: Console.Write("\n");
10: var witType = workItem.Result.Fields["System.WorkItemType"];
11: Console.Write("WIT Type =" + witType.ToString());
12: Console.Write("\n");
13: var witAreaPath = workItem.Result.Fields["System.AreaPath"];
14: Console.Write("WIT Area Path =" + witAreaPath.ToString());
15: Console.Write("\n");
16: var witState = workItem.Result.Fields["System.State"];
17: Console.Write("WIT State =" + witState.ToString());
18: Console.Write("\n");
19: var witTitle = workItem.Result.Fields["System.Title"];
20: Console.Write("WIT Title =" + witTitle.ToString());
21: Console.Write("\n");
- Create and execute queries in VSO / TFS Online using VSO REST API
Queries help you find work items that you want to review, triage, update, or generate a report.
Use the search box to find work items. Enter the ID or use filters. If you want a flat list of work items, a hierarchical list using a tree query, or a list showing dependencies using a direct links query, use the query editor to choose the query type.
You can create queries in Visual Studio Online, Team Web Access (TWA), and Team Explorer. Also, you can open a query in Excel or Project to perform bulk modifications.
1: VsoClient _vsoClient;
2: IVsoWit _clientWit;
3:
4: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
5: _clientWit = _vsoClient.GetService<IVsoWit>()
6:
7: const string LINK_QUERY = "Select System.Id, System.Title, System.State From WorkItems Where ([System.WorkItemType] = 'Product Backlog Item' AND [State] <> 'Closed' AND [State] <> 'Removed')";
8: var ResultItems = _clientWit.RunFlatQuery(Properties.Settings.Default.ProjectName, LINK_QUERY).Result;
9: for(int i =0; i < ResultItems.WorkItems.Count; i++)
10: {
11: var _id = ResultItems.WorkItems[i].Id;
12: Console.Write("\n");
13: Console.Write("WIT id =" + _id.ToString());
14: var _workItem = _clientWit.GetWorkItem(_id);
15: Console.Write("\n");
16: var _witAreaPath = _workItem.Result.Fields["System.AreaPath"];
17: Console.Write("WIT[" + _id + "] Area Path =" + _witAreaPath.ToString());
18: Console.Write("\n");
19: var _witState = _workItem.Result.Fields["System.State"];
20: Console.Write("WIT[" + _id + "] State =" + _witState.ToString());
21: Console.Write("\n");
22: var _witTitle = _workItem.Result.Fields["System.Title"];
23: Console.Write("WIT[" + _id + "] Title =" + _witTitle.ToString());
24: Console.Write("\n");
25: }
You can see the result in the test application console below:
- Create and update a work item
It is possible to use methods CreateWorkItem() and UpdateWorkItem() to create and update work items. The sample demo app demonstrates how to do that using the Visual Studio Online REST API. The initial state of the sample work items is shown below:
Sample code also demonstrates how to create relations between work items:
1: VsoClient _vsoClient;
2: IVsoWit _clientWit;
3:
4: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
5: _client = _vsoClient.GetService<IVsoWit>()
6: var orkItemId = 170;
7: var workItems = _client.GetWorkItems(new int[] { workItemId }, RevisionExpandOptions.all).Result;
8:
9: // Create new work item
10: var bug = new WorkItem();
11: bug.Fields["System.Title"] = "Sample bug N3";
12: bug.Fields["System.History"] = DateTime.Now.ToString();
13: bug = _client.CreateWorkItem(Properties.Settings.Default.ProjectName, "Bug", bug).Result;
14:
15: var other = workItems[0];
16:
17: // Update fields, add a link
18: bug.Fields["System.Title"] = bug.Fields["System.Title"] + " (updated)";
19: bug.Fields["System.Tags"] = "Demo Tag";
20: Console.Write("\n");
21: Console.Write("WIT id =" + workItemId.ToString());
22: Console.Write("WIT[" + workItemId + "] Title =" + bug.Fields["System.Title"].ToString());
23:
24: bug.Relations.Add(new WorkItemRelation()
25: {
26: Url = other.Url,
27: Rel = "System.LinkTypes.Related",
28: Attributes = new RelationAttributes() { Comment = "Test Bug Relation" }
29: });
30:
31: bug = _client.UpdateWorkItem(bug).Result;
The final result is shown below: a new bug: Sample Bug N3, updated and including a to another bug item.
- Remove a work item ( bug)
It is also often seen case when you want to remove a work item. Actually it is not possible to remove it, but you can change the item state to “Removed”.
1: VsoClient _vsoClient;
2: IVsoWit _client;
3:
4: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
5: _client = _vsoClient.GetService<IVsoWit>()
6:
7: const string LINK_QUERY = "Select System.Id, System.Title, System.State From WorkItems Where ([System.WorkItemType] = 'Bug')";
8: var ResultItems3 = _client.RunFlatQuery(Properties.Settings.Default.ProjectName, LINK_QUERY).Result;
9:
10: var workItemTitle = "Sample bug N2"
11:
12: for (int i = 0; i < ResultItems3.WorkItems.Count; i++)
13: {
14: var _id = ResultItems3.WorkItems[i].Id;
15:
16: Console.Write("\n");
17: Console.Write("WIT id =" + _id.ToString());
18: var _workItem = _client.GetWorkItem(_id).Result;
19: if (_workItem.Fields["System.Title"].ToString().Contains(workItemTitle))
20: {
21: Console.Write("\n");
22: var _witTitle = _workItem.Fields["System.Title"];
23: Console.Write("WIT[" + _id + "] Title =" + _witTitle.ToString());
24: Console.Write("\n");
25: var _witState = _workItem.Fields["System.State"];
26:
27: Console.Write("WIT[" + _id + "] State =" + _witState.ToString());
28: Console.Write("\n");
29: _workItem.Fields["System.State"] = "Removed";
30: var bug = _client.UpdateWorkItem(_workItem).Result;
31: Console.Write("\n");
32: _witState = _workItem.Fields["System.State"];
33: Console.Write("WIT[" + _id + "] Updated State =" + _witState.ToString());
34: Console.Write("\n");
35: }
36:
37:
38: }
The screen below shows the state after the execution of the last snippet: The item with the state, equal to “Removed” is not displayed in the list with work items:
Below you can see the code of the whole application:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6: using System.Net;
7: using VisualStudioOnline.Api.Rest.V1.Client;
8: using VisualStudioOnline.Api.Rest.V1.Model;
9:
10: namespace DemoApp
11: {
12: class Program
13: {
14: static void Main(string[] args)
15: {
16: VsoClient _vsoClient;
17: IVsoProject _client;
18: IVsoWit _clientWit;
19:
20: _vsoClient = new VsoClient(Properties.Settings.Default.AccountName, new NetworkCredential(Properties.Settings.Default.UserName, Properties.Settings.Default.Password));
21: _client = _vsoClient.GetService<IVsoProject>();
22: _clientWit = _vsoClient.GetService<IVsoWit>();
23:
24:
25:
26: if (args != null)
27: {
28: Console.WriteLine("args is null"); // Check for null array
29:
30: var option = args[0];
31:
32: switch (option)
33: {
34: case "firstproject":
35: var projects = _client.GetTeamProjects().Result;
36: var project = _client.GetTeamProject(projects[0].Id.ToString(), true).Result;
37: Console.Write("Project=" + project.ToString());
38: Console.Write("\n");
39: var descr = project.Description;
40: Console.Write("Project Descr=" + descr);
41: break;
42:
43: case "workitemtypes":
44: var workItemTypes = _clientWit.GetWorkItemTypes(Properties.Settings.Default.ProjectName).Result;
45: Console.Write("workItemTypes=" + workItemTypes.ToString());
46: Console.Write("\n");
47: for(int i = 0; i < workItemTypes.Count; i++)
48: {
49: var workItemTypeEl = _clientWit.GetWorkItemType(Properties.Settings.Default.ProjectName, workItemTypes.Items[i].Name).Result;
50: Console.Write("workItemType["+i+"]=" + workItemTypeEl.ToString());
51: Console.Write("\n");
52: }
53: var workItemType = _clientWit.GetWorkItemType(Properties.Settings.Default.ProjectName, workItemTypes.Items[0].Name).Result;
54: Console.Write("workItemType[0]=" + workItemType.ToString());
55: Console.Write("\n");
56:
57: break;
58:
59: case "getfields":
60: var fields = _clientWit.GetFields().Result;
61: Console.Write("fields=" + fields.ToString());
62: var field = _clientWit.GetField(fields.Items[0].ReferenceName).Result;
63: Console.Write("field[0]=" + fields.ToString());
64:
65: break;
66: case "getbacklogitem":
67: var workItem = _clientWit.GetWorkItem(170);
68: Console.Write("workItem=" + workItem.ToString());
69: Console.Write("\n");
70: var witType = workItem.Result.Fields["System.WorkItemType"];
71: Console.Write("WIT Type =" + witType.ToString());
72: Console.Write("\n");
73: var witAreaPath = workItem.Result.Fields["System.AreaPath"];
74: Console.Write("WIT Area Path =" + witAreaPath.ToString());
75: Console.Write("\n");
76: var witState = workItem.Result.Fields["System.State"];
77: Console.Write("WIT State =" + witState.ToString());
78: Console.Write("\n");
79: var witTitle = workItem.Result.Fields["System.Title"];
80: Console.Write("WIT Title =" + witTitle.ToString());
81: Console.Write("\n");
82:
83: break;
84:
85: case "TestRunQuery":
86:
87: const string LINK_QUERY2 = "Select System.Id, System.Title, System.State From WorkItems Where ([System.WorkItemType] = 'Product Backlog Item' AND [State] <> 'Closed' AND [State] <> 'Removed')";
88: var ResultItems2 = _clientWit.RunFlatQuery(Properties.Settings.Default.ProjectName, LINK_QUERY2).Result;
89: var _id2 = ResultItems2.WorkItems[0].Id;
90: var _workItem2 = _clientWit.GetWorkItem(_id2);
91: Console.Write("\n");
92: var _witAreaPath2 = _workItem2.Result.Fields["System.AreaPath"];
93: Console.Write("WIT Area Path =" + _witAreaPath2.ToString());
94: Console.Write("\n");
95: var _witState2 = _workItem2.Result.Fields["System.State"];
96: Console.Write("WIT State =" + _witState2.ToString());
97: Console.Write("\n");
98: var _witTitle2 = _workItem2.Result.Fields["System.Title"];
99: Console.Write("WIT Title =" + _witTitle2.ToString());
100: Console.Write("\n");
101: Console.Write("Flat Query Result =" + ResultItems2.ToString());
102: Console.Write("\n");
103:
104: break;
105: case "TestRunQueries":
106:
107: const string LINK_QUERY = "Select System.Id, System.Title, System.State From WorkItems Where ([System.WorkItemType] = 'Product Backlog Item' AND [State] <> 'Closed' AND [State] <> 'Removed')";
108: var ResultItems = _clientWit.RunFlatQuery(Properties.Settings.Default.ProjectName, LINK_QUERY).Result;
109: for(int i =0; i < ResultItems.WorkItems.Count; i++)
110: {
111: var _id = ResultItems.WorkItems[i].Id;
112: Console.Write("\n");
113: Console.Write("WIT id =" + _id.ToString());
114: var _workItem = _clientWit.GetWorkItem(_id);
115: Console.Write("\n");
116: var _witAreaPath = _workItem.Result.Fields["System.AreaPath"];
117: Console.Write("WIT[" + _id + "] Area Path =" + _witAreaPath.ToString());
118: Console.Write("\n");
119: var _witState = _workItem.Result.Fields["System.State"];
120: Console.Write("WIT[" + _id + "] State =" + _witState.ToString());
121: Console.Write("\n");
122: var _witTitle = _workItem.Result.Fields["System.Title"];
123: Console.Write("WIT[" + _id + "] Title =" + _witTitle.ToString());
124: Console.Write("\n");
125: }
126:
127: break;
128:
129: case "CreateAndUpdateWorkItem":
130:
131:
132:
133: CreateAndUpdateWorkItem(_clientWit, 170);
134: break;
135:
136: case "RemoveBugItem":
137:
138: RemoveBugItem(_clientWit, "Sample Bug N2");
139:
140: break;
141:
142: }
143: }
144:
145:
146: Console.ReadLine();
147:
148: }
149:
150: public static void CreateAndUpdateWorkItem(IVsoWit _client, int workItemId)
151: {
152: var workItems = _client.GetWorkItems(new int[] { workItemId }, RevisionExpandOptions.all).Result;
153:
154: // Create new work item
155: var bug = new WorkItem();
156: bug.Fields["System.Title"] = "Sample bug N3";
157: bug.Fields["System.History"] = DateTime.Now.ToString();
158: bug = _client.CreateWorkItem(Properties.Settings.Default.ProjectName, "Bug", bug).Result;
159:
160: var other = workItems[0];
161:
162: // Update fields, add a link
163: bug.Fields["System.Title"] = bug.Fields["System.Title"] + " (updated)";
164: bug.Fields["System.Tags"] = "Demo Tag";
165: Console.Write("\n");
166: Console.Write("WIT id =" + workItemId.ToString());
167: Console.Write("WIT[" + workItemId + "] Title =" + bug.Fields["System.Title"].ToString());
168:
169: bug.Relations.Add(new WorkItemRelation()
170: {
171: Url = other.Url,
172: Rel = "System.LinkTypes.Related",
173: Attributes = new RelationAttributes() { Comment = "Test Bug Relation" }
174: });
175:
176: bug = _client.UpdateWorkItem(bug).Result;
177: }
178:
179:
180: public static void RemoveBugItem(IVsoWit _client, int workItemId)
181: {
182: //"Sample bug N2 (updated)"
183: const string LINK_QUERY = "Select System.Id, System.Title, System.State From WorkItems Where ([System.WorkItemType] = 'Bug' AND [State] <> 'Closed' AND [State] <> 'Removed')";
184: var ResultItems = _client.RunFlatQuery(Properties.Settings.Default.ProjectName, LINK_QUERY).Result;
185: var workItems = _client.GetWorkItems(new int[] { workItemId }, RevisionExpandOptions.all).Result;
186: // Create new work item
187: var bug = new WorkItem();
188: bug.Fields["System.Title"] = "Sample bug N3";
189: bug.Fields["System.History"] = DateTime.Now.ToString();
190: bug = _client.CreateWorkItem(Properties.Settings.Default.ProjectName, "Bug", bug).Result;
191:
192: var other = workItems[0];
193:
194: // Update fields, add a link
195: bug.Fields["System.Title"] = bug.Fields["System.Title"] + " (updated)";
196: bug.Fields["System.Tags"] = "Demo Tag";
197: Console.Write("\n");
198: Console.Write("WIT id =" + workItemId.ToString());
199: Console.Write("WIT[" + workItemId + "] Title =" + bug.Fields["System.Title"].ToString());
200:
201: bug.Relations.Add(new WorkItemRelation()
202: {
203: Url = other.Url,
204: //Rel = "System.LinkTypes.Dependency-Forward",
205: Rel = "System.LinkTypes.Related",
206: Attributes = new RelationAttributes() { Comment = "Test Буг Relation" }
207: });
208:
209: bug = _client.UpdateWorkItem(bug).Result;
210: }
211:
212: public static void RemoveBugItem(IVsoWit _client, string workItemTitle)
213: {
214: //"Sample bug N2 (updated)"
215: const string LINK_QUERY = "Select System.Id, System.Title, System.State From WorkItems Where ([System.WorkItemType] = 'Bug')";
216: var ResultItems3 = _client.RunFlatQuery(Properties.Settings.Default.ProjectName, LINK_QUERY).Result;
217:
218: for (int i = 0; i < ResultItems3.WorkItems.Count; i++)
219: {
220: var _id = ResultItems3.WorkItems[i].Id;
221:
222: Console.Write("\n");
223: Console.Write("WIT id =" + _id.ToString());
224: var _workItem = _client.GetWorkItem(_id).Result;
225: if (_workItem.Fields["System.Title"].ToString().Contains(workItemTitle))
226: {
227: Console.Write("\n");
228: var _witTitle = _workItem.Fields["System.Title"];
229: Console.Write("WIT[" + _id + "] Title =" + _witTitle.ToString());
230: Console.Write("\n");
231: var _witState = _workItem.Fields["System.State"];
232:
233: Console.Write("WIT[" + _id + "] State =" + _witState.ToString());
234: Console.Write("\n");
235: _workItem.Fields["System.State"] = "Removed";
236: var bug = _client.UpdateWorkItem(_workItem).Result;
237: Console.Write("\n");
238: _witState = _workItem.Fields["System.State"];
239: Console.Write("WIT[" + _id + "] Updated State =" + _witState.ToString());
240: Console.Write("\n");
241: }
242:
243:
244: }
245: }
246: }
247: }
If you want more information about haw to manage programmatically Visual Studio Online work otems feel free to contact me at michael@mateev.net Follow my blog : mmateev.infragistics.com .
You can learn more about the PASS events if you follow me on Twitter @mihailmateev , and stay in touch on Facebook, Google+, LinkedIn and Bulgarian BI and .Net User Group !