Code review? (nforum 23 April – Göteborg)

No Gravatar

Har du länge drömt mardrömmar om din kod? Eller kanske du är en av dem som önskar att Code Review var en del av er arbetsprocess? Kanske du redan har sådan process? Mattias Jiderhamn från B2B företaget Expert Systems i Alingsås kommer och gästar nforum den 23 april och delar med sig av sina idéer och tankar kring just detta ämne.

Personligen är jag förvånad över att det är så få företag som lägger energi på att införa alla dessa mjuka ting i sina arbetsprocesser. TDD, Code Review, Acceptans Review m.m. Oftast brukar det bero på att de som bestämmer inte själva vart kodare och eller så finns det brister i förståelse för vad en bra process faktiskt innebär.

Jag kan förstå denna sits samtidigt som jag är stor motståndare här. I min värld har jag alltid strävat efter att det inte finns något som heter övertid, det finns inget som heter buggar m.m. Vi vet alla att det visst finns det men om du envist inte vill tillåta dem för dig själv och samtidigt även vill kunna utföra ett bra jobb kommer du automatiskt som programmerare hela tiden tvinga dig själv att hitta de effektiva och smarta vägarna att gå för att just slippa sitta över, slippa buggar m.m. Du nyttjar TDD, du tänker en extra gång innan du hackar din kod. Du bollar idéer med dina kollegor, du ber någon kollega i smyg kolla på din kod m.m. Vi brukar oftast bli kallade de Pragmatiska utvecklarna. Tycker dock det är synd att det inte direkt ligger lika mycket fokus på de Pragmatiska säljarna, de pragmatiska kunderna mot utvecklarna.
Många processer försöker men allt för många förstår inte processerna i sig. De tror oftast det bara är för utvecklarna skall hinna göra med features och missar att de själva egentligen också ingår i processen.
Känslan på en bra konferens brukar i många fall vara ”Det är fel folk som sitter här, vi kan detta, synd våra chefer inte är här och lyssnar på detta, det är de som behöver denna kunskap inte vi…” Du känner igen dig va? Tänkte väl det.

Jag fick höra en skrämmande historia en gång.
Köparen: Vad är detta? Test 40timmar? Code Review 10h? Det har jag aldrig betalt för förut, stryk det.
Säljaren: Ok.

Ta ansvar, stå på dig, kan du inte övertala din överman, gör bevis på att dina idéer faktiskt ger värde.
För så länge du kan leverera lufttid och inte siffror är du oftast rätt rökt. Dvs. kan du inte bevisa att det ger effektivitet kommer du heller inte få till det, då extra arbete (som de ser det) ökar bara deras siffra med den tid som redan finns, de ser sällan att tillägg drar av tid.

Andra ämnen vi tar upp på nforum den 23 April är TypeScript med John Tjust från oss på Squeed.
Även Filip Ekberg gästar oss med att prata om Async i C#.

Vi ses, anmäl dig på nforums egna sida.
www.nforum.se

Mvh Johan

Continuous Delivery

No Gravatar

Fredrik Normén på Squeed kommer den 30:e April att prata om Continuous Delivery och hans erfarenheter kring området i det projekt han sitter i just nu för ett finansbolag. Ni kommer att få grundläggande förståelse kring begreppet Continuous Delivery, samt hur Fredrik och hans team har valt att lösa vissa problem i sitt utvecklingsteam, allt från process, källkodshantering, branchning, automatisering etc,  detta med hjälp av Microsoft Team Foundation Server 2012 och NuGet samt andra verktyg och hack!

Detta är ett event via Swenug som sponsras av Squeed AB.

Anmäl er här:

http://www.swenug.se/events/gbg-continuous-delivery

Global Windows Azure Bootcamp på Squeed HQ

No Gravatar

Lördagen den 27:e Apirl kommer Global Windows Azure Bootcamp att genomföras runt om i världen, över 90 st platser. Vi på Squeed tillsammans med SweNug har valt att sponsra och vara med och arrangera denna bootcamp i Göteborg. Det blir en dag med labbar, så ni som är nyfikna på Windows Azure så är detta är ett bra tillfälle att få möjlighet att testa på och labba med Microsoft cloud services.

Vi börjar kl 10:00 och håller på fram till kl 15:00.

Det finns dock bara 20 platser, så försten till kvarn.

http://www.swenug.se/events/global-windows-azure-bootcamp

nforum 7 feb – Göteborg MVP Triolerna

No Gravatar

Av: Johan Normén

“Allt blir bara enklare…”
– Anders Bratland (MVP)

nforum fick en rykande start med tre av Göteborgs MVP:er (Most Valuable Professionals) inom .Net. Ca 60 deltagare var på plats för lyssna på tre lysande presentationer i sina egna unika upplägg.
Först ut var Anders Bratland där han presenterade Windows Azure Mobile Services. I mina ögon inte så mogen tjänst fast fullt tillräcklig för att komma igång med enkla och även ibland en del avancerade funktionaliteter med databas och server-script vilket utför en bra grund. Den stora tjusningen är att se hur Microsoft öppnat upp sig mer och mer, som Anders själv sa, de är snart mer öppna än några av de som redan länge vart öppna. Det ligger en hel del i det Anders säger vilket är otroligt kul. För vad sägs om Push notification stöd för iOS och Windows Phone (Android är troligen på gång när som.). Enkel copy & paste Autentisering mot de mest använda social medierna så som Twitter, Facebook mfl…

IMG_4366
Anders visade hur man enkelt via Azure kunde skapa sig en mobile tjänst för spara data som skickades från en Windows 8 applikation. Han visade även hur en dynamisk databas kunde byggas upp i backend så man slipper det manuella steget för enklare datalagring. Vi fick även se demo på Autentisering mot Twitter och en presentation om hur Push Notification fungerar. Microsoft har verkligen något här, det är ingen tvekan att inom utveckling har de alltid vart innovativa, det är bara en tidsfråga innan fler börjar förstå fördelarna och tjusningen med Azure.

Andra passet hölls av Fredrik Normén där han under ca 35 minuter gjorde en mycket bra teoretisk presentation av Microsoft satsning kring Continuous Delivery och TFS i Azure. För ca två år sedan pratade Microsoft på sin Build keynote om TFS Azure redan då tänkte jag, detta kommer bli hur grymt som helst. Fredrik presenterade just att så var fallet. Efter Fredriks presentation där han även visade hur lätt det var att komma åt sitt projekt mot TFS i Azure så insåg jag direkt, det är här det är, det är här vi skall vara. Vi är utvecklare vi vill göra glappet enklare mellan drift och utveckling. Vi vill utveckla inte leka dataadministratörer eller hur? Det skall vara enkelt att bygga och få ut sin app, det skall vara enkelt att få en tydlig överblick av sin ”kanban board” eller ”task board” eller vad man nu använder sig av. Det skall vara enkelt att skicka in kod med kvalité efter teamets egen definition på ”Done”.

IMG_4367

Sist men inte minst så kom vi fram till sista passet under dagen. Filip Ekberg gjorde en presentation om förhoppningsvis next generation .Net compiler Roslyn. Vad sägs om att i realtid kompilera C# eller VB .Net kod snabbare än vad den inbyggda kompilatorn i VS .Net kan leverera? Vad sägs om att enkelt med AOP eller Di/IoC ramverk ihop med Roslyn skapa mer eller mindre dynamiska plugin? Eller kanske to m göra sina tester enklare genom att kunna plugga in dina fakes och mocks i realtid m.m.?
Filip demostrerade hur han lätt kunde skapa en plugin som laddades innan en metod exekverades som i sin tur laddade in en texstfil med ren C# kod som Roslyn exekverade innan huvudkoden utfördes. Tekniken är inte ny, AOP ramverk har haft det länge, IoC/Di ramverk har löst det tidigare, ni som lekt med Emit kommer känna igen er. Det nya är hur snabbt och enkelt den gör det jobb man en gång fick skriva själv och då även med snabbare kompilering än tidigare.

IMG_4370

Tack till er som kom och jag hoppas många av er kommer tillbaka till nforum och för er som inte var där, vi kommer tillbaka. Fyra gånger per år tillsammans med Javaforum.

Här kan ni se Filips presentation på Youtube för er som missade den. Tyvärr fick vi inte de andra genialiska dragningarna på film. http://www.youtube.com/watch?v=BIY1QlchFeE

 

OWIN and Razor

Just for fun added a simple support for using Razor together with OWIN. You can read a little bit about OWIN on my previous blog post.

I wanted my example to be similar to the MVC pattern, so I can use a Model, Controller (in this case I use a simple method) and a View. My Model is simple:

    public class Customer
{

        public int Id { get; set; }

        public string Name { get; set; }
}

 

The "Controller" I use will only return the Model, but I can also return a IView and specify which view to be used to render my model. By default convention is used, so the name of the returned Model will be the name of the View to use. Here is my "Controller":

 

    public Customer ListCustomer()
{

       return new Customer() { Id = 1, Name = "John Doe" };
}

 

It just return a new Customer with the name "John Doe".

To configure the use of Razor with OWIN and to setup "routes", I created a RazorConfig class. By using its Get property, I can map a "route" to a "Controller". Here is the RezorConfig class:

 

     public class RazorConfig
{
private readonly Dictionary<string, Func<object>> _get = new Dictionary<string, Func<object>>();

        private IViewLocator _viewLocator;
private IViewParser _viewParser;

        public IDictionary<string, Func<object>> Get
{
get { return _get; }
}

        public IViewLocator ViewLocator
{
get
{
if (_viewLocator != null)
return _viewLocator;

                return new RazorViewLocator();
}
set
{
_viewLocator = value;
}
}

public IViewParser ViewParser
{
get
{
if (_viewParser != null)
return _viewParser;

                return new RazorViewParser();
}
set
{
_viewParser = value;
}
}
}

 

 

The RazorConfig can also be used to specify a custom view locator and a custom view parser. The RazorViewLoctor I have created will look for views in a Views folder. The RazorViewParser will use RazorEngine.

Here is the Startup.cs where I configure the "routes" with the RazorConfiguration:

 

   public class Startup
{
public void Configuration(IAppBuilder app)
{
var razorConfig = new RazorConfig();

            razorConfig.Get["/customer"] = ListCustomer;
razorConfig.Get["/user"] = ListUser;

            app.UseRazor(razorConfig);
}
}

 

The Get property of the RazorConfig will be sued to configure the "controllers" to be used for HTTP Get of a specific "route".

If I do a HTTP GET, e.g. GET http://localhost/customer", the ListCustomer will be invoked. The ListCusomer returns a Customer, so a view with the name Customer.cshtml will be used to render the returned Customer model.

I have created an extension method for the IAppBuilder, UseRazor. UseRazor takes the RazorConfig as a parameter:

 

    public static class RazorExtensions
{
public static void UseRazor(this IAppBuilder builder, RazorConfig razorConfig)
{
if (builder == null)
throw new ArgumentNullException("builder");

            builder.Use(new Func<object, object>(ignored => new Razor(razorConfig)));
}
}


The extension will use the class Razor. The class Razor will handle the HTTP request and make sure the correct view for the request will be written to the HTTP stream:

 

    public class Razor
{
private RazorConfig _config;

        public Razor(RazorConfig config)
{
_config = config;
}

public Task Invoke(IDictionary<string, object> env)
{
switch (((string)env["owin.RequestMethod"]).ToUpper())
{
case "GET":
HttpGetHandler(env);
break;
}

            return Task.FromResult<object>(null);
}

        private void HttpGetHandler(IDictionary<string, object> env)
{
var responseContent = _config.Get[(string)env["owin.RequestPath"]].Invoke();

            var responseHeader = (IDictionary<string, string[]>)env["owin.ResponseHeaders"];
responseHeader.Add("Content-Type", new[] { "text/html" });

            using (var writer = new StreamWriter((Stream)env["owin.ResponseBody"]))
{
var parsedView = ParseView(responseContent.GetType(), responseContent);
writer.Write(parsedView);
}
}

        private string ParseView(Type type, object model)
{
var viewName = GetViewName(model != null ? model.GetType() : type);
var view = model as IView ?? new View(viewName, model, type);

            var viewTemplate = _config.ViewLocator.GetView(null, view);

            return _config.ViewParser.ParseView(view, viewTemplate);
}

        private string GetViewName(Type modelType)
{
var viewAttributes = (ViewAttribute[])modelType.GetCustomAttributes(typeof(ViewAttribute), true);
if (viewAttributes.Length > 0)
return viewAttributes[0].ViewName;

return modelType.Name;
}
}

 

The Razor class can get the name of the view to be used to render a model by convention, or attribute (ViewAttribute on the model).

Here is the Customer.cshtml view I used to see that I managed to get Razor to work:

 

    <!DOCTYPE html>
<html>
<head></head>
<body>
<div id="body">

            <section>                
<div>

                  <hgroup>
<h1>Welcome '@Model.Name' to OWIN Razor Test!</h1>
</hgroup>
</div>

           </section>

         </div>
</body>
</html>

 

The code in this blog post is far from perfect, it just a simple code to render a Razor view for a model. You may notice that I fail to follow the law of delimiters when I Get- and Parse a View ;)

If you are interested in the RazorViewLocator and RazorViewParser code, you can take a look at the code added to the ASP.Net Web API Contrib.

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

Feature enabling when using Branch by Abstraction

Something that I haven't liked for a long time is the use of branches in a projects. I don't like the waste when it comes to merge between branches, "Merge hell". In the beginning of the project I'm in now, it took hours to do merges before a release, both handling conflicts, but also selects different changeset that should include and not include in the merge before a release. The hard part was when a branch per feature was present. Later on this was changed into one single dev branch, but still sometimes two branches may exists. The team was used to work like this from previous projects. To handle this merge hell one dedicated person handle all the merge so the team can work undisturbed (not true, they were involved when conflicts occurs). Even with a dedicated person, the problem still exists, it was just moved. The time it takes to do the merge, was there, the problem with non-disciplines people that did not following the guidelines when it comes to fixing bugs in different branches, or forgot to merge into the main branch, created problems. Wouldn't it be nice to get rid of all the merge problems, let everyone just work in the same branch? There is a way, it may not work for every projects though (sometimes a bigger change may be useful to have in a single branch, but I think it should first be avoided). The solution to the problem is what Paul Hammant calls "Branch by Abstraction".

In short, it's about creating an abstraction over the part of the system that need to be changed, or use an existing abstraction. Implement the new feature side by side with the current production code, and when the new feature is tested and ready for production, switch the old code with the new one, then the old code can be removed. New abstractions added, may also be removed if it's not needed for future use. All code is checked-in into one mainline by everyone in the team. Before a release, a release branch from the mainline is created (used to fix production bugs).

BUT! And it's a BIG but, it requires a lot of discipline among the team members and also a good architecture.

One key "solution" when it comes to work with Branch by Abstraction is to use "feature enabling", to be able to enable features first when it's ready for production.


Feature enabling on an environment level


Visual Studio has support of using compiler directives or build options like Debug and Release etc. I decided to not use those options, instead a flag in a configuration file, the reason is that I want my Continuous Integration still always use a release build, and the deployment of artifacts to the test environment (for the test team) should be in release mode. The same with compiler directives.

Because the build script we use uses XML transforming of configuration files based on the environment it will build and deploy too, I think the configuration file is a good place to add a "featuring enable" switch.

<appSettings>
<add key="FeatureEnableEnvironment" value="development" />
<appSettings>

 

The value of the FeatureEnableEnvironment key in the configuration for the test and development environment is "development". For production it's "production", but this value is ignored. Example of a transforming settings in the configuration file for the production environment:

<appSettings>
<add
key="FeatureEnableEnvironment"
value="production"
xdt:Transform="SetAttributes"
xdt:Locator="Match(key)"/>
</appSettings>

 

The bootstrapper used for configure the object to an IoC Container will read from the setting, and will override the production registration (default registration is for production) with the new object that developers is working on. Here is the bootstrapper for the IoC container, Microsoft Unity 2.0 is used as a IoC contrainer.


public void Configure(IUnityContainer container)
{
RegisterForProduction(container);

if (IsDevelopmentEnvironment())
RegisterForDevelopment(container);
}

private void RegisterForProduction(IUnityContainer container)
{
container.RegisterType<IRejectInvoiceProcess, RejectInvoiceProcess>();
}

private void RegisterForDevelopment(IUnityContainer container)
{
container.RegisterType<IRejectInvoiceProcess, RejectInvoiceProcess_2>();
}

 

When a new feature is under development, an override of the production registration is done within the method RegisterForDevelopment. When the feature is approved for production, the old object will be removed, and the new one will be used instead (probably also be renamed). The override registration in the RegisterForDevelopment will of course also be removed by the team.

Any suggestion and comment of making this even better are welcome, the problem with the blog I use is that there are a lot of spam, so adding a comment to the post will probably disappear among all the spams. So please send med a message on twitter when a comment is added, so I know when to look among all the comments.

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

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.

  using AppFunc = Func<
IDictionary<string, object>, // Environment
Task>; // Done

 

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:


public void Configuration(IAppBuilder app)

public Task Invoke(IDictionary<string, object> env)

 

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:

 

public class Startup : BaseNavigation
{
public void Configuration(IAppBuilder app)
{
Get["/list/customers"] = ListCustomers;
Get["/list/users"] = ListUsers;

app.Run(this);
}

public Task Invoke(IDictionary<string, object> env)
{
switch (((string)env["owin.RequestMethod"]).ToUpper())
{
case "GET":
HttpGetHandler(env);
break;
//...

       }

return Task.FromResult<object>(null);
}

}

 

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


public class BaseNavigation
{
Dictionary<string, Func<object>> _get = new Dictionary<string, Func<object>>();

public IDictionary<string, Func<object>> Get
{
get { return _get; }
}
}


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:

private void HttpGetHandler(IDictionary<string, object> env)
{
var responseContent = Get[(string)env["owin.RequestPath"]].Invoke();

var requestHeader = (IDictionary<string, string[]>)env["owin.RequestHeaders"];
var responseHeader = (IDictionary<string, string[]>)env["owin.ResponseHeaders"];

responseHeader.Add("Content-Type", requestHeader["Accept"]);
env["owin.ResponseStatusCode"] = 200;

using (var writer = new StreamWriter((Stream)env["owin.ResponseBody"]))
{
new JsonSerializer().Serialize(writer, responseContent);
}
}

 

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:

public IEnumerable<Customer> ListCustomers()
{
return new[] { new Customer() { Id = 1, Name = "John Doe" } };
}

public IEnumerable<User> ListUsers()
{
return new[] { new User() { Id = 1, Name = "Super User" } };
}


public class Customer
{
public int Id { get; set; }

public string Name { get; set; }
}

public class User
{
public int Id { get; set; }

public string Name { get; set; }
}

 

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

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:

        private string GetFirstServerItemFromBuild(string buildName)
{
var buildService = _teamProjectCollection.GetService<IBuildServer>();
var build = GetBuildDefinition(buildName, buildService);

return build.Workspace.Mappings.First().ServerItem;
}



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.

        private static VersionSpec CreateDateVSpec(DateTime date)
{
//Format is: D2009-11-16T14:32
return VersionSpec.ParseSingleSpec(
string.Format("D{0:yyy}-{0:MM}-{0:dd}T{0:HH}:{0:mm}", date),
string.Empty);
}



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.

    public Changeset GetLatestChangeset(DateTime versionTodate, string buildName)
{
var path = this.GetFirstServerItemFromBuild(buildName);

            var vcs = _teamProjectCollection.GetService<VersionControlServer>();

var versionFrom = CreateDateVSpec(versionTodate.AddDays(-21));
var versionTo = CreateDateVSpec(versionTodate);

var results = vcs.QueryHistory(path, VersionSpec.Latest, 0, RecursionType.Full, null, versionFrom, versionTo, int.MaxValue, false, true);

return results.Cast<Changeset>().FirstOrDefault();
}

 

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

 

    private static BuildDetail GetBuildDetail(string buildName)
{
var tfsBuildService = new TfsBuildService(
TFS_SERVER_COLLECTION_URI,
USER_DOMAIN,
USER_NAME,
USER_PASSWORD,
PROJECT_NAME);

var lastBuild = tfsBuildService.GetLastBuildDetail(buildName);
var changeSet = tfsBuildService.GetLatestChangeset(lastBuild.StartTime, buildName);

return new BuildDetail
{
RequestedBy = lastBuild.RequestedBy,
Status = lastBuild.Status.ToString(),
FinishTime = lastBuild.FinishTime,
StartTime = lastBuild.StartTime,
LastCheckedInUser = changeSet != null ? changeSet.OwnerDisplayName : lastBuild.RequestedBy,
ChangeSetComment = changeSet != null ? changeSet.Comment : string.Empty,
ChangeSetId = changeSet != null ? changeSet.ChangesetId : -1,
WorkItemTitle = tfsBuildService.GetLatestWorkItemTitle(changeSet)
};
}



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:

 

      public string GetLatestWorkItemTitle(Changeset changeSet)
{
if (changeSet == null)
return string.Empty;

if (changeSet.AssociatedWorkItems == null || !changeSet.AssociatedWorkItems.Any())
return string.Empty;

return changeSet.AssociatedWorkItems[0].Title;
}

 

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

Agenda för nforum den 7:e februari

No Gravatar

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 backend snabbt och enkelt. Vi tar en titt på hur man kommer igång och vad man kan göra. Kan hända att vi springer på lite javascript också!
Anders Bratland – Bratland Consulting AB

18:25 Paus

18:35 Continuous Delivery med TFS Service och Azure
Att leverera oftare skapar förtroende mot beställare och marknaden, det blir viktigare och viktigare att snabbt kunna leverera nya features och få snabbare processer kring “deployment”. Fredrik Normén kommer att visa hur ni kan använda Microsoft Team Foundation Service och Microsoft Azure för att kunna göra en ”Continuous Delivery”.
Fredrik Normén – Squeed

19:15 Paus

19:45 Compilation as a Service
”Vi går mot en ny era där fler och fler vill kunna erbjuda möjligheten att bygga ut system med widgets och plugins. Genom att utnyttja Compilation as a Service kan man göra det enklare för alla parter att skapa plugins som t.ex. aktiveras vid vissa händelser i en applikation.”

Roslyn exponerar C# och VB.NET kompilatorn och vi kan utnyttja detta för att genomföra just ovanstående. Kom och lyssna på mig för att få en introduktion till vad just Roslyn är och hur vi kan utnyttja Compiler as a Service för att möjliggöra plugins i framtida applikationer.”
Filip Ekberg – Star Republic

20:30 Öl, mingel och datasnack för de som vill på The Bishops Arms, Järntorget 6