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

Agenda för Javaforum den 7:e februari

Anmäl dig till mötet här 17:00 Registrering & mingel 17:30 Välkommen Tomas Trolltoft & Rikard Thulin, Squeed 17:40 The Java EE 7 Platform: Boosting Productivity and Embracing HTML5 The Java EE 7 platform focuses on Productivity and HTML5. JAX-RS 2 adds a new Client API to invoke the RESTful endpoints. JMS 2 is undergoing a complete overhaul to […]

Read More

Agenda för nforum den 7:e februari

Den 7 Feb är det dags igen. Denna gång våra MVP (Microsoft Most Valuable Professional) Trioler från Göteborg. Anmälan dig här. 17:00 Macka & mingel 17:30 Intro 17:35 Windows 8 + Node.js = true? Microsoft har lanserat en tjänst som heter Mobile Services i Azure. Mobile Services skall hjälpa app-utvecklare att komma igång med en […]

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

Using Razor engine together with Asp.Net Web API to create a Hypermedia API

This blog post is created just for fun, and it’s will be about how we can use Razor to create a Hypermedia API using XML as a hypermedia for a “Maze game” inspired from the book “Building Hypermedia APIs with HTML5 and Node”, Mike Amundsen. Only the server-side API is covered in this blog post, so no Client.

Note: The code in this blog post will use the WebAPI Contrib’s Formatting.RazorViewEngine

Here is a short description of the game:

In this “Maze Game”, a client should be able to request a maze to play. The client should be able to navigate from a starting point through the maze to the exit. The cells in the maze can have different exit (doorways) into other cells (north, south, east and west).

To design the media type for the game, we first need to know what clients can do, so here is a list of requirements of the game:

  • A list of available mazes to select among.
  • Be able to select one maze to play.
  • See all the doorways in each cell.
  • Navigate through a selected doorway into the next cell.
  • See the exit of the maze and navigate through the exit.

To design the hypermedia we can take the requirements and turn it into some states.

The following is a list of application states and transitions for each identified state of the maze:

Collection State

    The response represents a list of mazes.

Possible transitions are:

1) Select a maze (Item State), or
2) reload the list (Collection state).

Item State

    The response represents a single maze.

    Possible transition are:

  1. Start into the maze, or
  2. reload the list (Collection Sate), or
  3. reload the maze (Item State).

Cell State

    The response represents a single cell in the maze.

    Possible transitions are:

  1. Continue through one of the doorways to the next cell (Cell State)
  2. Return to the maze (Item State)
  3. Return to the maze list (Collection State), or
  4. reload the cell (Cell State).

Error State

    The response represents the details of an error. No transitions from this state.

Here is the hypermedia design of the above states:

[[code]]czoyMTM6XCI8c3Bhbj4mbHQ7bWF6ZSZndDs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2NvbGxlY3Rpb24vJmd0Ozxicj57WyYqJl19Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2l0ZW0vJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7Y2VsbC8mZ3tbJiomXX10Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7ZXJyb3ImZ3Q7PGJyPiZsdDsvbWF6ZSZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

For the Item State’s transition the <link href=”…” rel=”maze”/> will be used, the “href” and “rel” attribute is used to hold the URI and transition identifier.

For the Collection State the “href” attribute will be used for reloading the list:

<collection href=”…”>

A fully Collection State could look like this

[[code]]czo0MDE6XCI8c3Bhbj4mbHQ7bWF6ZSZndDs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2NvbGxlY3Rpb24gaHJlZj1cIiZoe1smKiZdfWVsbGlwO1wiJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7bGluayBocmVme1smKiZdfT1cIiZoZWxsaXA7XCIgcmVsPVwibWF6ZVwiIC8mZ3Q7PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZue1smKiZdfWJzcDsmbHQ7bGluayBocmVmPVwiJmhlbGxpcDtcIiByZWw9XCJtYXplXCIgLyZndDs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ie1smKiZdfXNwOyZuYnNwOyZuYnNwOyZuYnNwOyZoZWxsaXA7PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZsdDsvY29sbGVjdGlvbiZndDt7WyYqJl19PGJyPiZsdDsvbWF6ZSZndDs8YnI+PC9zcGFuPlwiO3tbJiomXX0=[[/code]]

The Item State is represented by using the <item/> element, it also uses the <link/> element for transitions and the” href” attribute for reloading the maze details. Here is an example:

[[code]]czozOTM6XCI8c3Bhbj4mbHQ7bWF6ZSZndDs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2l0ZW0gaHJlZj1cIiZoZWxsaXA7e1smKiZdfVwiJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7bGluayBocmVmPVwiJmhlbHtbJiomXX1saXA7XCIgcmVsPVwiY29sbGVjdGlvblwiIC8mZ3Q7PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZue1smKiZdfWJzcDsmbHQ7bGluayBocmVmPVwiJmhlbGxpcDtcIiByZWw9XCJzdGFydFwiIC8mZ3Q7PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZue1smKiZdfWJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmaGVsbGlwOzxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7L2l0ZW0mZ3Q7PGJyPiZ7WyYqJl19bHQ7L21hemUmZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

The Cell State is represented in the following way:

[[code]]czo4MDI6XCI8c3Bhbj4mbHQ7bWF6ZSZndDs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2NlbGwgaHJlZj1cIiZoZWxsaXA7e1smKiZdfVwiJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7bGluayBocmVmPVwiJmhlbHtbJiomXX1saXA7XCIgcmVsPVwibm9ydGhcIiAvJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsme1smKiZdfWx0O2xpbmsgaHJlZj1cIiZoZWxsaXA7XCIgcmVsPVwic291dGhcIiAvJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsme1smKiZdfW5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2xpbmsgaHJlZj1cIiZoZWxsaXA7XCIgcmVsPVwiZWFzdFwiIC8mZ3Q7PGJyPiZuYnNwOyZuYnNwOyZue1smKiZdfWJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbHQ7bGluayBocmVmPVwiJmhlbGxpcDtcIiByZWw9XCJ3ZXN0XCIgLyZndDs8e1smKiZdfWJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZsdDtsaW5rIGhyZWY9XCImaGVsbGlwO1wiIHtbJiomXX1yZWw9XCJleGl0XCIgLyZndDs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jmx0O2xpbmt7WyYqJl19IGhyZWY9XCImaGVsbGlwO1wiIHJlbD1cIm1hemVcIiAvJmd0Ozxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJ7WyYqJl19c3A7Jm5ic3A7Jmx0O2xpbmsgaHJlZj1cIiZoZWxsaXA7XCIgcmVsPVwiY29sbGVjdGlvblwiIC8mZ3Q7PGJyPiZuYnNwOyZuYnNwOyZuYnN7WyYqJl19cDsmbmJzcDsmbHQ7L2NlbGwmZ3Q7PGJyPiZsdDsvbWF6ZSZndDs8YnI+PC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Now when the design of the hypermedia is done, we can start creating our API and the Razor views that should render the media.

The following code are simple fakes that representation the Model of the Maze:

[[code]]czo2OTpcIjxzcGFuPnVzaW5nIFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljOzxicj51c2luZyBTeXN0ZW0uTGlucTsKPC9zcGFuPlwie1smKiZdfTt7WyYqJl19[[/code]]

[[code]]czoyMjk6XCI8c3Bhbj5uYW1lc3BhY2UgTXZjV2ViQXBpU2l0ZVRlc3QuTW9kZWxzPGJyPns8YnI+ICAgIHB1YmxpYyBjbGFzcyBNYXp7WyYqJl19ZURiPGJyPiAgICB7PGJyPiAgICAgICAgcHVibGljIE1hemVHYW1lIEdldE1hemUoKTxicj4gICAgICAgIHs8YnI+ICAgICAgICAgIHtbJiomXX0gIHZhciBtYXplID0gbmV3IE1hemUgeyBJZCA9IFwibXktb25seS1tYXplXCIsIERlc2NyaXB0aW9uID0gXCJNeSBvbmx5IG1hemVcIiB9O3tbJiomXX0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czoyNTE6XCI8c3Bhbj4gICAgICAgICAgICBtYXplLkFkZENlbGwobmV3IENlbGwgeyBJZCA9IDEsIERvb3J3YXlzID0gbmV3W10geyB7WyYqJl19bmV3IERvb3JXYXkgeyBJZCA9IDEsIERpcmVjdGlvbiA9IFwibm9ydGhcIiwgQ2VsbCA9IDIgfSB9IH0pOzxicj4gICAgICAgICAgICBte1smKiZdfWF6ZS5BZGRDZWxsKG5ldyBDZWxsIHsgSWQgPSAyLCBEb29yd2F5cyA9IG5ld1tdIHsgbmV3IERvb3JXYXkgeyBJZCA9IDEsIERpcmV7WyYqJl19Y3Rpb24gPSBcImV4aXRcIiB9IH0gfSk7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo4ODpcIjxzcGFuPiAgICAgICAgICAgIHJldHVybiBuZXcgTWF6ZUdhbWUgeyBNYXplcyA9IG5ldyBMaXN0Jmx0O01hemUmZ3Q7IHt7WyYqJl19IG1hemUgfSB9Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICAgICAgfTxicj4gICB9Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo5ODI6XCI8c3Bhbj4gICBwdWJsaWMgY2xhc3MgTWF6ZUdhbWU8YnI+ICAgezxicj4gICAgICAgIHB1YmxpYyBJRW51bWVyYWJsZSZ7WyYqJl19bHQ7TWF6ZSZndDsgTWF6ZXMgeyBnZXQ7IHNldDsgfTxicj4gICB9PGJyPjxicj4gICBwdWJsaWMgY2xhc3MgTWF6ZTxicj4gICB7PHtbJiomXX1icj4gICAgICAgIHByaXZhdGUgSUxpc3QmbHQ7Q2VsbCZndDsgX2NlbGxzID0gbmV3IExpc3QmbHQ7Q2VsbCZndDsoKTs8YnI+PGJye1smKiZdfT4gICAgICAgIHB1YmxpYyBzdHJpbmcgSWQgeyBnZXQ7IHNldDsgfTxicj4gICAgICAgIHB1YmxpYyBzdHJpbmcgRGVzY3JpcHRpb257WyYqJl19IHsgZ2V0OyBzZXQ7IH08YnI+ICAgICAgICBwdWJsaWMgQ2VsbCBTdGFydCB7IGdldCB7IHJldHVybiBDZWxscy5GaXJzdCgpOyB9fXtbJiomXX08YnI+ICAgICAgICBwdWJsaWMgSUVudW1lcmFibGUmbHQ7Q2VsbCZndDsgQ2VsbHMgeyBnZXQgeyByZXR1cm4gX2NlbGxzOyB9fTxie1smKiZdfXI+PGJyPiAgICAgICAgcHVibGljIHZvaWQgQWRkQ2VsbChDZWxsIGNlbGwpPGJyPiAgICAgICAgezxicj4gICAgICAgICAgICBjZWx7WyYqJl19bC5NYXplID0gdGhpczs8YnI+ICAgICAgICAgICAgX2NlbGxzLkFkZChjZWxsKTs8YnI+ICAgICAgICB9PGJyPiAgICB9PGJyPjxicntbJiomXX0+ICAgIHB1YmxpYyBjbGFzcyBDZWxsPGJyPiAgICB7PGJyPiAgICAgICAgcHVibGljIE1hemUgTWF6ZSB7IGdldDsgc2V0OyB9PGJye1smKiZdfT4gICAgICAgIHB1YmxpYyBpbnQgSWQgeyBnZXQ7IHNldDsgfTxicj4gICAgICAgIHB1YmxpYyBJRW51bWVyYWJsZSZsdDtEb29yV2F7WyYqJl19eSZndDsgRG9vcndheXMgeyBnZXQ7IHNldDsgfTxicj4gICAgfTxicj48YnI+ICAgIHB1YmxpYyBjbGFzcyBEb29yV2F5PGJyPiAgIHtbJiomXX0gezxicj4gICAgICAgIHB1YmxpYyBpbnQgSWQgeyBnZXQ7IHNldDsgfTxicj4gICAgICAgIHB1YmxpYyBzdHJpbmcgRGlyZWN0aW9ue1smKiZdfSB7IGdldDsgc2V0OyB9PGJyPiAgICAgICAgcHVibGljIGludCBDZWxsIHsgZ2V0OyBzZXQ7IH08YnI+ICAgIH08YnI+fQo8L3NwYW57WyYqJl19PlwiO3tbJiomXX0=[[/code]]

 

The following is the ApiController used. The code for accessing the Maze are just fakes (Because the blog post is not about how to write the server-side code, it’s about how to use the RazorViewEngine to create a hypermedia, the code is simple and stupid and far from perfect!):

[[code]]czo5NjU6XCI8c3Bhbj51c2luZyBTeXN0ZW0uV2ViLkh0dHA7PGJyPjxicj5uYW1lc3BhY2UgTXZjV2ViQXBpU2l0ZVRlc3QuQ29udHJ7WyYqJl19b2xsZXJzPGJyPns8YnI+ICAgIHVzaW5nIFN5c3RlbS5MaW5xOzxicj4gICAgdXNpbmcgTXZjV2ViQXBpU2l0ZVRlc3QuTW9kZWxzO3tbJiomXX08YnI+PGJyPiAgICBwdWJsaWMgY2xhc3MgTWF6ZUNvbnRyb2xsZXIgOiBBcGlDb250cm9sbGVyPGJyPiAgICB7PGJyPiAgICAgICAge1smKiZdfS8vIEdFVCAvbWF6ZTxicj4gICAgICAgIHB1YmxpYyBNYXplR2FtZSBHZXQoKTxicj4gICAgICAgIHs8YnI+ICAgICAgICAgICAgdmF7WyYqJl19ciBtYXplRGIgPSBuZXcgTWF6ZURiKCk7PGJyPiAgICAgICAgICAgIHJldHVybiBtYXplRGIuR2V0TWF6ZSgpOzxicj4gICAgICAgIHtbJiomXX19PGJyPjxicj4gICAgICAgIC8vIEdFVCAvbWF6ZS97bWF6ZX08YnI+ICAgICAgICBwdWJsaWMgTWF6ZSBHZXQoc3RyaW5nIG1hemUpe1smKiZdfTxicj4gICAgICAgIHs8YnI+ICAgICAgICAgICAgdmFyIG1hemVEYiA9IG5ldyBNYXplRGIoKTs8YnI+ICAgICAgICAgICAgcmV0dXJ7WyYqJl19biBtYXplRGIuR2V0TWF6ZSgpPGJyPiAgICAgICAgICAgICAgICAgICAgICAgICAuTWF6ZXMuU2luZ2xlKG0gPSZndDsgbS5JZCA9PXtbJiomXX0gbWF6ZSk7PGJyPiAgICAgICAgfTxicj48YnI+ICAgICAgICAvLyBHRVQgL21hemUve21hemV9L3tpZH08YnI+ICAgICAgICBwdWJse1smKiZdfWljIENlbGwgR2V0KHN0cmluZyBtYXplLCBpbnQgaWQpPGJyPiAgICAgICAgezxicj4gICAgICAgICAgICB2YXIgbWF6ZURiID0gbmV7WyYqJl19dyBNYXplRGIoKTs8YnI+ICAgICAgICAgICAgcmV0dXJuIG1hemVEYi5HZXRNYXplKCk8YnI+ICAgICAgICAgICAgICAgICAgICAgIHtbJiomXX0gICAuTWF6ZXMuU2luZ2xlKG0gPSZndDsgbS5JZCA9PSBtYXplKTxicj4gICAgICAgICAgICAgICAgICAgICAgICAgLkNlbGxzLlNpe1smKiZdfW5nbGUoYyA9Jmd0OyBjLklkID09IGlkKTs8YnI+ICAgICAgICB9PGJyPiAgICB9PGJyPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

The following code configures the route for the API so it can take “maze” and “id” from the URL as parameters (instead of using Querystrings) into the Get methods of the MazeController:

[[code]]czoyNzg6XCI8c3Bhbj48YnI+Y29uZmlnLlJvdXRlcy5NYXBIdHRwUm91dGUoPGJyPiAgICAgICAgICAgICAgICBuYW1lOiBcIkRlZmF1e1smKiZdfWx0QXBpXCIsPGJyPiAgICAgICAgICAgICAgICByb3V0ZVRlbXBsYXRlOiBcIm1hemUve21hemV9L3tpZH1cIiw8YnI+ICAgICAgICAgICB7WyYqJl19ICAgICBkZWZhdWx0czogbmV3IHsgY29udHJvbGxlciA9IFwibWF6ZVwiLCBtYXplID0gUm91dGVQYXJhbWV0ZXIuT3B0aW9uYWwsIGlke1smKiZdfSA9IFJvdXRlUGFyYW1ldGVyLk9wdGlvbmFsIH08YnI+ICAgICAgICAgICAgKTs8YnI+PC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Now when the server-side API is created, we can go on to the Razor Views that will render the hypermedia designed earlier in this blog post.

The WebApi Contrib’s Formatting RazorViewEngine can use convention before configuration to locate the View for rendering the Model the MazeController returns. The name of the view should be the same name as the class the MazeController returns. The Views are created in the Views folder and the views are MazeGame.cshtml (Representation the Collection State of the Maze), Maze.cshtml (Represents the Item State) and the Cell.cshtml (Represents the Cell State). Here is the code of the Views:

MazeGame.cshtml

[[code]]czozNDg6XCI8YnI+PHNwYW4+Jmx0Oz94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwidXRmLThcIiA/Jmd0Ozxicj4mbHQ7bWF6ZSZne1smKiZdfXQ7PGJyPiAgICAmbHQ7Y29sbGVjdGlvbiBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplR2FtZVwiJmd0Ozxicj4gICAgIHtbJiomXX0gICBAZm9yZWFjaCAodmFyIG1hemUgaW4gTW9kZWwuTWF6ZXMpPGJyPiAgICAgICAgezxicj4gICAgICAgICAgICAmbHQ7bGluayBoe1smKiZdfXJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZS9AbWF6ZS5JZFwiIHJlbD1cIm1hemVcIiAvJmd0Ozxicj4gICAgICAgIH08YnI+e1smKiZdfSAgICAmbHQ7L2NvbGxlY3Rpb24mZ3Q7PGJyPiZsdDsvbWF6ZSZndDs8YnI+PC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Here is a representation of the MazeGame.cshtml ouput:

[[code]]czo2MDpcIjxzcGFuPiZsdDs/eG1sIHZlcnNpb249XCIxLjBcIiBlbmNvZGluZz1cInV0Zi04XCIgPyZndDsgCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNjpcIjxzcGFuPiZsdDttYXplJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo3NTpcIjxzcGFuPiAgICAmbHQ7Y29sbGVjdGlvbiBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplR2FtZVwiJmd0Owo8L3tbJiomXX1zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo5OTpcIjxzcGFuPiAgICAgICAgICAgICZsdDtsaW5rIGhyZWY9XCJodHRwOi8vd2VibG9ncy5hc3AubmV0L21hemUvbXktb25seS1te1smKiZdfWF6ZVwiIHJlbD1cIm1hemVcIiAvJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czozNzpcIjxzcGFuPiAgICAmbHQ7L2NvbGxlY3Rpb24mZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNzpcIjxzcGFuPiZsdDsvbWF6ZSZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Maze.cshtml

[[code]]czozNjI6XCI8YnI+PHNwYW4+Jmx0Oz94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwidXRmLThcIiA/Jmd0Ozxicj4mbHQ7bWF6ZSZne1smKiZdfXQ7PGJyPiAgICAmbHQ7aXRlbSBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplL0BNb2RlbC5JZFwiICZndDs8YnI+ICAgIHtbJiomXX0gICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZUdhbWVcIiByZWw9XCJjb2xsZWN0aW9uXCImZ3Q7PGJyPntbJiomXX0gICAgICAgICZsdDtsaW5rIGhyZWY9XCJodHRwOi8vd2VibG9ncy5hc3AubmV0L21hemUvQE1vZGVsLklkL0BNb2RlbC5TdGFydC5JZHtbJiomXX1cIiByZWw9XCJzdGFydFwiIC8mZ3Q7PGJyPiAgICAmbHQ7L2l0ZW0mZ3Q7PGJyPiZsdDsvbWF6ZSZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

Here is a representation of the Maze.cshtml output:

[[code]]czo2NDpcIjxicj48c3Bhbj4mbHQ7P3htbCB2ZXJzaW9uPVwiMS4wXCIgZW5jb2Rpbmc9XCJ1dGYtOFwiID8mZ3Q7IAo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyNjpcIjxzcGFuPiZsdDttYXplJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo3OTpcIjxzcGFuPiAgICAmbHQ7aXRlbSBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplL215LW9ubHktbWF6ZVwiICZndHtbJiomXX07Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo5MDpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZUdhbWVcIiByZWw9XCJjb2x7WyYqJl19bGVjdGlvblwiJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo5ODpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZS9teS1vbmx5LW1hemUve1smKiZdfTFcIiByZWw9XCJzdGFydFwiIC8mZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICAmbHQ7L2l0ZW0mZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNzpcIjxzcGFuPiZsdDsvbWF6ZSZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

Cell.cshtml

 

[[code]]czo2MDpcIjxzcGFuPiZsdDs/eG1sIHZlcnNpb249XCIxLjBcIiBlbmNvZGluZz1cInV0Zi04XCIgPyZndDsgCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNjpcIjxzcGFuPiZsdDttYXplJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo5MDpcIjxzcGFuPiAgICAmbHQ7Y2VsbCBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplL0BNb2RlbC5NYXplLklkL0BNe1smKiZdfW9kZWwuSWRcIiZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo1OTpcIjxzcGFuPiAgICAgICAgQGZvcmVhY2ggKHZhciBjZWxsIGluIE1vZGVsLkRvb3J3YXlzKQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

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

[[code]]czoxMjM6XCI8c3Bhbj4gICAgICAgICAgICAmbHQ7bGluayBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplL0BNb2RlbC5Ne1smKiZdfWF6ZS5JZC9AY2VsbC5DZWxsXCIgcmVsPVwiQGNlbGwuRGlyZWN0aW9uXCIgLyZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

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

[[code]]czo5NzpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZS9ATW9kZWwuTWF6ZS5Je1smKiZdfWRcIiByZWw9XCJtYXplXCIgLyZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo4ODpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZVwiIHJlbD1cImNvbGxlY3R7WyYqJl19aW9uXCIgLyZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICAmbHQ7L2NlbGwmZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNzpcIjxzcGFuPiZsdDsvbWF6ZSZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

Here is a representation of the Cell.cshtml output:

[[code]]czo2MDpcIjxzcGFuPiZsdDs/eG1sIHZlcnNpb249XCIxLjBcIiBlbmNvZGluZz1cInV0Zi04XCIgPyZndDsgCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNjpcIjxzcGFuPiZsdDttYXplJmd0Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo4MDpcIjxzcGFuPiAgICAmbHQ7Y2VsbCBocmVmPVwiaHR0cDovL3dlYmxvZ3MuYXNwLm5ldC9tYXplL215LW9ubHktbWF6ZS8xXCImZ3tbJiomXX10Owo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czo5ODpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZS9teS1vbmx5LW1hemUve1smKiZdfTJcIiByZWw9XCJub3J0aFwiIC8mZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo5NTpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZS9teS1vbmx5LW1hemVcIntbJiomXX0gcmVsPVwibWF6ZVwiIC8mZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo4ODpcIjxzcGFuPiAgICAgICAgJmx0O2xpbmsgaHJlZj1cImh0dHA6Ly93ZWJsb2dzLmFzcC5uZXQvbWF6ZVwiIHJlbD1cImNvbGxlY3R7WyYqJl19aW9uXCIgLyZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czozMTpcIjxzcGFuPiAgICAmbHQ7L2NlbGwmZ3Q7Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czoyNzpcIjxzcGFuPiZsdDsvbWF6ZSZndDsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

Now when the Views for rending the hypermedia is done, we need to configure so we can use a specific media type to access our hypermedia. We will use “application/maze+xml” as the media type.

To add the support of our own media type “application/maze+xml” we can take advantage of the WebApiContrib Razor Formatter’s HtmlMediaTypeViewFormatter. We can simply add the “application/maze+xml” media type to the HtmlMediaTypeViewFormatter, here is the Application_Start method of the Global.asax to add our media type and use the HtmlMediaTypeViewFormatter:

[[code]]czo0ODpcIjxzcGFuPnByb3RlY3RlZCB2b2lkIEFwcGxpY2F0aW9uX1N0YXJ0KCkKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

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

[[code]]czo1NjpcIjxzcGFuPiAgICAgIEFyZWFSZWdpc3RyYXRpb24uUmVnaXN0ZXJBbGxBcmVhcygpOwo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo2OTpcIjxzcGFuPiAgICAgIHZhciBmb3JtYXR0ZXIgPSBuZXcgSHRtbE1lZGlhVHlwZVZpZXdGb3JtYXR0ZXIoKTsKPC9zcGFuPlwie1smKiZdfTt7WyYqJl19[[/code]]

[[code]]czoxMDQ6XCI8c3Bhbj4gICAgICBmb3JtYXR0ZXIuU3VwcG9ydGVkTWVkaWFUeXBlcy5BZGQobmV3IE1lZGlhVHlwZUhlYWRlclZhbHV7WyYqJl19ZShcImFwcGxpY2F0aW9uL21hemUreG1sXCIpKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo4MDpcIjxzcGFuPiAgICAgIEdsb2JhbENvbmZpZ3VyYXRpb24uQ29uZmlndXJhdGlvbi5Gb3JtYXR0ZXJzLkFkZChmb3JtYXR0ZXJ7WyYqJl19KTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

[[code]]czo3NDpcIjxzcGFuPiAgICAgIEdsb2JhbFZpZXdzLkRlZmF1bHRWaWV3UGFyc2VyID0gbmV3IFJhem9yVmlld1BhcnNlcigpOwo8L3N7WyYqJl19cGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo3NjpcIjxzcGFuPiAgICAgIEdsb2JhbFZpZXdzLkRlZmF1bHRWaWV3TG9jYXRvciA9IG5ldyBSYXpvclZpZXdMb2NhdG9yKCk7Cjx7WyYqJl19L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

 

[[code]]czo3NzpcIjxzcGFuPiAgICAgIFdlYkFwaUNvbmZpZy5SZWdpc3RlcihHbG9iYWxDb25maWd1cmF0aW9uLkNvbmZpZ3VyYXRpb24pOwp7WyYqJl19PC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo3ODpcIjxzcGFuPiAgICAgIEZpbHRlckNvbmZpZy5SZWdpc3Rlckdsb2JhbEZpbHRlcnMoR2xvYmFsRmlsdGVycy5GaWx0ZXJzKTt7WyYqJl19Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

[[code]]czo2NjpcIjxzcGFuPiAgICAgIFJvdXRlQ29uZmlnLlJlZ2lzdGVyUm91dGVzKFJvdXRlVGFibGUuUm91dGVzKTsKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo3MDpcIjxzcGFuPiAgICAgIEJ1bmRsZUNvbmZpZy5SZWdpc3RlckJ1bmRsZXMoQnVuZGxlVGFibGUuQnVuZGxlcyk7Cjwvc3Bhbj57WyYqJl19XCI7e1smKiZdfQ==[[/code]]

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

To access the Maze Hypermedia API by a HTTP GET, make sure the HTTP Header Accept is set to “application/maze+xml”:

[[code]]czo0MjpcIjxzcGFuPkFjY2VwdDogYXBwbGljYXRpb24vbWF6ZSt4bWwKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

All done!

By using ASP.Net Web API and the WebApiContrib.Formatting.RazorViewEngine, we can use Razor to create a hypermedia, wasn’t that cool? 😉

By following me on twitter @fredrikn, you will know when I publish new blog posts.

 

 

 

 

 


 

Read More