Thursday, February 14, 2013

Invoke a Task outside of the MSBuild engine

I have been working with RazorGenerator at work to precompile MVC razor views into an assembly, and for reasons that I won’t go into, we were unable to use the included MSBuild task on our build system. The workaround, or hack if you want, was to wrap the call to task within a ConsoleApplication which we would call from our build system.

Here is a simplified version of the ConsoleApp:

static int Main(string[] args)
{
var engine = new RazorCodeGen()
{
ProjectRoot = args[0];
CodeGenDirectory = args[1];
RootNamespace = args[2];
FilesToPrecompile = args.Skip(3).Select(filePath => new TaskItem(filePath)).ToArray();
}

return codeGenTask.Execute() ? 0 : 1;
}

Nothing astonishing here, besides the lack of error handling. We new up a RazorCodeGen task, setup its properties from arguments that we got from the command line, and call execute. If you run this you will get an error that says “Task attempted to log before it was initialized.”. The reason is that the task is accessing the Task.Log property and it is not running under the MSBuild host. To work around this, we can create our own BuildEngine that the Task can use:

public class ConsoleBuildEngine: IBuildEngine
{
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine("[Error] " + e.Message);
}

public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine("[Message] " + e.Message);
}

// Additional methods
}

The interface has a lot of methods, but the only ones you need are the LogEvent and a couple of properties that you can stub. And now, we set our build engine before calling Execute:

var engine = new RazorCodeGen()
{
BuildEngine = new ConsoleBuildEngine(),

// other properties
}

That’s all there is to it! You can use this to unit test your task and Mock the IBuildEngine, or if you live in the dark ages of build systems like I do, you can call into the console app.

- Federico

PS: For extra points, you can format the messages according to MSBuild error message formats to get better reporting when running the task

Sunday, February 3, 2013

MSBuild in-line task to modify file contents during a build

I have been writing some MSBuild scripts for our build server at work recently, and I needed to modify an xml file during the build process. Even though there are a number of community supported libraries out there with tons of MSBuild Tasks, I learned about this cool feature of MSBuild that let’s you write C# code directly on the .targets file for this kind of small things:

Let’s take a look at the .targets file and walk through it:

<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn)
UpdateProjects;
</BuildDependsOn>
</PropertyGroup>

<Target Name="UpdateProjects">
<ItemGroup>
<CSProjects Include="**\*.csproj" />
</ItemGroup>
<Message Text="Updating Projects:[@(CSProjects)]" />
<UpdateProject ProjectPath="%(CSProjects.Identity)" />
</Target>

<UsingTask
TaskName="UpdateProject "
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<ProjectPath Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Using Namespace="System" />
<Using Namespace="System.Linq" />
<Using Namespace="System.Xml.Linq" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var document = XDocument.Load(ProjectPath);
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
document.Descendants(ns + "Project").Attributes("ToolsVersion").Single().Value = "4.0";
document.Save(ProjectPath);
]]>
</Code>
</Task>
</UsingTask>
</Project>


  • Line #3: I believe this is the standard way of hooking into the build events in a .targets file. The “BuildDependsOn” property is defined in one of the common C# targets file, and what we are doing is appending our target to whatever value this property has. By the way, this is preferable than overriding the standard Target elements because you don’t know  if another script is going to override your override !
  • Line #10: This is our custom target, we define a collection of all .csproj files under this directory and then execute a task called “UpdateProject” on each of them.
  • Line #18: This is the definition of the “UpdateProject” task and it contains our code inside the CDATA section! You can also see in line #21 how you can define parameters to pass into the inline task, which then you can access in your code through an auto-generated property of the same name. The code loads the file and updates the “ToolsVersion” attribute and then saves it.

It’s pretty cool that it can be checked into your source control and that it is right in your face instead of hidden inside some .dll with tasks that you know nothing about.


Anyway, hope that helps somebody.


- Federico

Sunday, January 27, 2013

Creating and modifying Excel files in .NET

I had forgotten to post about EPPlus, a library for creating and modifying Excel files in .NET. I used it recently for a website where I needed to import data that was sent on spread sheets. It worked like a charm and the API was very intuitive.

Load content from a posted spread sheet:

using (var stream = FileUpload.FileContent)
using (var excel = new ExcelPackage(stream))
{
// get the first worksheet
var worksheet = excel.Workbook.Worksheets[1];

// get the value of the first cell
var val = worksheet.Cells[0, 0].Value;
}

- Federico

Monday, January 7, 2013

BitBucket: How to link to latest revision of file

On my previous post I was trying to link to file in bitbucket, but if you try to link to a file from the “Source” tab in the repo you will get a fully qualified URL to a particular revision. For example:

https://bitbucket.org/farmas/ccnet-dashboard/src/f918370a2e47d38b3c3d85d2370779368864045f/dashboard.html

I just learned that if you want to link to the latest revision of the file you need to replace the revision number to “tip” or “master” (depending if it is a mercurial or git repo). For example:

https://bitbucket.org/farmas/ccnet-dashboard/src/master/dashboard.html

^_^

- Federico

Sunday, December 23, 2012

Live dashboard for CruiseControl.NET

Today I bring you a simple dashboard for CruiseControl.NET that you can use to show the status of your projects on a screen visible by the team.

To use it, download the dashboard page and place it inside your webdashboard install (C:\Program Files (x86)\CruiseControl.NET\webdashboard). The page will poll your server for all projects and show their status:

ccnet-dashboard

You can look at the demo page to get an idea of how it looks like. I made source code available in bitbucket here: https://bitbucket.org/farmas/ccnet-dashboard.

- Federico

Saturday, December 15, 2012

Knockout.js: Wait for an animation to complete before updating model

Hello! I have been playing a lot with knockout lately and here is a pattern that I use to coordinate changing observable properties in the model until some animation has completed.

Here is the use case: suppose I am building dashboard to display the status of some projects. Every couple of seconds the app polls a service to retrieve the latest information and if the status changes I want to update the UI. Simple enough:

function ProjectViewModel = {
this.name = "Unit test runs",
this.status = ko.observable("Pass")
};
var project = new ProjectViewModel();
ko.applyBindings(project)

// update the status
project.status("Building");

Additionally, I would like to show an animation when the status is changing. In other words, I would like to trigger an animation and when it finishes, change the status. One could call jQuery animate manually, but I wanted the animation to be a knockout binding so that I could reuse it. I wanted the code to “look” like this:

project.beginUpdate(function(){
project.status("Building");
});

BeginUpdate would tell my model that an update has begun and to do anything it needs to do, including any animation, and when that is done, call me back to change its properties. So, let’s start by looking at the changes to the model:

function ProjectViewModel(){
this.isUpdating = ko.observable(false);
this.endUpdate = null;
this.beginUpdate = function(endUpdateCallback) {
this.endUpdate = function(){
endUpdateCallback.call(this);
this.isUpdating(false);
};

this.isUpdating(true);
};
}

“isUpdating” is an observable property that the model uses to advertise that it is in the process of updating, it will be used by the binding. Then when the client calls beginUpdate we notify observers that we have started to update and also setup the endUpdate callback. On my projects I place this on a base ViewModel so that all models inherit them.

Next, let’s look at the declarative binding:

<div data-bind='fadeWhileUpdate: isUpdating'>
</div>

Here we see that we have a custom binding that is triggered by the “isUpdating” property of the model. Quite simple.

And lastly the binding:

ko.bindingHandlers.fadeWhileUpdate = {
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var isUpdating = ko.utils.unwrapObservable(valueAccessor());

if (isUpdating) {
$(element).fadeOut("slow", viewModel.endUpdate.bind(viewModel));
}
else {
$(element).fadeIn();
}
}
}

This is where the animation comes in. If the model is updating, the element is fadeOut, and when that completes we call the endUpdate method of the model. The end result is that the project will fade out, then its status will be changed by knockout and then the project will fade in with the new property.

- Federico

Sunday, November 11, 2012

Missed opportunity to be generous

One day, on my previous job, one of my fellow testers came to my desk all excited about a bug that he had found. He explained the circumstances in which the bug was reproduced and the impact. It was a really nasty bug.

I could have just leave at that, congratulate him and maybe talk about if we could apply the technique he used to find other bugs. But instead, I chose to comment how I had already found a similar bug the day before and that his bug should probably be closed as a duplicate.

You can imagine which course of action would have left my co-worker pumped-up and satisfied with his own skills and work, and which one would leave him feeling that he just wasted his time.

I often tell this story as an example of the many little opportunities that we have to be generous with our co-workers during any day. It also reminds me to keep an eye out for those moments when I can be a positive influence and stay myself from that “show off” nature that sometimes afflicts us.

- Federico