Team Foundation Server 2012 build notification using ASP.Net Web API

For the last three years I have helped a financial company with a business critical financial system. I have the role as an architect and coach when it comes to system design. I also spend times to make the team work more efficiently, to release new features with high quality, and maintainable code faster. So the last months I have spent a lot of time with a Deployment Process, to see how we can use Continuous Delivery. We use Visual Studio 2012 and Team Foundation Server 2012 (TFS) as our configuration system. We use gated check-ins (The goal is to use branch by abstractions, so the team work against one mainline only, to remove the "merge hell"). Even if we use gated check-ins we had to disable some acceptance tests because the time it takes for them to run. Instead we use a build that runs at lunch time and one at the night to include the acceptance tests (Those needs to be observed by the team). So far TFS have worked perfect, both for Gated check-in and Continuous Integration for the mainline. We also use TFS for a "push deployment" to our internal test and UAT environment. Everything is automated. We haven't yet "enable" the "push-deploy" against our production environment yet.

For showing progress and what the team is working on we have a LCD screen on the wall displaying the TFS's Kanban board. During this weekend I have put together a simple program that will show if a build has failed (the one we run during lunch and at the night, that includes the long running acceptance tests) and the person that brook it, just for fun. Today every team member uses the Build Notification program shipped with Visual Studio, and also an e-mail alert for build notifications. The little program I put together during this weekend will show alerts on the LCD screen.

Team Foundation Server has Web Services that can query the build server for information, but it returns a lot of data and I had some authentication problem accessing it. Instead I decided to create a REST service with ASP.Net Web API to query the build server, and just fetch the information I need for my purpose, and also to try out the TFS 2012 APIs.

Accessing the TFS Build server


To access the TFS build server from code I added references to the Microsoft.TeamFoundation.Build.Client, Microsoft.TeamFoundation.Client and Microsoft.TeamFoundation.Common assemblies (In the Reference Manager you can find those assemblies by selecting Assemblies/Extensions).

To connect to the TFS and get the Team project collection I'm working against, I use the TfsTeamProjetCollection class. The TfsTeamProjectCollection constructor can take an URI and ICredentials as argument. I will use the NetworkCredential to provide login information to the TFS.


_teamProjectCollection = new TfsTeamProjectCollection(

                                                      new Uri(uri),

                                                      new NetworkCredential(username, password, domain));

 

To get access to the Build Server to get the latest build definition, the TfsTeamProjectCollection's GetService<T> method can be used, and the <T> represents the type of the service to get. In this case I want the IBuildServer. To get a build definition from the build server, the IBuildServer's GetBuildDefinition method can be used, it can take the name of the project the build belongs to and also the build to get. The GetBuildDefinition method returns IBuildDefintion.

var buildService = _teamProjectCollection.GetService<IBuildServer>();

 

var build = buildService.GetBuildDefinition(_projectName, buildName);

 

if (build == null)

   throw new BuildDefinitionDoesNotExistException(string.Format("The Build '{0}' can't be found", buildName));

 

To get the details from a specific build, the IBuildServer's GetBuild method can be used. The GetBuild method can take an URI for a specific build and returns IBuildDetail. In my case I want to get the latest build, so I use the IBuildDefinition's LastBuildUri property so get the URI for the latest build.

return buildService.GetBuild(build.LastBuildUri);

 

Here is the whole code of a class (TfsBuildService) that uses the code above (I uses this as a simple helper method for my Web API):

 

namespace TfsNotifierWebApi.Models

{

    using System;

    using System.Net;

 

    using Microsoft.TeamFoundation.Build.Client;

    using Microsoft.TeamFoundation.Client;

 

    public class TfsBuildService : ITfsBuildService

    {

        private readonly string _projectName;

 

        private TfsTeamProjectCollection _teamProjectCollection;

 

        public TfsBuildService(string uri, string domain, string username, string password, string projectName)

        {

            _projectName = projectName;

 

            _teamProjectCollection = new TfsTeamProjectCollection(

                                                                new Uri(uri),

                                                                new NetworkCredential(username, password, domain));

        }

 

 

        public IBuildDetail GetLastBuildDetail(string buildName)

        {

            var buildService = _teamProjectCollection.GetService<IBuildServer>();

 

            var build = buildService.GetBuildDefinition(_projectName, buildName);

 

            if (build == null)

                throw new BuildDefinitionDoesNotExistException(string.Format("The Build '{0}' can't be found", buildName));

 

            return buildService.GetBuild(build.LastBuildUri);

        }

    }

}


The IBuildDetail will provide me with information such as who requested the build (RequestedBy) and the status of the build (Status).

 

The Web API

I created a simple ASP.Net Web API ApiController that will use the TfsBuildService class (mentioned above) to access a build, and returns information about the build. Here is the code of my Web API:

namespace TfsNotifierWebApi.Controllers

{

    using System;

    using System.Net;

    using System.Net.Http;

    using System.Web.Http;

 

    using TfsNotifierWebApi.Models;

 

    public class BuildController : ApiController

    {

        public const string PROJECT_NAME = "myProject";

        public const string TFS_SERVER_COLLECTION_URI = "http://<my server>:8080/tfs/<my collection>";

 

        public const string USER_NAME = "<username>";

        public const string USER_PASSWORD = "<password>";

        public const string USER_DOMAIN = "<domain>";

 

 

        public HttpResponseMessage Get(string buildName)

        {

            try

            {

                return Request.CreateResponse(HttpStatusCode.OK, GetBuildDetail(buildName));

            }

            catch (Exception e)

            {

                return Request.CreateResponse(HttpStatusCode.NotFound, new HttpError(e.Message));

            }

        }

 

        private static BuildDetail GetBuildDetail(string buildName)

        {

            var tfsBuildService = new TfsBuildService(

                                                    TFS_SERVER_COLLECTION_URI,

                                                    USER_DOMAIN,

                                                    USER_NAME,

                                                    USER_PASSWORD,

                                                    PROJECT_NAME);

 

            var lastBuild = tfsBuildService.GetLastBuildDetail(buildName);

 

            return new BuildDetail

            {

                RequestedBy = lastBuild.RequestedBy,

                Status = lastBuild.Status.ToString(),

                FinishTime = lastBuild.FinishTime,

                StartTime = lastBuild.StartTime

            };

        }

    }

}

 

IMPORTANT NOTE: The constants storing the information about accessing the TFS is only used in this code for demonstration purpose, NEVER store sensitive information in constants, it's a BAD idea when it comes to security.

The Web API will returns a HttpRespnseMessage with the content set to my custom class, BuildDefintion:

namespace TfsNotifierWebApi.Models

{

    using System;

 

    public class BuildDetail

    {

        public string RequestedBy { get; set; }

 

        public string Status { get; set; }

 

        public DateTime FinishTime { get; set; }

 

        public DateTime StartTime { get; set; }

    }

}

 

I think the Web API code will describe itself, so I will skip the details.

The Client Side

 

On the client side to call the Web API, I decided to use a simple WPF application. I use the HttpClient to access the Web API.

private dynamic GetBuildInfo(string buildName)

{

    var httpClient = new HttpClient();

 

    var result = httpClient.GetAsync("http://localhost:10927/api/build/" + buildName).Result;

    var buildDetail = result.Content.ReadAsStringAsync().Result;

 

    return JObject.Parse(buildDetail);

}

 

I use the Newsoft.Json lib to parse the JSON result returned from the Web API into a dynamic object, JObject.Parse.

The WPF application was made by KISS (Keep it Simple Stupid). I use a simple DispatcherTimer to poll against the Web API to see if the latest build status has changed.


using System;

using System.Net.Http;

using System.Windows;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using Newtonsoft.Json.Linq;

public MainWindow()

{

   WindowState = WindowState.Minimized;

 

   InitializeComponent();

 

   UpdateBuildStatus();

 

   var dispatcherTimer = new System.Windows.Threading.DispatcherTimer();

   dispatcherTimer.Tick += dispatcherTimer_Tick;

   dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 5);

   dispatcherTimer.Start();

}

 

void dispatcherTimer_Tick(object sender, EventArgs e)

{

   UpdateBuildStatus();

}

 

The UpdateBuildStatus method will update the client with red color if the build has failed, and yellow if partially succeeded. It will also try to get an image from an Images folder of the user started the build.

private void UpdateBuildStatus()

{

   var result = GetBuildInfo("Test - Nightly Build");

 

    requestByTextBlock.Text = result.RequestedBy;

    requestByImage.Source = new BitmapImage(new Uri("Images/" + result.RequestedBy + ".jpg", UriKind.Relative));

 

    var status = result.Status;

 

    switch ((string)status)

    {

        case "Failed":

             SetBackgroundColor(237, 28, 36);

             Activate();

             WindowState = WindowState.Maximized;

             break;

        case "PartiallySucceeded":

             SetBackgroundColor(255, 201, 14);

             Activate();

             WindowState = WindowState.Maximized;

             break;

        default:

             WindowState = WindowState == WindowState.Maximized ? WindowState.Minimized : WindowState;

             SetBackgroundColor(195, 195, 195);

             break;

        }

    }
}


The SetBackgroundColor method is a helper method to set a Grid's background color.

private void SetBackgroundColor(byte r, byte g, byte b)

{

   mainGrid.Background = new SolidColorBrush(Color.FromRgb(r, g, b));

}

 

Here is the XAML is you are interested:

<Window x:Class="TfsNotifier.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="1000" Width="800">

    <Grid Name="mainGrid">

        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">

            <TextBlock FontSize="90px" FontWeight="ExtraBold" Name="requestByTextBlock"></TextBlock>

            <Image Stretch="None" Name="requestByImage" MaxHeight="500" MaxWidth="700"></Image>

        </StackPanel>

    </Grid>

</Window>

 

The last words


You may wonder why I have created a Web API instead of using the TFS Api directly from the WPF. By using the Web API I can also create a Windows 8 app or a ASP.Net Single Page Application (SPA) as clients etc.

If you want to know when I have published a blog post, then feel free to follow me on twitter: @fredrikn

2 Comments

  • Nice example, but would have been much better without the client polling.

    Why not subscribe to the events on the TeamProjectCollection that you're interested in (e.g. BuildCompletionEvent, BuildStatusChangedEvent)? You could do this directly from the client if you wish or if you prefer to host this on a server for the reasons you state, then you might want to use SignalR to distribute the interesting events to subscribed clients.

  • Nigel Page: Oh, interesting idea, thanks! Never thought about SingalR, can be a nice blog post about it :)

Comments have been disabled for this content.