TeamCity integration with C# and REST API

TeamCity integration with C# and REST API

As I mentioned in my previous Get Noticed post. TeamScreen first integration will be with TeamCity. For those who don’t know TeamCity is a continuous integration tool created by awesome guys from JetBrains company that brought you Resharper, IntelliJ, WebSharper and many other great tools.

What I want from TeamCity is a list of builds and statuses for them. A quick search in google gave me this page, which describes how to connect to TeamCity and get an info I need using REST API.

A little digression here – I personally love all REST stuff. it’s simplicity and creation speed makes it 100 times better than SOAP and WCF in my opinion. I’m still remembering the pain I needed to go through to just configure those things. REST I believe really allowed to easy integration various services regardless of their technology stack. Do you think we would have things like https://ifttt.com/ available without it?

Ok, so we know that we’ll consume some kind of the REST API. How to do it from .NET Core? Normally I would use RESTSharp for it, which I‘ve done in previous projects and I had no problems with it, but big “HELP Wanted” on its github page and lack of official .Net Core support forced me to reconsider. Of course, you can use standard HttpClient class, but in my opinion, it’s a mundane task.

After a quick search in google, I came across two interesting libraries – Refit and RestEase. I had a tough nut to crack to choose between them because they’re pretty similar. In the end, I chose RestEase because of the Response<T> class that it can return, which can come in handy in future, when we’ll discuss more functional approach.

Next thing to handle when it comes to accessing REST API is authentication. TeamCity has three different types of it. The best for this moment I believe is basic HTTP authentication – you just need to add httpAuth in URL and credentials in HTTP header and you’re good to go.

Builds itself in TeamCity are grouped in projects and have a list of last build statuses, so I’ll create two functions – one to download projects and second to consequently download a list of builds with last status for those projects.

Ok, let’s start coding. First, we need an interface for rest client, which we could use with RestEase:

using System.Net.Http.Headers;
using System.Threading.Tasks;
using RestEase;

namespace TeamScreen.TeamCity
{
    [Header("Accept", "application/json")]
    public interface ITeamCityClient
    {
        [Header("Authorization")]
        AuthenticationHeaderValue Authorization { get; set; }

        [Get("/httpAuth/app/rest/projects")]
        Task<GetProjectsResponse> GetProjectsAsync();

        [Get("/httpAuth/app/rest/buildTypes?locator=affectedProject:(id:{projectId})&fields=buildType(id,name,project,builds($locator(running:false,canceled:false,count:1),build(number,status,statusText)))")]
        Task<GetBuildsResponse> GetBuildsWithStatusesAsync([Path]string projectId);
    }
}

As I mentioned before we have two methods – one for projects and one for builds marked with Get attribute representing HTTP address to connect. As you can see I added httpAuth to URLs for authentication purposes. Important elements in this class are also:

  • [Path] attribute on projectId parameter to replace it in URL
  • Authentication property in which I’ll pass encoded credentials
  • Accept header to get JSON instead of default XML response from TeamCity

ITeamCityClient interface is later used in TeamCityService which creates an instance of it using RestClient class, adds encoded credentials and downloads projects and their corresponding builds. I filter out Root project – it’s a container for all other projects and I wanted to use projects itself to have more flexibility later.

using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using RestEase;

namespace TeamScreen.TeamCity
{
    public interface ITeamCityService
    {
        Task<BuildJob[]> GetBuilds(string path, string username, string password);
    }

    public class TeamCityService : ITeamCityService
    {
        private const string RootProject = "_Root";

        public async Task<BuildJob[]> GetBuilds(string path, string username, string password)
        {
            var api = RestClient.For<ITeamCityClient>(path);
            var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
            api.Authorization = new AuthenticationHeaderValue("Basic", credentials);

            var projects = await api.GetProjectsAsync();
            return projects.Projects
                .Where(x => x.Id != RootProject)
                .SelectMany(x => api.GetBuildsWithStatusesAsync(x.Id).Result.BuildJobs)
                .ToArray();
        }
    }
}

Next step is a controller which uses build in ASP.NET Core Dependency Injection container to get an instance of TeamCityService to download build’s list using TeamCityService, which of course need to be registered before.

using Microsoft.AspNetCore.Mvc;
using TeamScreen.TeamCity;
using System.Threading.Tasks;

namespace TeamScreen.Controllers
{
    public class TeamCityController : Controller
    {
        private readonly ITeamCityService _teamCityService;

        public TeamCityController(ITeamCityService teamCityService)
        {
            _teamCityService = teamCityService;
        }

        public async Task<IActionResult> Index()
        {
            var builds = await _teamCityService.GetBuilds("","", "");
            return View(builds);
        }
    }
}

And last but not least razor view to display actual build’s list colored based on status:

@using TeamScreen.TeamCity
@model IEnumerable<BuildJob>

<h1>TeamCity status</h1>

@foreach (var build in Model)
{
    if (build.BuildCollection.Builds.FirstOrDefault().Status == BuildStatus.Success)
    {
        <h2 style="color: green">@build.Project.Name - @build.Name</h2>
    }
    else
    {
        <h2 style="color: red">@build.Project.Name - @build.Name</h2>
    }
}

All of that gives us this finished page:

You can find full code here. Right now it has a lot of rough edges, but it’s a start. For now thanks for reading and see you next time 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *