NDC Oslo 2016

I år var nionde gången som NDC Oslo gick av stapeln och konferensen har under denna tiden vuxit till en av Europas största .NET konferenser. Den bjöd på 2 dagars workshops och 3 dagars konferens. Troy Hunt inledde konferensen med en rolig och tänkvärd keynote: “Yesterday’s Technology is Dead, Today’s is on Life Support”. Han […]

Read More

NDC London 2016 Onsdagen

Efter en briljant keynote av Scott Hanselman, så startade årets upplaga av NDC London. En utvecklarkonferens med primärt fokus på .NET och webbteknologier. För min egen del så hade jag valt ASP.NET 5 och .NET Core som ett stort fokus för första dagen. Jag blev inte besviken, med många riktigt bra föreläsningar från några av […]

Read More

Microsoft Sommarkollo 2013

Magnus Härlin och Fredrik Normén från oss på Squeed kommer att prata på Microsoft Sommarkollo den 25/6 i Göteborg, “ASP.Net, det senaste, det heta och en titt in i framtiden”. Kommer att bli mycket spännande, vem vet kanske lite Angular.js också! Boka reda nu för att vara säker på att du får en plats: http://www.microsoftsommarkollo.se/index.php/2013/05/asp-net-det-senaste-det-heta-och-en-titt-in-i-framtiden/

Read More

Creating a simple REST like service with OWIN – Open Web Server Interface

OWIN, a standard interface between .NET web servers and web applications. The goal of OWIN is to decouple server and application and, by being an open standard, stimulate the open source ecosystem of .NET web development tools.” – owin.org

OWIN can be used for extremely lightweight hosts that can run from command line, Windows service, client, low power devices etc. Many applications today doesn’t use all the features that IIS (Internet Information Service) provide use with, because we just doesn’t need them.

In this blog post I will show you a very simple REST like service using OWIN (the code will just support easy GETs nothing more, so it’s not a complete REST service).

OWIN is based on a very simple interface, it uses something called application delegate or AppFunc. An application delegate takes the IDictionary<string,object> environment and returns a Task when it has finished processing.

[[code]]czoxMzA6XCI8c3Bhbj4gIHVzaW5nIEFwcEZ1bmMgPSBGdW5jJmx0Ozxicj4gICAgICAgIElEaWN0aW9uYXJ5Jmx0O3N0cmluZywgb2J7WyYqJl19amVjdCZndDssIC8vIEVudmlyb25tZW50PGJyPiAgICAgICAgVGFzayZndDs7IC8vIERvbmUKPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

The dictionary has some keys that can be used to get access to request and response, headers, body, Query string, request method etc. You can find the key’s in the OWIN specification (This blog post will mention some of them later).

In this blog post I will use Katana to host my OWIN and REST like service. To setup a simple OWIN application using Katana, take a look at this documentation (I will not give instruction how to set it up in this blog post).

An OWIN hosts can do the following when its starts:

It first creates properties with startup data or capabilities provided by the host. The properties is an IDirectory<string, object>. The host selects the server to be used and will provides it with the Properties collection, then it locates the application setup code and invokes it with the Properties collection. The application can then use the properties and decide its own requesting pipeline. Then the host invokes the server startup code with the properties and finish configure itself to accepting requests.

The OWIN namespace has the IAppBuilder interface, this will contain the Properties mentioned above. When using Katana a Startup class is needed with two methods:

[[code]]czoxMjU6XCI8YnI+PHNwYW4+cHVibGljIHZvaWQgQ29uZmlndXJhdGlvbihJQXBwQnVpbGRlciBhcHApPGJyPjxicj5wdWJsaWMgVGF7WyYqJl19c2sgSW52b2tlKElEaWN0aW9uYXJ5Jmx0O3N0cmluZywgb2JqZWN0Jmd0OyBlbnYpCjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

The Configuration method will be called when the hosts is starting, and the IAppBuilder will be passed to the Configuration method. The Invoke will be called every time an incoming request should be handled. The env argument will contain a dictionary with environment information, such as request and response.

I created a simple solution to register “routes” and execute methods based on the requested path. Here is my Startup class:

 

[[code]]czo1MTA6XCI8c3Bhbj5wdWJsaWMgY2xhc3MgU3RhcnR1cCA6IEJhc2VOYXZpZ2F0aW9uPGJyPns8YnI+ICAgIHB1YmxpYyB2b2lkIEN7WyYqJl19b25maWd1cmF0aW9uKElBcHBCdWlsZGVyIGFwcCk8YnI+ICAgIHs8YnI+ICAgICAgIEdldFtcIi9saXN0L2N1c3RvbWVyc1wiXSA9IExpe1smKiZdfXN0Q3VzdG9tZXJzOzxicj4gICAgICAgR2V0W1wiL2xpc3QvdXNlcnNcIl0gPSBMaXN0VXNlcnM7PGJyPjxicj4gICAgICAgYXBwLlJ1bntbJiomXX0odGhpcyk7PGJyPiAgICB9PGJyPjxicj4gICAgcHVibGljIFRhc2sgSW52b2tlKElEaWN0aW9uYXJ5Jmx0O3N0cmluZywgb2JqZWN0e1smKiZdfSZndDsgZW52KTxicj4gICAgezxicj4gICAgICAgc3dpdGNoICgoKHN0cmluZyllbnZbXCJvd2luLlJlcXVlc3RNZXRob2RcIl0pLlRvVXtbJiomXX1wcGVyKCkpPGJyPiAgICAgICB7PGJyPiAgICAgICAgICBjYXNlIFwiR0VUXCI6PGJyPiAgICAgICAgICAgICAgICBIdHRwR2V0SGFuZGx7WyYqJl19ZXIoZW52KTs8YnI+ICAgICAgICAgICAgICAgYnJlYWs7PGJyPiAgICAgICAgICAgLy8uLi4KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

[[code]]czo4OTpcIjxzcGFuPiAgICAgICB9PGJyPjxicj4gICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdCZsdDtvYmplY3QmZ3Q7KG51bGx7WyYqJl19KTs8YnI+ICAgIH0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

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

 

The BaseNavigation class inherited by the Startup class just contains a Get property:

[[code]]czoyODc6XCI8YnI+PHNwYW4+cHVibGljIGNsYXNzIEJhc2VOYXZpZ2F0aW9uPGJyPns8YnI+ICAgIERpY3Rpb25hcnkmbHQ7c3RyaW57WyYqJl19ZywgRnVuYyZsdDtvYmplY3QmZ3Q7Jmd0OyBfZ2V0ID0gbmV3IERpY3Rpb25hcnkmbHQ7c3RyaW5nLCBGdW5jJmx0O29iamVjdCZndHtbJiomXX07Jmd0OygpOzxicj48YnI+ICAgIHB1YmxpYyBJRGljdGlvbmFyeSZsdDtzdHJpbmcsIEZ1bmMmbHQ7b2JqZWN0Jmd0OyZndDsgR2V0e1smKiZdfTxicj4gICAgezxicj4gICAgICAgIGdldCB7IHJldHVybiBfZ2V0OyB9PGJyPiAgICB9PGJyPn08L3NwYW4+PGJyPlwiO3tbJiomXX0=[[/code]]

In the Startup’s Configuration method I use the Get property to set up which methods that should handle the request for a specific “route”. The Get property will only be used to get the “routes” when the incoming request is a HTTP GET. Within the Startup class’s Invoke method (as mentioned earlier, the Invoke method will be called on each request) I use the “env” argument to get access to the requested method, this is done by using one of the key specified (“owin.RequestMethod”) in the OWIN specification.

If the request method is GET, I make a call to my HttpGetHandler that will handle the GET:

[[code]]czo2NDY6XCI8c3Bhbj5wcml2YXRlIHZvaWQgSHR0cEdldEhhbmRsZXIoSURpY3Rpb25hcnkmbHQ7c3RyaW5nLCBvYmplY3QmZ3Q7IGV7WyYqJl19bnYpPGJyPns8YnI+ICAgIHZhciByZXNwb25zZUNvbnRlbnQgPSBHZXRbKHN0cmluZyllbnZbXCJvd2luLlJlcXVlc3RQYXRoXCJdXS5Je1smKiZdfW52b2tlKCk7PGJyPjxicj4gICAgdmFyIHJlcXVlc3RIZWFkZXIgPSAoSURpY3Rpb25hcnkmbHQ7c3RyaW5nLCBzdHJpbmdbXSZndDt7WyYqJl19KWVudltcIm93aW4uUmVxdWVzdEhlYWRlcnNcIl07PGJyPiAgICB2YXIgcmVzcG9uc2VIZWFkZXIgPSAoSURpY3Rpb25hcnkmbHQ7c3Rye1smKiZdfWluZywgc3RyaW5nW10mZ3Q7KWVudltcIm93aW4uUmVzcG9uc2VIZWFkZXJzXCJdOzxicj48YnI+ICAgIHJlc3BvbnNlSGVhZGVyLkFkZHtbJiomXX0oXCJDb250ZW50LVR5cGVcIiwgcmVxdWVzdEhlYWRlcltcIkFjY2VwdFwiXSk7PGJyPiAgICBlbnZbXCJvd2luLlJlc3BvbnNlU3RhdHVzQ297WyYqJl19ZGVcIl0gPSAyMDA7PGJyPjxicj4gICAgdXNpbmcgKHZhciB3cml0ZXIgPSBuZXcgU3RyZWFtV3JpdGVyKChTdHJlYW0pZW52W1wib3dpe1smKiZdfW4uUmVzcG9uc2VCb2R5XCJdKSk8YnI+ICAgIHs8YnI+ICAgICAgICBuZXcgSnNvblNlcmlhbGl6ZXIoKS5TZXJpYWxpemUod3JpdGVye1smKiZdfSwgcmVzcG9uc2VDb250ZW50KTs8YnI+ICAgIH0gICAgPGJyPn0KPC9zcGFuPlwiO3tbJiomXX0=[[/code]]

 

NOTE: I don’t care about the HTTP Headers “Content-type” or “Accept” header to decide the format for the returned response, I simply use JSON, the goal is not to write a perfect REST infrastructure, just a simple demonstration of OWIN, there are no error handlers is added to the code.

By using the “owin.RequestPath”, I can get access to the request path, for example if I do GET http://localhost/list/customer, the “list/customer” will be the request path. Because the key value of the BaseNivgation’s Get property is the request path, I just use the path as a key and invoke the Func<T> registered, in this case when “list/customer” is the request path, the ListCustomers method will be executed. To demonstrate how we can get access to the request and response header, I simply gets the “owin.RequestHeaders” and “owin.ResponseHeaders”, and adds the “Content-Type” to the response header with the value of the request headers “Accept” header.

To write to the response stream of a request, the “owin.ResponseBody” key of the “env” argument can be used. The “owin.RequestBody” can be used to get the stream of the request body.

By setting the “env”‘s “owin.ResponseStatusCode”, I can specify the HTTP status of the request, in this case 200 (which is also default).

Here are the ListCustomers and ListUsers methods and the Customer and User classes:

[[code]]czoyNzA6XCI8c3Bhbj5wdWJsaWMgSUVudW1lcmFibGUmbHQ7Q3VzdG9tZXImZ3Q7IExpc3RDdXN0b21lcnMoKTxicj57PGJyPiAgIHJ7WyYqJl19ZXR1cm4gbmV3W10geyBuZXcgQ3VzdG9tZXIoKSB7IElkID0gMSwgTmFtZSA9IFwiSm9obiBEb2VcIiB9IH07PGJyPn08YnI+PGJyPnB1e1smKiZdfWJsaWMgSUVudW1lcmFibGUmbHQ7VXNlciZndDsgTGlzdFVzZXJzKCk8YnI+ezxicj4gICByZXR1cm4gbmV3W10geyBuZXcgVXNlcih7WyYqJl19KSB7IElkID0gMSwgTmFtZSA9IFwiU3VwZXIgVXNlclwiIH0gfTs8YnI+fQo8L3NwYW4+XCI7e1smKiZdfQ==[[/code]]

[[code]]czoyNDA6XCI8c3Bhbj48YnI+cHVibGljIGNsYXNzIEN1c3RvbWVyPGJyPns8YnI+ICAgcHVibGljIGludCBJZCB7IGdldDsgc2V0OyB7WyYqJl19fTxicj48YnI+ICAgcHVibGljIHN0cmluZyBOYW1lIHsgZ2V0OyBzZXQ7IH08YnI+fTxicj48YnI+cHVibGljIGNsYXNzIFVzZXI8YntbJiomXX1yPns8YnI+ICAgIHB1YmxpYyBpbnQgSWQgeyBnZXQ7IHNldDsgfTxicj48YnI+ICAgIHB1YmxpYyBzdHJpbmcgTmFtZSB7IGdldDsge1smKiZdfXNldDsgfTxicj59Cjwvc3Bhbj5cIjt7WyYqJl19[[/code]]

 

Summary

 

In this blog post you have seen how OWIN and Katana was used to create a simple REST like service (well, far from a complete one). The idea was to show some basic stuffs that can be done by using OWIN.

 

If you want to know when I publish a new blog post, 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

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

Using Razor together with ASP.NET Web API

On the blog post “If Then, If Then, If Then, MVC” I found the following code example:

[HttpGet]<br><span>public</span> ActionResult List() 
{
    var list = <span>new</span>[] { <span>"John"</span>, <span>"Pete"</span>, <span>"Ben"</span> };

    <span>if</span> (Request.AcceptTypes.Contains(<span>"application/json"</span>)) {
        <span>return</span> Json(list, JsonRequestBehavior.AllowGet);
    }

    <span>if</span> (Request.IsAjaxRequest()) [
        <span>return</span> PartialView(<span>"_List"</span>, list);
    }

    <span>return</span> View(list);
}


The code is a ASP.NET MVC Controller where it reuse the same “business” code but returns JSON if the request require JSON, a partial view when the request is an AJAX request or a normal ASP.NET MVC View.

The above code may have several reasons to be changed, and also do several things, the code is not closed for modifications. To extend the code with a new way of presenting the model, the code need to be modified. So I started to think about how the above code could be rewritten so it will follow the Single Responsibility and open-close principle. I came up with the following result and with the use of ASP.NET Web API:

<span>public</span> String[] Get()
{
      <span>return</span> <span>new</span>[] { <span>"John"</span>, <span>"Pete"</span>, <span>"Ben"</span> };
}

 

It just returns the model, nothing more. The code will do one thing and it will do it well. But it will not solve the problem when it comes to return Views. If we use the ASP.NET Web Api we can get the result as JSON or XML, but not as a partial view or as a ASP.NET MVC view. Wouldn’t it be nice if we could do the following against the Get() method?

 

Accept: application/json

JSON will be returned – Already part of the Web API

 

Accept: text/html

Returns the model as HTML by using a View

 

The best thing, it’s possible!

 

By using the RazorEngine I created a custom MediaTypeFormatter (RazorFormatter, code at the end of this blog post) and associate it with the media type “text/html”. I decided to use convention before configuration to decide which Razor view should be used to render the model. To register the formatter I added the following code to Global.asax:

GlobalConfiguration.Configuration.Formatters.Add(<span>new</span> RazorFormatter());


Here is an example of a ApiController that just simply returns a model:

<span>using</span> System.Web.Http;

<span>namespace</span> WebApiRazor.Controllers
{
    <span>public</span> <span>class</span> CustomersController : ApiController
    {
        <span>// GET api/values</span>
        <span>public</span> Customer Get()
        {
            <span>return</span> <span>new</span> Customer { Name = <span>"John Doe"</span>, Country = <span>"Sweden"</span> };
        }
    }

    <span>public</span> <span>class</span> Customer
    {
        <span>public</span> <span>string</span> Name { get; set; }

        <span>public</span> <span>string</span> Country { get; set; }
    }
}

 

Because I decided to use convention before configuration I only need to add a view with the same name as the model, Customer.cshtml, here is the example of the View:

 

&lt;!DOCTYPE html&gt;
&lt;html&gt;
    
    &lt;head&gt;
        &lt;script src=<span>"http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.5.1.min.js"</span> type=<span>"text/javascript"</span>&gt;&lt;/script&gt;
    &lt;/head&gt;

    &lt;body&gt;

        &lt;div id=<span>"body"</span>&gt;
            &lt;section&gt;
                
                &lt;div&gt;
                    &lt;hgroup&gt;
                        &lt;h1&gt;Welcome <span>'@Model.Name'</span> to ASP.NET Web API Razor Formatter!&lt;/h1&gt;
                    &lt;/hgroup&gt;
                &lt;/div&gt;
                &lt;p&gt;
                    Using the same URL <span>"api/values"</span> but <span>using</span> AJAX: &lt;button&gt;Press to show content!&lt;/button&gt;
                &lt;/p&gt;
                &lt;p&gt;
                    
                &lt;/p&gt;

            &lt;/section&gt;
        &lt;/div&gt;

    &lt;/body&gt;
    
    &lt;script type=<span>"text/javascript"</span>&gt;

        $(<span>"button"</span>).click(function () {

            $.ajax({
                url: <span>'/api/values'</span>,
                type: <span>"GET"</span>,
                contentType: <span>"application/json; charset=utf-8"</span>,
                success: function(data, status, xhr)
                {
                    alert(data.Name);
                },
                error: function(xhr, status, error)
                {
                    alert(error);
                }});
            });
&lt;/script&gt;
&lt;/html&gt;

 

Now when I open up a browser and enter the following URL: http://localhost/api/customers the above View will be displayed and it will render the model the ApiController returns. If I use Ajax against the same ApiController with the content type set to “json”, the ApiController will now return the model as JSON.

Here is a part of a really early prototype of the Razor formatter (The code is far from perfect, just use it for testing). I will rewrite the code and also make it possible to specify an attribute to the returned model, so it can decide which view to be used when the media type is “text/html”, but by default the formatter will use convention:

<span>using</span> System;
<span>using</span> System.Net.Http.Formatting;

<span>namespace</span> WebApiRazor.Models
{
    <span>using</span> System.IO;
    <span>using</span> System.Net;
    <span>using</span> System.Net.Http.Headers;
    <span>using</span> System.Reflection;
    <span>using</span> System.Threading.Tasks;

    <span>using</span> RazorEngine;

    <span>public</span> <span>class</span> RazorFormatter : MediaTypeFormatter
    {
        <span>public</span> RazorFormatter()
        {
            SupportedMediaTypes.Add(<span>new</span> MediaTypeHeaderValue(<span>"text/html"</span>)); 
            SupportedMediaTypes.Add(<span>new</span> MediaTypeHeaderValue(<span>"application/xhtml+xml"</span>));
        }

        //...<br><span>public</span> <span>override</span> Task WriteToStreamAsync(
                                                Type type,
                                                <span>object</span> <span>value</span>,
                                                Stream stream,
                                                HttpContentHeaders contentHeaders,
                                                TransportContext transportContext)
        {
            var task = Task.Factory.StartNew(() =&gt;
                {
<span>                    var viewPath = // Get path to the view by the name of the type</span>

                    var template = File.ReadAllText(viewPath);

                    Razor.Compile(template, type, type.Name);
                    var razor = Razor.Run(type.Name, <span>value</span>);

                    var buf = System.Text.Encoding.Default.GetBytes(razor);

                    stream.Write(buf, 0, buf.Length);

                    stream.Flush();
                });

            <span>return</span> task;
        }
    }
}

 

Summary

By using formatters and the ASP.NET Web API we can easily just extend our code without doing any changes to our ApiControllers when we want to return a new format. This blog post just showed how we can extend the Web API to use Razor to format a returned model into HTML.

 

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

Read More

ASP.NET Web API Exception Handling

When I talk about exceptions in my product team I often talk about two kind of exceptions, business and critical exceptions. Business exceptions are exceptions thrown based on “business rules”, for example if you aren’t allowed to do a purchase. Business exceptions in most case aren’t important to log into a log file, they can directly be shown to the user. An example of a business exception could be “DeniedToPurchaseException”, or some validation exceptions such as “FirstNameIsMissingException” etc.

Critical Exceptions are all other kind of exceptions such as the SQL server is down etc. Those kind of exception message need to be logged and should not reach the user, because they can contain information that can be harmful if it reach out to wrong kind of users.

I often distinguish business exceptions from critical exceptions by creating a base class called BusinessException, then in my error handling code I catch on the type BusinessException and all other exceptions will be handled as critical exceptions.

This blog post will be about different ways to handle exceptions and how Business and Critical Exceptions could be handled.

Web API and Exceptions the basics

When an exception is thrown in a ApiController a response message will be returned with a status code set to 500 and a response formatted by the formatters based on the “Accept” or “Content-Type” HTTP header, for example JSON or XML. Here is an example:

 

        <span>public</span> IEnumerable&lt;<span>string</span>&gt; Get()
        {
            <span>throw</span> <span>new</span> ApplicationException(<span>"Error!!!!!"</span>);

            <span>return</span> <span>new</span> <span>string</span>[] { <span>"value1"</span>, <span>"value2"</span> };
        }

The response message will be:

HTTP/1.1 500 Internal Server Error
Content-Length: 860
Content-Type: application/json; charset=utf-8

{ <span>"ExceptionType"</span>:<span>"System.ApplicationException"</span>,<span>"Message"</span>:<span>"Error!!!!!"</span>,<span>"StackTrace"</span>:<span>"   at ..."</span>}

 

The stack trace will be returned to the client, this is because of making it easier to debug. Be careful so you don’t leak out some sensitive information to the client. So as long as you are developing your API, this is not harmful. In a production environment it can be better to log exceptions and return a user friendly exception instead of the original exception.

There is a specific exception shipped with ASP.NET Web API that will not use the formatters based on the “Accept” or “Content-Type” HTTP header, it is the exception is the HttpResponseException class.

Here is an example where the HttpReponseExcetpion is used:

        <span>// GET api/values</span>
        [ExceptionHandling]
        <span>public</span> IEnumerable&lt;<span>string</span>&gt; Get()
        {
            <span>throw</span> <span>new</span> HttpResponseException(<span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError));

            <span>return</span> <span>new</span> <span>string</span>[] { <span>"value1"</span>, <span>"value2"</span> };
        }


The response will not contain any content, only header information and the status code based on the HttpStatusCode passed as an argument to the HttpResponseMessage. Because the HttpResponsException takes a HttpResponseMessage as an argument, we can give the response a content:

        <span>public</span> IEnumerable&lt;<span>string</span>&gt; Get()
        {
            <span>throw</span> <span>new</span> HttpResponseException(<span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError)
                                                {
                                                    Content = <span>new</span> StringContent(<span>"My Error Message"</span>),
                                                    ReasonPhrase = <span>"Critical Exception"</span>
                                                });

            <span>return</span> <span>new</span> <span>string</span>[] { <span>"value1"</span>, <span>"value2"</span> };
        }

 

The code above will have the following response:

 

HTTP/1.1 500 Critical Exception
Content-Length: 5
Content-Type: text/plain; charset=utf-8

My Error Message

The Content property of the HttpResponseMessage doesn’t need to be just plain text, it can also be other formats, for example JSON, XML etc.

By using the HttpResponseException we can for example catch an exception and throw a user friendly exception instead:

        <span>public</span> IEnumerable&lt;<span>string</span>&gt; Get()
        {
            <span>try</span>
            {
                DoSomething();

                <span>return</span> <span>new</span> <span>string</span>[] { <span>"value1"</span>, <span>"value2"</span> };

            }
            <span>catch</span> (Exception e)
            {
                <span>throw</span> <span>new</span> HttpResponseException(<span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError)
                                                {
                                                    Content = <span>new</span> StringContent(<span>"An error occurred, please try again or contact the administrator."</span>),
                                                    ReasonPhrase = <span>"Critical Exception"</span>
                                                });
            }
        }

 

Adding a try catch to every ApiController methods will only end in duplication of code, by using a custom ExceptionFilterAttribute or our own custom ApiController base class we can reduce code duplicationof code and also have a more general exception handler for our ApiControllers . By creating a custom ApiController’s and override the ExecuteAsync method, we can add a try catch around the base.ExecuteAsync method, but I prefer to skip the creation of a own custom ApiController, better to use a solution that require few files to be modified.

The ExceptionFilterAttribute has a OnException method that we can override and add our exception handling. Here is an example:

    <span>using</span> System;
    <span>using</span> System.Diagnostics;
    <span>using</span> System.Net;
    <span>using</span> System.Net.Http;
    <span>using</span> System.Web.Http;
    <span>using</span> System.Web.Http.Filters;

    <span>public</span> <span>class</span> ExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        <span>public</span> <span>override</span> <span>void</span> OnException(HttpActionExecutedContext context)
        {
            <span>if</span> (context.Exception <span>is</span> BusinessException)
            {
                <span>throw</span> <span>new</span> HttpResponseException(<span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content = <span>new</span> StringContent(context.Exception.Message),
                    ReasonPhrase = <span>"Exception"</span>
                });

            }<br>
            //Log Critical errors<br>            Debug.WriteLine(context.Exception);

            <span>throw</span> <span>new</span> HttpResponseException(<span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = <span>new</span> StringContent(<span>"An error occurred, please try again or contact the administrator."</span>),
                ReasonPhrase = <span>"Critical Exception"</span>
            });
        }
    }

 

Note: Something to have in mind is that the ExceptionFilterAttribute will be ignored if the ApiController action method throws a HttpResponseException.

The code above will always make sure a HttpResponseExceptions will be returned, it will also make sure the critical exceptions will show a more user friendly message. The OnException method can also be used to log exceptions.

By using a ExceptionFilterAttribute the Get() method in the previous example can now look like this:

        <span>public</span> IEnumerable&lt;<span>string</span>&gt; Get()
        {
                DoSomething();

                <span>return</span> <span>new</span> <span>string</span>[] { <span>"value1"</span>, <span>"value2"</span> };
        }


To use the an ExceptionFilterAttribute, we can for example add the ExceptionFilterAttribute to our ApiControllers methods or to the ApiController class definition, or register it globally for all ApiControllers. You can read more about is here.

Note: If something goes wrong in the ExceptionFilterAttribute and an exception is thrown that is not of type HttpResponseException, a formatted exception will be thrown with stack trace etc to the client.

How about using a custom IHttpActionInvoker?

We can create our own IHTTPActionInvoker and add Exception handling to the invoker. The IHttpActionInvoker will be used to invoke the ApiController’s ExecuteAsync method. Here is an example where the default IHttpActionInvoker, ApiControllerActionInvoker, is used to add exception handling:

    <span>public</span> <span>class</span> MyApiControllerActionInvoker : ApiControllerActionInvoker
    {
        <span>public</span> <span>override</span> Task&lt;HttpResponseMessage&gt; InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
        {
            var result = <span>base</span>.InvokeActionAsync(actionContext, cancellationToken);

            <span>if</span> (result.Exception != <span>null</span> &amp;&amp; result.Exception.GetBaseException() != <span>null</span>)
            {
                var baseException = result.Exception.GetBaseException();

                <span>if</span> (baseException <span>is</span> BusinessException)
                {
                    <span>return</span> Task.Run&lt;HttpResponseMessage&gt;(() =&gt; <span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError)
                                                                {
                                                                    Content = <span>new</span> StringContent(baseException.Message),
                                                                    ReasonPhrase = <span>"Error"</span>

                                                                });
                }
                <span>else</span>
                {
                    <span>//Log critical error</span>
                    Debug.WriteLine(baseException);

                    <span>return</span> Task.Run&lt;HttpResponseMessage&gt;(() =&gt; <span>new</span> HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = <span>new</span> StringContent(baseException.Message),
                        ReasonPhrase = <span>"Critical Error"</span>
                    });
                }
            }

            <span>return</span> result;
        }
    }

You can register the IHttpActionInvoker with your own IoC to resolve the MyApiContollerActionInvoker, or add it in the Global.asax:

GlobalConfiguration.Configuration.Services.Remove(<span>typeof</span>(IHttpActionInvoker), GlobalConfiguration.Configuration.Services.GetActionInvoker());

GlobalConfiguration.Configuration.Services.Add(<span>typeof</span>(IHttpActionInvoker), <span>new</span> MyApiControllerActionInvoker());

 

How about using a Message Handler for Exception Handling?

By creating a custom Message Handler, we can handle error after the ApiController and the ExceptionFilterAttribute is invoked and in that way create a global exception handler, BUT, the only thing we can take a look at is the HttpResponseMessage, we can’t add a try catch around the Message Handler’s SendAsync method. The last Message Handler that will be used in the Wep API pipe-line is the HttpControllerDispatcher and this Message Handler is added to the HttpServer in an early stage. The HttpControllerDispatcher will use the IHttpActionInvoker to invoke the ApiController method. The HttpControllerDipatcher has a try catch that will turn ALL exceptions into a HttpResponseMessage, so that is the reason why a try catch around the SendAsync in a custom Message Handler want help us. If we create our own Host for the Wep API we could create our own custom HttpControllerDispatcher and add or exception handler to that class, but that would be little tricky but is possible.

We can in a Message Handler take a look at the HttpResponseMessage’s IsSuccessStatusCode property to see if the request has failed and if we throw the HttpResponseException in our ApiControllers, we could use the HttpResponseException and give it a Reason Phrase and use that to identify business exceptions or critical exceptions.

I wouldn’t add an exception handler into a Message Handler, instead I should use the ExceptionFilterAttribute and register it globally for all ApiControllers. BUT, now to another interesting issue. What will happen if we have a Message Handler that throws an exception?  Those exceptions will not be catch and handled by the ExceptionFilterAttribute.

I found a  bug in my previews blog post about “Log message Request and Response in ASP.NET WebAPI” in the MessageHandler I use to log incoming and outgoing messages. Here is the code from my blog before I fixed the bug:

 

    <span>public</span> <span>abstract</span> <span>class</span> MessageHandler : DelegatingHandler
    {
        <span>protected</span> <span>override</span> async Task&lt;HttpResponseMessage&gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var corrId = <span>string</span>.Format(<span>"{0}{1}"</span>, DateTime.Now.Ticks, Thread.CurrentThread.ManagedThreadId);
            var requestInfo = <span>string</span>.Format(<span>"{0} {1}"</span>, request.Method, request.RequestUri);

            var requestMessage = await request.Content.ReadAsByteArrayAsync();

            await IncommingMessageAsync(corrId, requestInfo, requestMessage);

            var response = await <span>base</span>.SendAsync(request, cancellationToken);

            var responseMessage = await response.Content.ReadAsByteArrayAsync();

            await OutgoingMessageAsync(corrId, requestInfo, responseMessage);

            <span>return</span> response;
        }

        <span>protected</span> <span>abstract</span> Task IncommingMessageAsync(<span>string</span> correlationId, <span>string</span> requestInfo, <span>byte</span>[] message);
        <span>protected</span> <span>abstract</span> Task OutgoingMessageAsync(<span>string</span> correlationId, <span>string</span> requestInfo, <span>byte</span>[] message);
    }

 

If a ApiController throws a HttpResponseException, the Content property of the HttpResponseMessage from the SendAsync will be NULL. So a null reference exception is thrown within the MessageHandler. The yellow screen of death will be returned to the client, and the content is HTML and the Http status code is 500. The bug in the MessageHandler was solved by adding a check against the HttpResponseMessage’s IsSuccessStatusCode property:

    <span>public</span> <span>abstract</span> <span>class</span> MessageHandler : DelegatingHandler
    {
        <span>protected</span> <span>override</span> async Task&lt;HttpResponseMessage&gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var corrId = <span>string</span>.Format(<span>"{0}{1}"</span>, DateTime.Now.Ticks, Thread.CurrentThread.ManagedThreadId);
            var requestInfo = <span>string</span>.Format(<span>"{0} {1}"</span>, request.Method, request.RequestUri);

            var requestMessage = await request.Content.ReadAsByteArrayAsync();

            await IncommingMessageAsync(corrId, requestInfo, requestMessage);

            var response = await <span>base</span>.SendAsync(request, cancellationToken);

            <span>byte</span>[] responseMessage;

            <span>if</span> (response.IsSuccessStatusCode)
                responseMessage = await response.Content.ReadAsByteArrayAsync();
            <span>else</span>
                responseMessage = Encoding.UTF8.GetBytes(response.ReasonPhrase);

            await OutgoingMessageAsync(corrId, requestInfo, responseMessage);

            <span>return</span> response;
        }

        <span>protected</span> <span>abstract</span> Task IncommingMessageAsync(<span>string</span> correlationId, <span>string</span> requestInfo, <span>byte</span>[] message);
        <span>protected</span> <span>abstract</span> Task OutgoingMessageAsync(<span>string</span> correlationId, <span>string</span> requestInfo, <span>byte</span>[] message);
    }<br>


If we don’t handle the exceptions that can occur in a custom Message Handler, we can have a hard time to find the problem causing the exception. The savior in this case is the Global.asax’s Application_Error:

        <span>protected</span> <span>void</span> Application_Error()
        {
            var exception = Server.GetLastError();

            Debug.WriteLine(exception);
        }


I would recommend you to add the Application_Error to the Global.asax and log all exceptions to make sure all kind of exception is handled.


Summary

There are different ways we could add Exception Handling to the Wep API, we can use a custom ApiController, ExceptionFilterAttribute, IHttpActionInvoker or Message Handler. The ExceptionFilterAttribute would be a good place to add a global exception handling, require very few modification, just register it globally for all ApiControllers, even the IHttpActionInvoker can be used to minimize the modifications of files. Adding the Application_Error to the global.asax is a good way to catch all unhandled exception that can occur, for example exception thrown in a Message Handler.

 

If you want to know when I have posted a blog post, you can follow me on twitter @fredrikn

Read More