Get started by using ASP.Net Web API and NancyFx with OWIN/Katana

Many of you may have probably already heard about OWIN (Open Web Interface for .Net) and Katana that implements OWIN and use different components for supporting OWIN, for example System.Web and System.Net.HttpListener. If you haven’t heard about OWIN or Katana before, please read the following: OWIN and Katana.

This blog post will be about using Katana and how to setup the use of ASP.Net Web API and NancyFx.

One way to begin with using Katana is by installing the Katana Tooling. Download the tooling and unzip the downloaded zip file. Then install the Katana.vsix. The file includes Microsoft Visual Studio 2012 templates for Katana Web Applications and Katana Console Applications.

When the installation is completed you can start Visual Studio 2012. Create a New Project and select Templates, Visual C#. You will not see two Katana projects, the Katana Console Application and the Katana Web Application:

The Katana Console Application will use the HttpListener and the Katana Web Application will use System.Web (“ASP.Net”). By using the Katana Web Application, you can use the web.config file, global.asax etc, it is more or less a simple ASP.Net project. The Katana Console Application is more of a lightweight host that don’t take advantage of ASP.Net.

Select the Katana Console Application, name it to what you like. I will use the default suggested name.

The Katana Console Application creates two .cs files, Program.cs and Statup.cs. The Program.cs file looks like this:

[[code]]czoyNzpcIjxzcGFuPmNsYXNzIFByb2dyYW0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo0OTpcIjxzcGFuPiAgICBzdGF0aWMgdm9pZCBNYWluKHN0cmluZ1tdIGFyZ3MpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2MTpcIjxzcGFuPiAgICAgICAgc3RyaW5nIHVyaSA9IFwiaHR0cDovL2xvY2FsaG9zdDoxMjM0NS9cIjsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2MjpcIjxzcGFuPiAgICAgICAgdXNpbmcgKFdlYkFwcC5TdGFydCZsdDtTdGFydHVwJmd0Oyh1cmkpKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgewo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo1NDpcIjxzcGFuPiAgICAgICAgICAgQ29uc29sZS5Xcml0ZUxpbmUoXCJTdGFydGVkXCIpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo0NDpcIjxzcGFuPiAgICAgICAgICAgLy8gT3BlbiB0aGUgYnJvd3Nlcgo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0NDpcIjxzcGFuPiAgICAgICAgICAgUHJvY2Vzcy5TdGFydCh1cmkpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo2MDpcIjxzcGFuPiAgICAgICAgICAgLy8gUHJlc3MgYW55IGtleSB0byBzdG9wIHRoZSBzZXJ2ZXIKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo0MzpcIjxzcGFuPiAgICAgICAgICAgQ29uc29sZS5SZWFkS2V5KCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo1NTpcIjxzcGFuPiAgICAgICAgICAgQ29uc29sZS5Xcml0ZUxpbmUoXCJTdG9wcGluZ1wiKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

The WeApp.Start<Statup>(uri) will be used to start listening for incoming request to the specified URI, in this case the http://localhost:12345. The generic Start method will make sure the Startup.cs class will be used during the startup. The Startup class will be used for configuration, more about this later. The Process.Start will simply start a new process, in this case when a URI is specified, your default Internet browser will be opened when the Console application is started.

The Startup.cs file is used for configuration, for example specify which middleware that should be used. A Middleware is the modules that an OWIN host will use, so instead of a normal IIS pipeline with several modules chained after each other, you can add the modules you prefer to use instead. It gives you more freedom to just add those you really needs. Here is the Startup.cs file created by the Katana Console Application template:

[[code]]czo0NjpcIjxicj48c3Bhbj5wdWJsaWMgcGFydGlhbCBjbGFzcyBTdGFydHVwCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2MDpcIjxzcGFuPiAgICBwdWJsaWMgdm9pZCBDb25maWd1cmF0aW9uKElBcHBCdWlsZGVyIGFwcCkKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMzpcIjxzcGFuPiNpZiBERUJVRwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0MDpcIjxzcGFuPiAgICAgICBhcHAuVXNlRXJyb3JQYWdlKCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMDpcIjxzcGFuPiNlbmRpZgo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0NTpcIjxzcGFuPiAgICAgICBhcHAuVXNlV2VsY29tZVBhZ2UoXCIvXCIpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxODpcIjxzcGFuPn08L3NwYW4+PGJyPlwiO3tbJiomXX0=[[/code]]

The Configuration method takes an IAppBuilder as an argument. The IAppBuilder is used to register OWIN middlewares. The UseErrorPage and UseWelcomePage methods are extension methods to the IAppBuilder, they are used to add the use of an error page and simply shows a welcome page when the application is started. It’s a common pattern to add an extension method to the IAppBuilder. You can remove the lines inside of the Configuration method because we aren’t going to use them.

Using ASP.Net Web API with OWIN/Katana

 

To use ASP.Net Web API together with OWIN/Katana you need to install ASP.Net Web API OWIN NuGet package. Enter:

[[code]]czo3NjpcIjxicj48c3Bhbj5JbnN0YWxsLVBhY2thZ2UgTWljcm9zb2Z0LkFzcE5ldC5XZWJBcGkuT3dpbiAmbmRhc2g7cHJlPC9zcGF7WyYqJl19bj48YnI+XCI7e1smKiZdfQ==[[/code]]

In the Package Manager Console in Visual Studio 2012 and then press enter to install the ASP.Net Web API.

Note: You can instead of downloading Katana, simply create an empty Console Application and in the Package Manager Console write: Install-Package Microsoft.AspNet.WebApi.OwinSelfHost –Pre, you can read more about it here.

The next step is to configure the use of ASP.Net Web API. In the Configuration method of the Startup.cs file add the following lines of code:

[[code]]czo1MTpcIjxzcGFuPnZhciBjb25maWcgPSBuZXcgSHR0cENvbmZpZ3VyYXRpb24oKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo0MTpcIjxzcGFuPmNvbmZpZy5Sb3V0ZXMuTWFwSHR0cFJvdXRlKAo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0NjpcIjxzcGFuPiAgICAgICAgICAgICBuYW1lOiBcIkRlZmF1bHRBcGlcIiwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2NjpcIjxzcGFuPiAgICAgICAgICAgICByb3V0ZVRlbXBsYXRlOiBcImFwaS97Y29udHJvbGxlcn0ve2lkfVwiLAo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo3MzpcIjxzcGFuPiAgICAgICAgICAgICBkZWZhdWx0czogbmV3IHsgaWQgPSBSb3V0ZVBhcmFtZXRlci5PcHRpb25hbCB9Cjwvc3B7WyYqJl19YW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyNTpcIjxzcGFuPiAgICAgICAgICk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czozNzpcIjxzcGFuPmFwcC5Vc2VXZWJBcGkoY29uZmlnKTsgCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

The HttpConfiguration class is used to setup the ASP.Net Web API routes. To use the ASP.Net Web API the IAppBuilder extension method UseWebApi is used. The UseWebApi takes HttpConfiguration as an argument. This is all you need to do to set up the use of the ASP.Net Web API. The next step is to add a simple ApiController.

Add a new class, DefaultController, make sure it inherits the System.Web.Http.ApiController class.

[[code]]czo2MDpcIjxzcGFuPnB1YmxpYyBjbGFzcyBEZWZhdWx0Q29udHJvbGxlciA6IEFwaUNvbnRyb2xsZXIKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Add a Get method that simply returns “Hello World”.

[[code]]czo2MDpcIjxzcGFuPnB1YmxpYyBjbGFzcyBEZWZhdWx0Q29udHJvbGxlciA6IEFwaUNvbnRyb2xsZXIKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozNzpcIjxzcGFuPiAgICBwdWJsaWMgc3RyaW5nIEdldCgpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo0MzpcIjxzcGFuPiAgICAgICByZXR1cm4gXCJIZWxsbyBXb3JsZCFcIjsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Run the application and in your browser enter: http://localhost:12345/api/Default and you should now get a XML result, something like this:

[[code]]czoxMTU6XCI8c3Bhbj4mbHQ7c3RyaW5nIHhtbG5zPVwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6e1smKiZdfWF0aW9uL1wiJmd0O0hlbGxvIFdvcmxkISZsdDsvc3RyaW5nJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

You have now created a simple OWIN Host using Katana to host a simple ASP.Net Web API application.

Using NancyFx with OWIN/Katana

 

To use NancyFx you need to install the install the following NuGet package:

[[code]]czo0MzpcIjxzcGFuPkluc3RhbGwtUGFja2FnZSBOYW5jeS5Pd2luPC9zcGFuPjxicj5cIjt7WyYqJl19[[/code]]

Open the Startup.cs file and change the Configuration method to the following code:

 

[[code]]czo1NjpcIjxzcGFuPnB1YmxpYyB2b2lkIENvbmZpZ3VyYXRpb24oSUFwcEJ1aWxkZXIgYXBwKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMzpcIjxzcGFuPiAgICBhcHAuVXNlTmFuY3koKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

This is all you need to specify the use of NancyFx, no need to register routes etc. like you did when using the ASP.Net Web API. The next thing to do is to add a Get “action” method. Create a new class and make sure it inherits the Nancy.NancyModule class:

 

[[code]]czo1NDpcIjxzcGFuPnB1YmxpYyBjbGFzcyBEZWZhdWx0TW9kdWxlIDogTmFuY3lNb2R1bGUKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxODpcIjxzcGFuPn08L3NwYW4+PGJyPlwiO3tbJiomXX0=[[/code]]

 

Add a constructor and then use the Get property to specify a route and the lambda expression that should run when the route is accessed by a HTTP GET method, for example returning “Hello World!”:

[[code]]czo1ODpcIjxzcGFuPjxicj5wdWJsaWMgY2xhc3MgRGVmYXVsdE1vZHVsZSA6IE5hbmN5TW9kdWxlCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozOTpcIjxzcGFuPiAgIHB1YmxpYyBEZWZhdWx0TW9kdWxlKCkKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxODpcIjxzcGFuPiAgIHsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo1NDpcIjxzcGFuPiAgICAgIEdldFtcIi9cIl0gPSBfID0mZ3Q7IFwiSGVsbG8gV29ybGQhXCI7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxODpcIjxzcGFuPiAgIH0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

Run the application and in your browser enter: http://localhost:12345/ and you should now see “Hello World!” in the browser.

You have now created a simple OWIN Host using Katana to host a simple NancyFx application.

Summary

 

In this blog post you have learned about how easy it is to create a lightweight host to host either ASP.Net Web API or NancyFx by using OWIN and Katana.

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

Read More

Introducing Polymelia Deploy

During the last month I have created different deployment tools, as a proof of concepts. The tools have change from push deploy into pull deploy, from an own XML workflow and environment definition into using Microsoft Workflow. Finally I decided to introduce to you the Polymelia Deploy tool. The goal of the tool is to make it open source. The code I have is still in a proof of concept level, and need some more work until it will be available.

Polymelia uses agents installed on servers. By using pull deployment, no one can communicate directly to an agent. This make it much easier for to install agents on servers and no ports need to be opened. Each agents have a role. For example a role as a “Web Server”, or “Application Server”. When an agent is running it will ask a Controller for tasks to be executed.

 

Because agents has roles and Polymelia uses Pull deploy, we can now add a new server, put an agent on the server, specify the role, for example “Web Server”. When the server is up and running the agent can ask a controller for tasks. The latest succeeded tasks will be retrieved and executed. That makes it easy to just add a new server to a load balancing environment and get it auto configured and installed when it’s up and running. No need to do a push deploy, or do changes to the deploy script.

 

In a near future the agents will be able to be scheduled, when and how often it should ask for a task. The agents will also use SingalR, and by using SingalR, a controller can know when a new agent is added to the environment, and by suing Polymelia Deploy Client, we are able to approve that agent before it can ask for a tasks. Some ideas on the list to do, is to be able to specify an IP range for auto allow new agents without needing to approve them.

Polymelia have as the moment just a few activates (but will get more, maybe you will help me create them ;)), one activity is a NuGet Installation activity, it has a similar solution as Octopus Deploy. The activity will get binaries from an Artifact Repository using NuGet server.

 

 

The packages can have configuration files that can be transformed, variables that can replace connection string, appsettings keys and WCF endpoints, but will in a near future replace all kind of keys and values in the configuration file using markers in the config file:

<appSettings>
<add key=”$VariableName$” Value=”$variableName2″/>
</appSettings>

 

The NuGet Installation activity will also search for PowerShell scripts in the package, pass variables and execute the script. It will make it possible to use PowerShell to configure and install packages on a server. Because Polymelia is based on Microsoft Workflow, it’s possible to use pre-defined activities that will reduce the use of PowerShell, like creating a MSMQ, Install a Service, Create an app Pool, Run PowerShell script and Start a Virtual Machine etc.

Polymelia Deploy Client

 

Polymelia Deploy Client is the tool to create deployable workflows, and is used to perform the deployment of a specific version.

When Polymelia Deploy Client is loaded we can create or select a project:

 

When a project is created or loaded, we are able to add environments:

 

When the environment(s) are added we can start creating our deploy tasks. The following illustrate how we can tell the Controller to start a Virtual Machine, the Virtual Machine has an agent installed with the role “Web Server”. When the Virtual Machine is started a parallel activity is started and will execute two “Deploy to Agent” activities. One to the role “Web Server” and one for the role “Database”. The tasks added into the “Deploy to Agent” are the tasks that the Controller will add to a queue. The “Web Server” role will read from the queue to execute the tasks added for that role. The “Web Server” will get two packages from a NuGet server and install them on the server, this is done in parallel.

 

When hitting the DEPLOY button, we need to specify the version we are going to deploy, and the deploy workflow will then be passed to the Controller for execution. When the agents is starting to install tasks, they reports back to the Controller and the client can read from the reports.

This project is still under development. If you are interested to contribute to this project, please let me know. The reason I make this tool Open Source, is to get help of the community to build it, and also give an open source option for the community to use.

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

Read More

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

In my previous blog post I wrote about how to get information about a build, the problem with that code was that the code only returned the user who requested the build, not the user who had checked-in a changeset that failed the build. So this blog post will cover that part.

The VersionControlServer class in the assembly Microsoft.TeamFoundation.VersionControl.Client can be used to get Changesets from a specific branch. To get access to the VersionControlServer I use the TfsTeamPorjectCollection‘s GetService<T> method, where T is set to VersionControlServer. To query a branch changeset history, the QueryHistory method of the VersionControlServer can be used. The QueryHistory method needs the path to the branch and other query parameters to get changessets. To get the path, I created a helper method (GetFirstServerItemFromBuild, it will get the path from the BuildDefinition:

[[code]]czozNDI6XCI8c3Bhbj4gICAgICAgIHByaXZhdGUgc3RyaW5nIEdldEZpcnN0U2VydmVySXRlbUZyb21CdWlsZChzdHJpbmcgYnVpbGR7WyYqJl19TmFtZSk8YnI+ICAgICAgICB7PGJyPiAgICAgICAgICAgIHZhciBidWlsZFNlcnZpY2UgPSBfdGVhbVByb2plY3RDb2xsZWN0aW9uLntbJiomXX1HZXRTZXJ2aWNlJmx0O0lCdWlsZFNlcnZlciZndDsoKTs8YnI+ICAgICAgICAgICAgdmFyIGJ1aWxkID0gR2V0QnVpbGREZWZpbml0e1smKiZdfWlvbihidWlsZE5hbWUsIGJ1aWxkU2VydmljZSk7PGJyPjxicj4gICAgICAgICAgICByZXR1cm4gYnVpbGQuV29ya3NwYWNlLk1hcHB7WyYqJl19aW5ncy5GaXJzdCgpLlNlcnZlckl0ZW07PGJyPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

When setting up a build in TFS, we need to specify Working folders under the Workspace settings, in my case the first working folder has the path to the branch.

By using the BuildDefintion‘s Workspace and its Mappings property, I can get the first item from the Working folders. The ServerItem property will return the Source Control Folder.

To get the most possible ChangeSet that may cause the build to fail I specify a “from date” (passed as an argument to a helper method, more about that later) and a “to date” to the QueryHistory method. The “from date” will be three weeks back in time from when the latest build was started, and the “to date” will be when the latest build was started. I will then take the First ChangeSet from the QueryHistory’s result. QueryHistory don’t take a DateTime as argument for the dates, instead a VersionSpec class, so I created a helper method that will parse a DateTime to a VersionSpec.

[[code]]czozNzk6XCI8c3Bhbj4gICAgICAgIHByaXZhdGUgc3RhdGljIFZlcnNpb25TcGVjIENyZWF0ZURhdGVWU3BlYyhEYXRlVGltZSBkYXR7WyYqJl19ZSk8YnI+ICAgICAgICB7PGJyPiAgICAgICAgICAgIC8vRm9ybWF0IGlzOiAgRDIwMDktMTEtMTZUMTQ6MzI8YnI+ICAgICAgICAgIHtbJiomXX0gIHJldHVybiBWZXJzaW9uU3BlYy5QYXJzZVNpbmdsZVNwZWMoPGJyPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge1smKiZdfSAgICAgICAgICAgIHN0cmluZy5Gb3JtYXQoXCJEezA6eXl5fS17MDpNTX0tezA6ZGR9VHswOkhIfTp7MDptbX1cIiwgZGF0ZSksPGJyPntbJiomXX0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZy5FbXB0eSk7PGJyPiAgICAgICAgfQo8e1smKiZdfS9zcGFuPlwiO3tbJiomXX0=[[/code]]

I created a helper method added to my TfsBuidService class for helping me to get the latest change set, here is the code for the helper method.


[[code]]czoxOTc6XCI8c3Bhbj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDtwdWJsaWMgQ2hhbmdlc2V0IEdldExhdGVzdENoYW5nZXNldChEYXR7WyYqJl19ZVRpbWUgdmVyc2lvblRvZGF0ZSwgc3RyaW5nIGJ1aWxkTmFtZSk8YnI+ICAgICAgezxicj4gICAgICAgICAgICB2YXIgcGF0aCA9IHtbJiomXX10aGlzLkdldEZpcnN0U2VydmVySXRlbUZyb21CdWlsZChidWlsZE5hbWUpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0OTE6XCI8c3Bhbj4gICAgICAgICAgICB2YXIgdmNzID0gX3RlYW1Qcm9qZWN0Q29sbGVjdGlvbi5HZXRTZXJ2aWNlJmx0O1ZlcnN7WyYqJl19aW9uQ29udHJvbFNlcnZlciZndDsoKTs8YnI+PGJyPiAgICAgICAgICAgIHZhciB2ZXJzaW9uRnJvbSA9IENyZWF0ZURhdGVWU3BlY3tbJiomXX0odmVyc2lvblRvZGF0ZS5BZGREYXlzKC0yMSkpOzxicj4gICAgICAgICAgICB2YXIgdmVyc2lvblRvID0gQ3JlYXRlRGF0ZVZTcGVje1smKiZdfSh2ZXJzaW9uVG9kYXRlKTs8YnI+PGJyPiAgICAgICAgICAgIHZhciByZXN1bHRzID0gdmNzLlF1ZXJ5SGlzdG9yeShwYXRoLCBWZXJ7WyYqJl19c2lvblNwZWMuTGF0ZXN0LCAwLCBSZWN1cnNpb25UeXBlLkZ1bGwsIG51bGwsIHZlcnNpb25Gcm9tLCB2ZXJzaW9uVG8sIGludC5NYXtbJiomXX14VmFsdWUsIGZhbHNlLCB0cnVlKTs8YnI+PGJyPiAgICAgICAgICAgIHJldHVybiByZXN1bHRzLkNhc3QmbHQ7Q2hhbmdlc2V0Jmd0e1smKiZdfTsoKS5GaXJzdE9yRGVmYXVsdCgpOzxicj4gICAgICAgIH0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

I made some changes to my Web API, so it takes information from the latest change set:

 

[[code]]czoxMzY1OlwiPHNwYW4+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7cHJpdmF0ZSBzdGF0aWMgQnVpbGREZXRhaWwgR2V0QnVpbGREZXR7WyYqJl19YWlsKHN0cmluZyBidWlsZE5hbWUpPGJyPiAgICAgIHs8YnI+ICAgICAgICAgIHZhciB0ZnNCdWlsZFNlcnZpY2UgPSBuZXcgVGZzQntbJiomXX11aWxkU2VydmljZSg8YnI+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRGU19TRVJWe1smKiZdfUVSX0NPTExFQ1RJT05fVVJJLDxicj4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVVN7WyYqJl19RVJfRE9NQUlOLDxicj4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVVNFUl9OQU1FLHtbJiomXX08YnI+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVTRVJfUEFTU1dPUkQsPGJyPiAge1smKiZdfSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUFJPSkVDVF9OQU1FKTs8YnI+PGJyPiAgICB7WyYqJl19ICAgICAgIHZhciBsYXN0QnVpbGQgPSB0ZnNCdWlsZFNlcnZpY2UuR2V0TGFzdEJ1aWxkRGV0YWlsKGJ1aWxkTmFtZSk7PGJyPiAgIHtbJiomXX0gICAgICAgIHZhciBjaGFuZ2VTZXQgPSB0ZnNCdWlsZFNlcnZpY2UuR2V0TGF0ZXN0Q2hhbmdlc2V0KGxhc3RCdWlsZC5TdGFydFRpe1smKiZdfW1lLCBidWlsZE5hbWUpOzxicj48YnI+ICAgICAgICAgICByZXR1cm4gbmV3IEJ1aWxkRGV0YWlsPGJyPiAgICAgICAgICAgezxicj57WyYqJl19ICAgICAgICAgICAgICAgUmVxdWVzdGVkQnkgPSBsYXN0QnVpbGQuUmVxdWVzdGVkQnksPGJyPiAgICAgICAgICAgICAgIFN0YXR1c3tbJiomXX0gPSBsYXN0QnVpbGQuU3RhdHVzLlRvU3RyaW5nKCksPGJyPiAgICAgICAgICAgICAgIEZpbmlzaFRpbWUgPSBsYXN0QnVpbGQuRmlue1smKiZdfWlzaFRpbWUsPGJyPiAgICAgICAgICAgICAgIFN0YXJ0VGltZSA9IGxhc3RCdWlsZC5TdGFydFRpbWUsPGJyPiAgICAgICAgICAgICB7WyYqJl19ICBMYXN0Q2hlY2tlZEluVXNlciA9IGNoYW5nZVNldCAhPSBudWxsID8gY2hhbmdlU2V0Lk93bmVyRGlzcGxheU5hbWUgOiBsYXN0QntbJiomXX11aWxkLlJlcXVlc3RlZEJ5LDxicj4gICAgICAgICAgICAgICBDaGFuZ2VTZXRDb21tZW50ID0gY2hhbmdlU2V0ICE9IG51bGwgPyBje1smKiZdfWhhbmdlU2V0LkNvbW1lbnQgOiBzdHJpbmcuRW1wdHksPGJyPiAgICAgICAgICAgICAgIENoYW5nZVNldElkID0gY2hhbmdlU2V0ICF7WyYqJl19PSBudWxsID8gY2hhbmdlU2V0LkNoYW5nZXNldElkIDogLTEsPGJyPiAgICAgICAgICAgICAgIFdvcmtJdGVtVGl0bGUgPSB0ZnNCdXtbJiomXX1pbGRTZXJ2aWNlLkdldExhdGVzdFdvcmtJdGVtVGl0bGUoY2hhbmdlU2V0KTxicj4gICAgICAgICAgIH07PGJyPiAgICAgICB9Cjwve1smKiZdfXNwYW4+XCI7e1smKiZdfQ==[[/code]]

The resource returned from the Web API will now also include extra information, such as the last work item associated to the last changeset, changeset’s id, changeset’s comment by the user who check-in the code and the last user who checked-in the code. To get the latest changeset, the last build’s StartTime is used to query the build’s branch for the latest changeset.

To get the latest work item associated to the changeset, I created a helper method that takes the ChangeSet as an argument, just to extract the Work item title:

 

[[code]]czozOTU6XCI8c3Bhbj4gICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwO3B1YmxpYyBzdHJpbmcgR2V0TGF0ZXN0V29ya0l0ZW1UaXRsZSh7WyYqJl19Q2hhbmdlc2V0IGNoYW5nZVNldCk8YnI+ICAgICAgezxicj4gICAgICAgICAgaWYgKGNoYW5nZVNldCA9PSBudWxsKTxicj4gICAgIHtbJiomXX0gICAgICAgICByZXR1cm4gc3RyaW5nLkVtcHR5Ozxicj48YnI+ICAgICAgICAgIGlmIChjaGFuZ2VTZXQuQXNzb2NpYXRlZFdvcmtJe1smKiZdfXRlbXMgPT0gbnVsbCB8fCAhY2hhbmdlU2V0LkFzc29jaWF0ZWRXb3JrSXRlbXMuQW55KCkpPGJyPiAgICAgICAgICAgICAgcmV0dXJ7WyYqJl19biBzdHJpbmcuRW1wdHk7PGJyPjxicj4gICAgICAgICAgcmV0dXJuIGNoYW5nZVNldC5Bc3NvY2lhdGVkV29ya0l0ZW1zWzBdLlRpdHtbJiomXX1sZTs8YnI+ICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

Even if the TFS API for 2012 had lack of example on MSDN, it was quite easy to understand how to use them.

I hope some of you may found this blog post useful.

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

Read More

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.

[[code]]czo3MjpcIjxicj48c3Bhbj5fdGVhbVByb2plY3RDb2xsZWN0aW9uID0gbmV3IFRmc1RlYW1Qcm9qZWN0Q29sbGVjdGlvbigKPC9zcGF7WyYqJl19bj5cIjt7WyYqJl19[[/code]]

[[code]]czo4MTpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ldyBVcmkodXJ7WyYqJl19aSksCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxMTk6XCI8c3Bhbj4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXcgTmV0d297WyYqJl19cmtDcmVkZW50aWFsKHVzZXJuYW1lLCBwYXNzd29yZCwgZG9tYWluKSk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

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.

[[code]]czo4OTpcIjxzcGFuPnZhciBidWlsZFNlcnZpY2UgPSBfdGVhbVByb2plY3RDb2xsZWN0aW9uLkdldFNlcnZpY2UmbHQ7SUJ1aWxkU2V7WyYqJl19cnZlciZndDsoKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo4MzpcIjxzcGFuPnZhciBidWlsZCA9IGJ1aWxkU2VydmljZS5HZXRCdWlsZERlZmluaXRpb24oX3Byb2plY3ROYW1lLCBidWlsZE57WyYqJl19YW1lKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czozMjpcIjxzcGFuPmlmIChidWlsZCA9PSBudWxsKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxMjQ6XCI8c3Bhbj4gICB0aHJvdyBuZXcgQnVpbGREZWZpbml0aW9uRG9lc05vdEV4aXN0RXhjZXB0aW9uKHN0cmluZy5Gb3JtYXR7WyYqJl19KFwiVGhlIEJ1aWxkIFwnezB9XCcgY2FuXCd0IGJlIGZvdW5kXCIsIGJ1aWxkTmFtZSkpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

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.

[[code]]czo2MzpcIjxzcGFuPnJldHVybiBidWlsZFNlcnZpY2UuR2V0QnVpbGQoYnVpbGQuTGFzdEJ1aWxkVXJpKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

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):

 

[[code]]czo0ODpcIjxzcGFuPm5hbWVzcGFjZSBUZnNOb3RpZmllcldlYkFwaS5Nb2RlbHMKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW07Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czozNTpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW0uTmV0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo2MjpcIjxzcGFuPiAgICB1c2luZyBNaWNyb3NvZnQuVGVhbUZvdW5kYXRpb24uQnVpbGQuQ2xpZW50Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo1NjpcIjxzcGFuPiAgICB1c2luZyBNaWNyb3NvZnQuVGVhbUZvdW5kYXRpb24uQ2xpZW50Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo2NTpcIjxzcGFuPiAgICBwdWJsaWMgY2xhc3MgVGZzQnVpbGRTZXJ2aWNlIDogSVRmc0J1aWxkU2VydmljZQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo1OTpcIjxzcGFuPiAgICAgICAgcHJpdmF0ZSByZWFkb25seSBzdHJpbmcgX3Byb2plY3ROYW1lOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo3ODpcIjxzcGFuPiAgICAgICAgcHJpdmF0ZSBUZnNUZWFtUHJvamVjdENvbGxlY3Rpb24gX3RlYW1Qcm9qZWN0Q29sbGVjdGlvbjt7WyYqJl19Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czoxMjU6XCI8c3Bhbj4gICAgICAgIHB1YmxpYyBUZnNCdWlsZFNlcnZpY2Uoc3RyaW5nIHVyaSwgc3RyaW5nIGRvbWFpbiwgc3RyaW57WyYqJl19ZyB1c2VybmFtZSwgc3RyaW5nIHBhc3N3b3JkLCBzdHJpbmcgcHJvamVjdE5hbWUpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgewo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo1MzpcIjxzcGFuPiAgICAgICAgICAgIF9wcm9qZWN0TmFtZSA9IHByb2plY3ROYW1lOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo4MDpcIjxzcGFuPiAgICAgICAgICAgIF90ZWFtUHJvamVjdENvbGxlY3Rpb24gPSBuZXcgVGZzVGVhbVByb2plY3RDb2xsZWN0aW97WyYqJl19bigKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo5MTpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7WyYqJl19bmV3IFVyaSh1cmkpLAo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxMjk6XCI8c3Bhbj4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7WyYqJl19IG5ldyBOZXR3b3JrQ3JlZGVudGlhbCh1c2VybmFtZSwgcGFzc3dvcmQsIGRvbWFpbikpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

 

[[code]]czo3ODpcIjxzcGFuPiAgICAgICAgcHVibGljIElCdWlsZERldGFpbCBHZXRMYXN0QnVpbGREZXRhaWwoc3RyaW5nIGJ1aWxkTmFtZSl7WyYqJl19Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgewo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxMDE6XCI8c3Bhbj4gICAgICAgICAgICB2YXIgYnVpbGRTZXJ2aWNlID0gX3RlYW1Qcm9qZWN0Q29sbGVjdGlvbi5HZXRTZXJ2aWN7WyYqJl19ZSZsdDtJQnVpbGRTZXJ2ZXImZ3Q7KCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo5NTpcIjxzcGFuPiAgICAgICAgICAgIHZhciBidWlsZCA9IGJ1aWxkU2VydmljZS5HZXRCdWlsZERlZmluaXRpb24oX3Byb2plY3R7WyYqJl19TmFtZSwgYnVpbGROYW1lKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo0NDpcIjxzcGFuPiAgICAgICAgICAgIGlmIChidWlsZCA9PSBudWxsKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxMzc6XCI8c3Bhbj4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEJ1aWxkRGVmaW5pdGlvbkRvZXNOb3RFeGlzdEV4Y2VwdGlvbih7WyYqJl19c3RyaW5nLkZvcm1hdChcIlRoZSBCdWlsZCBcJ3swfVwnIGNhblwndCBiZSBmb3VuZFwiLCBidWlsZE5hbWUpKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo3NTpcIjxzcGFuPiAgICAgICAgICAgIHJldHVybiBidWlsZFNlcnZpY2UuR2V0QnVpbGQoYnVpbGQuTGFzdEJ1aWxkVXJpKTsKPC97WyYqJl19c3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

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:

[[code]]czo1MzpcIjxzcGFuPm5hbWVzcGFjZSBUZnNOb3RpZmllcldlYkFwaS5Db250cm9sbGVycwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW07Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czozNTpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW0uTmV0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0MDpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW0uTmV0Lkh0dHA7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo0MDpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW0uV2ViLkh0dHA7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo0OTpcIjxzcGFuPiAgICB1c2luZyBUZnNOb3RpZmllcldlYkFwaS5Nb2RlbHM7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo2MjpcIjxzcGFuPiAgICBwdWJsaWMgY2xhc3MgQnVpbGRDb250cm9sbGVyIDogQXBpQ29udHJvbGxlcgo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2OTpcIjxzcGFuPiAgICAgICAgcHVibGljIGNvbnN0IHN0cmluZyBQUk9KRUNUX05BTUUgPSBcIm15UHJvamVjdFwiOwo8L3NwYW4+XCJ7WyYqJl19O3tbJiomXX0=[[/code]]

[[code]]czoxMjg6XCI8c3Bhbj4gICAgICAgIHB1YmxpYyBjb25zdCBzdHJpbmcgVEZTX1NFUlZFUl9DT0xMRUNUSU9OX1VSSSA9IFwiaHR0cDove1smKiZdfS8mbHQ7bXkgc2VydmVyJmd0Ozo4MDgwL3Rmcy8mbHQ7bXkgY29sbGVjdGlvbiZndDtcIjsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo3MzpcIjxzcGFuPiAgICAgICAgcHVibGljIGNvbnN0IHN0cmluZyBVU0VSX05BTUUgPSBcIiZsdDt1c2VybmFtZSZndDtcIjsKPC9zcHtbJiomXX1hbj5cIjt7WyYqJl19[[/code]]

[[code]]czo3NzpcIjxzcGFuPiAgICAgICAgcHVibGljIGNvbnN0IHN0cmluZyBVU0VSX1BBU1NXT1JEID0gXCImbHQ7cGFzc3dvcmQmZ3Q7XCI7CntbJiomXX08L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo4NjpcIjxzcGFuPiAgICAgICAgcHVibGljIGNvbnN0IHN0cmluZyBVU0VSX0RPTUFJTiA9IFwiJmx0O2RvbWFpbjwvc3Bhbj4mZ3Q7e1smKiZdfTxzcGFuPlwiOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

 

[[code]]czo3MDpcIjxzcGFuPiAgICAgICAgcHVibGljIEh0dHBSZXNwb25zZU1lc3NhZ2UgR2V0KHN0cmluZyBidWlsZE5hbWUpCjwvc3Bhbj57WyYqJl19XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgewo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyOTpcIjxzcGFuPiAgICAgICAgICAgIHRyeQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyNzpcIjxzcGFuPiAgICAgICAgICAgIHsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxMDY6XCI8c3Bhbj4gICAgICAgICAgICAgICAgcmV0dXJuIFJlcXVlc3QuQ3JlYXRlUmVzcG9uc2UoSHR0cFN0YXR1c0NvZGUuT0t7WyYqJl19LCBHZXRCdWlsZERldGFpbChidWlsZE5hbWUpKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyNzpcIjxzcGFuPiAgICAgICAgICAgIH0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo0NTpcIjxzcGFuPiAgICAgICAgICAgIGNhdGNoIChFeGNlcHRpb24gZSkKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyNzpcIjxzcGFuPiAgICAgICAgICAgIHsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxMTE6XCI8c3Bhbj4gICAgICAgICAgICAgICAgcmV0dXJuIFJlcXVlc3QuQ3JlYXRlUmVzcG9uc2UoSHR0cFN0YXR1c0NvZGUuTm97WyYqJl19dEZvdW5kLCBuZXcgSHR0cEVycm9yKGUuTWVzc2FnZSkpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyNzpcIjxzcGFuPiAgICAgICAgICAgIH0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo4MTpcIjxzcGFuPiAgICAgICAgcHJpdmF0ZSBzdGF0aWMgQnVpbGREZXRhaWwgR2V0QnVpbGREZXRhaWwoc3RyaW5nIGJ1aWxkTmF7WyYqJl19bWUpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgewo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo2ODpcIjxzcGFuPiAgICAgICAgICAgIHZhciB0ZnNCdWlsZFNlcnZpY2UgPSBuZXcgVGZzQnVpbGRTZXJ2aWNlKAo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo5MjpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBURlNfU0VSVkVSX0N7WyYqJl19T0xMRUNUSU9OX1VSSSwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo3ODpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVU0VSX0RPTUFJTix7WyYqJl19Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo3NjpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVU0VSX05BTUUsCjx7WyYqJl19L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo4MDpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVU0VSX1BBU1NXT1J7WyYqJl19RCwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo4MDpcIjxzcGFuPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQUk9KRUNUX05BTUV7WyYqJl19KTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo4ODpcIjxzcGFuPiAgICAgICAgICAgIHZhciBsYXN0QnVpbGQgPSB0ZnNCdWlsZFNlcnZpY2UuR2V0TGFzdEJ1aWxkRGV0YWlsKGJ7WyYqJl19dWlsZE5hbWUpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo0ODpcIjxzcGFuPiAgICAgICAgICAgIHJldHVybiBuZXcgQnVpbGREZXRhaWwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyNzpcIjxzcGFuPiAgICAgICAgICAgIHsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2NjpcIjxzcGFuPiAgICAgICAgICAgICAgICBSZXF1ZXN0ZWRCeSA9IGxhc3RCdWlsZC5SZXF1ZXN0ZWRCeSwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2NzpcIjxzcGFuPiAgICAgICAgICAgICAgICBTdGF0dXMgPSBsYXN0QnVpbGQuU3RhdHVzLlRvU3RyaW5nKCksCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2NDpcIjxzcGFuPiAgICAgICAgICAgICAgICBGaW5pc2hUaW1lID0gbGFzdEJ1aWxkLkZpbmlzaFRpbWUsCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2MTpcIjxzcGFuPiAgICAgICAgICAgICAgICBTdGFydFRpbWUgPSBsYXN0QnVpbGQuU3RhcnRUaW1lCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyODpcIjxzcGFuPiAgICAgICAgICAgIH07Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

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:

[[code]]czo0ODpcIjxzcGFuPm5hbWVzcGFjZSBUZnNOb3RpZmllcldlYkFwaS5Nb2RlbHMKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICB1c2luZyBTeXN0ZW07Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo0MjpcIjxzcGFuPiAgICBwdWJsaWMgY2xhc3MgQnVpbGREZXRhaWwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2MTpcIjxzcGFuPiAgICAgICAgcHVibGljIHN0cmluZyBSZXF1ZXN0ZWRCeSB7IGdldDsgc2V0OyB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo1NjpcIjxzcGFuPiAgICAgICAgcHVibGljIHN0cmluZyBTdGF0dXMgeyBnZXQ7IHNldDsgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo2MjpcIjxzcGFuPiAgICAgICAgcHVibGljIERhdGVUaW1lIEZpbmlzaFRpbWUgeyBnZXQ7IHNldDsgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo2MTpcIjxzcGFuPiAgICAgICAgcHVibGljIERhdGVUaW1lIFN0YXJ0VGltZSB7IGdldDsgc2V0OyB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

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.

[[code]]czo2MDpcIjxzcGFuPnByaXZhdGUgZHluYW1pYyBHZXRCdWlsZEluZm8oc3RyaW5nIGJ1aWxkTmFtZSkKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo1MjpcIjxzcGFuPiAgICB2YXIgaHR0cENsaWVudCA9IG5ldyBIdHRwQ2xpZW50KCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czoxMDc6XCI8c3Bhbj4gICAgdmFyIHJlc3VsdCA9IGh0dHBDbGllbnQuR2V0QXN5bmMoXCJodHRwOi8vbG9jYWxob3N0OjEwOTI3L2Fwe1smKiZdfWkvYnVpbGQvXCIgKyBidWlsZE5hbWUpLlJlc3VsdDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo3ODpcIjxzcGFuPiAgICB2YXIgYnVpbGREZXRhaWwgPSByZXN1bHQuQ29udGVudC5SZWFkQXNTdHJpbmdBc3luYygpLlJlc3VsdDt7WyYqJl19Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo1MjpcIjxzcGFuPiAgICByZXR1cm4gSk9iamVjdC5QYXJzZShidWlsZERldGFpbCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

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.

[[code]]czozMTpcIjxicj48c3Bhbj51c2luZyBTeXN0ZW07Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czozNjpcIjxzcGFuPnVzaW5nIFN5c3RlbS5OZXQuSHR0cDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozNTpcIjxzcGFuPnVzaW5nIFN5c3RlbS5XaW5kb3dzOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0MTpcIjxzcGFuPnVzaW5nIFN5c3RlbS5XaW5kb3dzLk1lZGlhOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0OTpcIjxzcGFuPnVzaW5nIFN5c3RlbS5XaW5kb3dzLk1lZGlhLkltYWdpbmc7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2ODpcIjxzcGFuPnVzaW5nIE5ld3RvbnNvZnQuSnNvbi5MaW5xOzxicj48YnI+cHVibGljIE1haW5XaW5kb3coKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo1MzpcIjxzcGFuPiAgIFdpbmRvd1N0YXRlID0gV2luZG93U3RhdGUuTWluaW1pemVkOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czozOTpcIjxzcGFuPiAgIEluaXRpYWxpemVDb21wb25lbnQoKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czozNzpcIjxzcGFuPiAgIFVwZGF0ZUJ1aWxkU3RhdHVzKCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

[[code]]czo4NjpcIjxzcGFuPiAgIHZhciBkaXNwYXRjaGVyVGltZXIgPSBuZXcgU3lzdGVtLldpbmRvd3MuVGhyZWFkaW5nLkRpc3BhdGNoZXJ7WyYqJl19VGltZXIoKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2MjpcIjxzcGFuPiAgIGRpc3BhdGNoZXJUaW1lci5UaWNrICs9IGRpc3BhdGNoZXJUaW1lcl9UaWNrOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo2OTpcIjxzcGFuPiAgIGRpc3BhdGNoZXJUaW1lci5JbnRlcnZhbCA9IG5ldyBUaW1lU3BhbigwLCAwLCAwLCA1KTsKPC9zcGFuPlwie1smKiZdfTt7WyYqJl19[[/code]]

[[code]]czo0MTpcIjxzcGFuPiAgIGRpc3BhdGNoZXJUaW1lci5TdGFydCgpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo2NzpcIjxzcGFuPnZvaWQgZGlzcGF0Y2hlclRpbWVyX1RpY2sob2JqZWN0IHNlbmRlciwgRXZlbnRBcmdzIGUpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozNzpcIjxzcGFuPiAgIFVwZGF0ZUJ1aWxkU3RhdHVzKCk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

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.

[[code]]czo0NjpcIjxzcGFuPnByaXZhdGUgdm9pZCBVcGRhdGVCdWlsZFN0YXR1cygpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo2NzpcIjxzcGFuPiAgIHZhciByZXN1bHQgPSBHZXRCdWlsZEluZm8oXCJUZXN0IC0gTmlnaHRseSBCdWlsZFwiKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo2MzpcIjxzcGFuPiAgICByZXF1ZXN0QnlUZXh0QmxvY2suVGV4dCA9IHJlc3VsdC5SZXF1ZXN0ZWRCeTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxMjY6XCI8c3Bhbj4gICAgcmVxdWVzdEJ5SW1hZ2UuU291cmNlID0gbmV3IEJpdG1hcEltYWdlKG5ldyBVcmkoXCJJbWFnZXMvXCIgK3tbJiomXX0gcmVzdWx0LlJlcXVlc3RlZEJ5ICsgXCIuanBnXCIsIFVyaUtpbmQuUmVsYXRpdmUpKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo0NTpcIjxzcGFuPiAgICB2YXIgc3RhdHVzID0gcmVzdWx0LlN0YXR1czsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo0MTpcIjxzcGFuPiAgICBzd2l0Y2ggKChzdHJpbmcpc3RhdHVzKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxOTpcIjxzcGFuPiAgICB7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czozNjpcIjxzcGFuPiAgICAgICAgY2FzZSBcIkZhaWxlZFwiOgo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo1OTpcIjxzcGFuPiAgICAgICAgICAgICBTZXRCYWNrZ3JvdW5kQ29sb3IoMjM3LCAyOCwgMzYpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czozODpcIjxzcGFuPiAgICAgICAgICAgICBBY3RpdmF0ZSgpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo2MzpcIjxzcGFuPiAgICAgICAgICAgICBXaW5kb3dTdGF0ZSA9IFdpbmRvd1N0YXRlLk1heGltaXplZDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMzpcIjxzcGFuPiAgICAgICAgICAgICBicmVhazsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo0ODpcIjxzcGFuPiAgICAgICAgY2FzZSBcIlBhcnRpYWxseVN1Y2NlZWRlZFwiOgo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo2MDpcIjxzcGFuPiAgICAgICAgICAgICBTZXRCYWNrZ3JvdW5kQ29sb3IoMjU1LCAyMDEsIDE0KTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozODpcIjxzcGFuPiAgICAgICAgICAgICBBY3RpdmF0ZSgpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo2MzpcIjxzcGFuPiAgICAgICAgICAgICBXaW5kb3dTdGF0ZSA9IFdpbmRvd1N0YXRlLk1heGltaXplZDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMzpcIjxzcGFuPiAgICAgICAgICAgICBicmVhazsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMDpcIjxzcGFuPiAgICAgICAgZGVmYXVsdDoKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxMTY6XCI8c3Bhbj4gICAgICAgICAgICAgV2luZG93U3RhdGUgPSBXaW5kb3dTdGF0ZSA9PSBXaW5kb3dTdGF0ZS5NYXhpbWl6ZWR7WyYqJl19ID8gV2luZG93U3RhdGUuTWluaW1pemVkIDogV2luZG93U3RhdGU7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2MTpcIjxzcGFuPiAgICAgICAgICAgICBTZXRCYWNrZ3JvdW5kQ29sb3IoMTk1LCAxOTUsIDE5NSk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czozMzpcIjxzcGFuPiAgICAgICAgICAgICBicmVhazsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyMzpcIjxzcGFuPiAgICAgICAgfQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyNDpcIjxzcGFuPiAgICB9PGJyPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

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

[[code]]czo2OTpcIjxzcGFuPnByaXZhdGUgdm9pZCBTZXRCYWNrZ3JvdW5kQ29sb3IoYnl0ZSByLCBieXRlIGcsIGJ5dGUgYikKPC9zcGFuPlwie1smKiZdfTt7WyYqJl19[[/code]]

[[code]]czoxNTpcIjxzcGFuPnsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo4MzpcIjxzcGFuPiAgIG1haW5HcmlkLkJhY2tncm91bmQgPSBuZXcgU29saWRDb2xvckJydXNoKENvbG9yLkZyb21SZ2IociwgZyx7WyYqJl19IGIpKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoxNTpcIjxzcGFuPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

Here is the XAML is you are interested:

[[code]]czo1NzpcIjxzcGFuPiZsdDtXaW5kb3cgeDpDbGFzcz1cIlRmc05vdGlmaWVyLk1haW5XaW5kb3dcIgo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo4NzpcIjxzcGFuPiAgICAgICAgeG1sbnM9XCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVze1smKiZdfWVudGF0aW9uXCIKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo3NjpcIjxzcGFuPiAgICAgICAgeG1sbnM6eD1cImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sXCIKPHtbJiomXX0vc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo3MDpcIjxzcGFuPiAgICAgICAgVGl0bGU9XCJNYWluV2luZG93XCIgSGVpZ2h0PVwiMTAwMFwiIFdpZHRoPVwiODAwXCImZ3Q7Cjwvc3Bhbj57WyYqJl19XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0NjpcIjxzcGFuPiAgICAmbHQ7R3JpZCBOYW1lPVwibWFpbkdyaWRcIiZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo5NjpcIjxzcGFuPiAgICAgICAgJmx0O1N0YWNrUGFuZWwgSG9yaXpvbnRhbEFsaWdubWVudD1cIkNlbnRlclwiIFZlcnRpY2FsQWxpZ3tbJiomXX1ubWVudD1cIkNlbnRlclwiJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxMjY6XCI8c3Bhbj4gICAgICAgICAgICAmbHQ7VGV4dEJsb2NrIEZvbnRTaXplPVwiOTBweFwiIEZvbnRXZWlnaHQ9XCJFeHRyYUJvbGR7WyYqJl19XCIgTmFtZT1cInJlcXVlc3RCeVRleHRCbG9ja1wiJmd0OyZsdDsvVGV4dEJsb2NrJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoxMjE6XCI8c3Bhbj4gICAgICAgICAgICAmbHQ7SW1hZ2UgU3RyZXRjaD1cIk5vbmVcIiBOYW1lPVwicmVxdWVzdEJ5SW1hZ2VcIiBNYXhIe1smKiZdfWVpZ2h0PVwiNTAwXCIgTWF4V2lkdGg9XCI3MDBcIiZndDsmbHQ7L0ltYWdlJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo0MTpcIjxzcGFuPiAgICAgICAgJmx0Oy9TdGFja1BhbmVsJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICAmbHQ7L0dyaWQmZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyOTpcIjxzcGFuPiZsdDsvV2luZG93Jmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

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

Read More