At one point in time I needed some reports from TFS related to the work items that were handled during some sprints. Even though this is possible from Excel (there is a nice integration between these two) my developer ego was hurt by this ideea and I’ve decided to create a little app in C# that connects to TFS. I’ve searched high and I’ve seached low but did not find any full tutorial or at least a working one so the next step is to share with you how you can achieve this.
Step 1 – create a new Visual Studio project
First thing, first thing first, create a new project inside VS (I used VS 2013 since 2015 was not yet released). Initially, for POC purposes, the application was created as desktop application with all the relevant code in it. After few successful tests I’ve moved the code handling TFS communication into a BL and added a new ASP.Net project which acts as the GUI. The Desktop app still works but I’m not planning of using it anymore.
Step 2 – Create the main UI
For the GUI part I’ve decided to use the ASP.Net project type since I’m very familiar with it and because it provides all the tools to create a simple web application very fast (in my humble opinion). After cleaning all the junk that Visual Studio creates (authentications, authorizations, account managing pages, etc.) the only files that remained were default.aspx, global.asax, web.config and Site.Master.
Step 3 – Create a framework for generating reports
Since the reports will be either seen on a web page or sent by email and because theoretically the reports can also be generated by a desktop application, I’ve chosen to put the logic of generating the reports in the ReportingBL and generate them using Razor. Since Razor was designed to work with Web thingies and because the ReportingBL could be used by multiple clients without Web thingies I’ve downloaded a small set of classes from http://www.getcodesamples.com/src/EF734693 which handle the runtime compiling of my templates.
There could be a package you can download from somewhere but I did not bother to find it. This little gem of classes let’s you write code as below but I’m sure you already know about how, why, etc. If not, read here.
Step 4 – Connect to TFS from your app
This is done using the following snippet (I’m not including the variable definition because you can find them easily)
- public SprintDocumentation(string server, string project, string templatesPath)
- {
- tfsServer = new TfsTeamProjectCollection(new Uri(server));
- tfsStore = (WorkItemStore)tfsServer.GetService(typeof(WorkItemStore));
- tfsSProject = tfsStore.Projects[project];
- hyperLinkService = tfsServer.GetService<TswaClientHyperlinkService>();
- teamService = tfsServer.GetService<TfsTeamService>();
- this.templatesPath = templatesPath;
- }
You will need to add reference
- Microsoft.TeamFoundation.Client
- Microsoft.TeamFoundation.Common
- Microsoft.TeamFoundation.ProjectManagement
- Microsoft.TeamFoundation.VersionControl.Client
- Microsoft.TeamFoundation.WorkItemTracking.Client
and the following namespaces imported into you class
- using Microsoft.TeamFoundation.Client;
- using Microsoft.TeamFoundation.Framework.Client;
- using Microsoft.TeamFoundation.Framework.Common;
- using Microsoft.TeamFoundation.ProcessConfiguration.Client;
- using Microsoft.TeamFoundation.Server;
- using Microsoft.TeamFoundation.WorkItemTracking.Client;
Step 5 – Get all iterations from TFS
This is done using the following snippet
- public ProjectIterations GetAllIterations()
- {
- ProjectIterations result = new ProjectIterations();
- var configSvc = tfsServer.GetService<TeamSettingsConfigurationService>();
- var configs = configSvc.GetTeamConfigurationsForUser(new[] { tfsSProject.Uri.ToString() });
- ICommonStructureService4 css = tfsServer.GetService<ICommonStructureService4>();
- NodeInfo[] structures = css.ListStructures(tfsSProject.Uri.ToString());
- NodeInfo iterations = structures[0];
- XmlElement iterationTree = css.GetNodesXml(new[] { iterations.Uri }, true);
- BuildIterations(iterationTree.ChildNodes, css, ref result);
- result.Sort((x, y) => y.StartDate.Value.CompareTo(x.StartDate.Value));
- return result;
- }
- private void BuildIterations(XmlNodeList iterationsTree, ICommonStructureService4 css, ref ProjectIterations iterations)
- {
- foreach (XmlNode item in iterationsTree)
- {
- if (item.OuterXml.Contains(„YOUR_TEAM_NAME”))
- {
- var nodeId = GetNodeID(item.OuterXml);
- var nodeInfo = css.GetNode(nodeId);
- if (nodeInfo.StartDate != null && nodeInfo.FinishDate != null)
- {
- ProjectIteration pi = new ProjectIteration();
- pi.Name = nodeInfo.Name;
- pi.StartDate = nodeInfo.StartDate;
- pi.EndDate = nodeInfo.FinishDate;
- pi.IterationPath = nodeInfo.Path.Replace(„\\Iteration”, „”).TrimStart(‘\\’);
- iterations.Add(pi);
- }
- if (item.ChildNodes.Count > 0)
- {
- BuildIterations(item.ChildNodes, css, ref iterations);
- }
- }
- }
- }
- private string GetNodeID(string xml)
- {
- var first = „NodeID=\””;
- var start = xml.IndexOf(first) + first.Length;
- var end = xml.IndexOf(„\””, start);
- return xml.Substring(start, (end – start));
- }
There is another way (not parsing an XML) but that method does not return the EndDate which I needed.
Step 6 – Getting the work items by iteration
- private WorkItemCollection GetWorkItemInfoListForIteration(string iterationPath)
- {
- string select = String.Format(„SELECT System.ID FROM workitems WHERE [Iteration Path] = ‘” + iterationPath + „‘”);
- return tfsStore.Query(select);
- }
The method above return a list of work items which you can transform into your own collection depending on the fields you need.
For the complete code listing of the class that handles TFS connection visit this page. If it helps, say thanks. If not, contact me for more details.