Sunday, November 25, 2012

Released: Kill long meetings

A lot has already been said and written about meetings, and some have carried the message above par; 'Meetings: where work goes to die'. Today, I'm not going to foul the internet with another rant, but I'd like to show you a small application built over the last few weeks after work.

I regularly find myself building small things as an antitoxin to the regular periods of not writing and shipping code at work. This time, me and @cgeers, built a not-so-serious application that aims to kill long meetings by visualizing the amount of time and money burned in a meeting.

While the application in itself probably will never attract a sustainable user base, development sprouted two useful Twitter Bootstrap plug-ins: a spin edit control and a multi-color progressbar.

Used technology stack 

On the server, we're using Nancy on an ASP.NET host with Razor views. There is hardly anything going on at the server, so we could have picked any server-side framework I guess. We might just be attracted to the as 'little friction as possible' Nancy ethos though. Returning a view, and setting up a route for it, takes just a few LOC.
namespace KillLongMeetings
{
    public class RootModule : NancyModule
    {
        public RootModule()
        {
            Get["/"] = p => View["HomeView"];
        }
    }
}
Next to returning a correct view, we're also making bundles on the server. For that, we're using Cassette for Nancy.

At the client, we chose to use Twitter Bootstrap, because getting a lay-out right that works everywhere these days is hard; we like to spend that time on other things. Next to jQuery - duh, we're using knockout.js as our client-side MVVM framework. This worked out lovely for our scenario, but overall, I'm distancing myself a bit from this framework. It's simple, and extremely easy to get started with, but lots of companies I heard of seem to adapt this as an application framework, which it is not. It's just two-way model binding in the browser. More complex scenarios benefit from a cleaner separation of concerns for validation, working with remote data, application composition and testing. Right now, I'm doing something with angular.js, and it seems very promising so far. It's also a lot less intrusive than knockout.js.

We're using AppHarbor and GitHub for continuous deployment. The production site however is a static site hosted on GitHub pages. Next to serving everything really fast, we're now not paying for anything except the domain name.

You can give it a quick spin at killlongmeetings.com. The source can be found on GitHub. Let us know what you think!

Sunday, November 18, 2012

Released: Nancy.AspNetSprites.Razor

I was setting up a web application that shows an image for each listed product on the home page. When there were a few products, this worked pretty smooth, but as the number of products (and thereby images) increased, performance degraded. The problem is that each image initiates a separate request. The solution for this problem is to reduce the number of requests by combining the images using CSS sprites. Here is an in-detail explanation of how this works.

I remembered the Hanselman writing about a Nuget package which does all the heavy lifting for you: Microsoft's ASP.NET Sprite and Image Optimization.
The ASP.NET Sprite and Image Optimization framework is designed to decrease the amount of time required to request and display a page from a web server by performing a variety of optimizations on the page’s images. This is the fourth preview of the feature and works with ASP.NET Web Forms 4, ASP.NET MVC 3, and ASP.NET Web Pages (Razor) projects.
Installing that package, it dawned on me that I wasn't using Mvc, nor WebForms, but Nancy on an ASP.NET host with the Razor view engine. So out-of-the-box, this package couldn't work for me.

I looked at the project a bit, and discovered that it would be easily portable to Nancy (on an ASP.NET host with the Razor view engine). It consists of three parts: AspNetSprites-Core, AspNetSprites-WebFormsControl and AspNetSprites-MvcAndRazorHelper. I could make use of the core package, and port the Mvc package to Nancy. The port was pretty straightforward; I removed the Mvc dependencies, and returned Nancy's IHtmlString instead of Mvc's IHtmlString. Note that this all still heavily relies on ASP.NET and its infrastructure.

I packaged this project for Nuget yesterday, and the source is on GitHub (under the MIT license).

Here is some documentation shaped as a how to.

Create an Empty ASP.NET Web Application

You can use the ASP.NET Empty Web Application template.

Install the package
PM; Install-Package Nancy.AspNetSprites.Razor
Attempting to resolve dependency 'AspNetSprites-Core (= 0.4)'.
Attempting to resolve dependency 'Nancy (= 0.13.0)'.
Attempting to resolve dependency 'Nancy.Hosting.Aspnet (= 0.13.0)'.
Attempting to resolve dependency 'Nancy.Viewengines.Razor (= 0.13.0)'.
Successfully installed 'AspNetSprites-Core 0.4'.
Successfully installed 'Nancy 0.13.0'.
Successfully installed 'Nancy.Hosting.Aspnet 0.13.0'.
Successfully installed 'Nancy.Viewengines.Razor 0.13.0'.
Successfully installed 'Nancy.AspNetSprites.Razor 0.0.2'.
Successfully added 'AspNetSprites-Core 0.4' to SpritesExample.
Successfully added 'Nancy 0.13.0' to SpritesExample.
Successfully added 'Nancy.Hosting.Aspnet 0.13.0' to SpritesExample.
Successfully added 'Nancy.Viewengines.Razor 0.13.0' to SpritesExample.
Successfully added 'Nancy.AspNetSprites.Razor 0.0.2' to SpritesExample.
This will add the references, alter the web.config, add an App_Sprites folder and add the necessary conventions automatically.

Known issue: depending on the name of your assembly, the AppSpritesConvention might be overwritten by one of Nancy's conventions. While I'm trying to come up with a good solution, you can move the convention to your bootstrapper.

Add images to the App_Sprites folder

Add the images you want to make sprites for to the App_Sprites folder. You can also make use of subfolders. In this example I added two images: image1.jpg and image2.jpg.

A working example

Add a simple Nancy module which returns a view named View.
public class Module : NancyModule
{
    public Module() 
    {
        Get["/"] = p => View["View"];
    }
}
In your view, make use of the Sprite.ImportStyleSheet and the Sprite.Image method to include the stylesheet and images.
<!doctype html>
<html>
    <head>   
        @Sprite.ImportStylesheet("~/App_Sprites")
    </head>
    <body>
    @Sprite.Image("~/App_Sprites/image1.jpg", new Dictionary<string, object>()
    {
        { "alt", "First image" }, 
        { "title","First image" }
    } )

    @Sprite.Image("~/App_Sprites/image2.jpg", new Dictionary<string, object>()
    {
        { "alt", "Second image" }, 
        { "title","Second image" }
    } )
</body>
The outputted HTML and CSS now looks like this.
<!doctype html>
<html>
<head>   
    <link href="App_Sprites/highCompat.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
    <img alt="First image" class="image1.jpg" src="data:image/gif;base64,..." title="First image" />
    <img alt="Second image" class="image2.jpg" src="data:image/gif;base64,..." title="Second image" />
</body>

.image1\.jpg
{
    width:468px;
    height:315px;
    background-image:url(sprite0.png);
    background-position:0px 0px;
}
.image2\.jpg
{
    width:468px;
    height:315px;
    background-image:url(sprite0.png);
    background-position:-469px 0px;
}

I hope it will prove useful for someone in the future. Let me know if you have any feedback or questions.

Wednesday, November 14, 2012

No trains today? No resources were found either.

Belgian's National Railway has decided to strike again today to assail Europe's oncoming austerity. I don't think there is anything wrong with sticking it to the man, or fighting for your right to party, if you believe it is just, yet the preferred tactics don't feel quite right. There must be more constructive ways to get your point across, instead of terribly inconveniencing all those people who give you a job in the first place, your customers. Any takers on proposing better alternatives?

With an average of 7 days of striking per year, it's self-evident that bashing our railway company has become a national sport. I hardly ever participate, but today I'm happy to make an exception. I have something tangible to show; a mini The Daily WTF. This weekend my girlfriend called me over to her desk to show me this.


Taking it out on their IT department might not be completely fair, but I couldn't resist. How do you succeed in putting your home page in production like that? It wasn't even fixed for quite some time. You can see by looking at the url and the source that they're using ASP.NET WebForms, but they must be rolling their own technique for localization. I can't imagine this passing through  acceptance without someone noticing, so it has to be a deployment issue. Was the production build only partially successful? Did those unfortunate few having to come in on weekends for deployment forget to carry out a manual procedure? Did someone bork the production configuration? Why did it take so long to rectify this? Was the gap between the teams responsible for development and deployment too wide? Was there no monitoring for these 'NOT_FOUND' errors? Are they even being treated as such? Did anyone even bother to smoke test after deployment?

Is there anyone else who wants to take a stab at guessing what went wrong here? 

Sunday, November 11, 2012

jQuery-validate error messages in Twitter bootstrap tooltips

At work, we're using the combination of ASP.NET MVC3, jQuery, jQuery-validate, knockout and Twitter Bootstrap on one of our projects. Having postponed looking at the aesthetics of the client-side validation for too long, we eventually found ourselves unsatisfied with the default error labels. Wanting to save on space, we're experimenting with the error messages being shown in a Twitter Bootstrap tooltip. After poking around for some while, I came up with something satisfactory.

In this example, I have a bootstrapped form that looks like this.
@using (Html.BeginForm("ChangePassword", "Account", FormMethod.Post, new { @class = "form-horizontal"})) {
    <div class="control-group">
        <label class="control-label">Old password</label>
        <div class="controls">
            @Html.PasswordFor(m => m.OldPassword)                            
        </div>       
    </div>
    <div class="control-group">
        <label class="control-label">New password</label>
        <div class="controls">
            @Html.PasswordFor(m => m.NewPassword)                
        </div>
    </div>
    <div class="control-group">
        <label class="control-label">Confirm password</label>
        <div class="controls">
            @Html.PasswordFor(m => m.ConfirmPassword)                
        </div>                            
    </div>  
    <div class="control-group">
        <div class="controls">
            <button type="submit" class="btn btn-primary">Change password</button>
        </div>
    </div>
}
To make the error messages show up in tooltips on the input controls, I eventually ended up with the snippet below.
$.validator.setDefaults({
    showErrors: function (errorMap, errorList) {
        this.defaultShowErrors();                            

        // destroy tooltips on valid elements                              
        $("." + this.settings.validClass)                    
            .tooltip("destroy");            

        // add/update tooltips 
        for (var i = 0; i < errorList.length; i++) {
            var error = errorList[i];
                         
            $("#" + error.element.id)
                .tooltip({ trigger: "focus" })
                .attr("data-original-title", error.message)                
        }
    }
});
I'm setting a custom showErrors function to extend the behaviour of the jQuery validator. I don't want to lose the default behaviour (highlighting etc), so I start with invoking the default showErrors function, to then destroy the tooltip on all valid input elements and to finally add or update the tooltip and its title on all invalid input elements. The errorList argument holds all the information I need for this; an array of invalid elements and their corresponding error messages.
[Object, Object]
> 0: Object
>> element: <input>
>> message: "The Current password field is required."
> 1: Object
>> element: <input>
>> message: "The New password field is required."
> length: 2
The end result looks like this.


Sunday, November 4, 2012

Commuting? Have you done the math?

On my first job interview, over four years ago, I was asked whether I would relocate if I was hired. Back then, I still lived in the Campine region with my parents, while the Ferranti Computer Systems headquarters are in Antwerp. I thought about it for a few seconds and told the interviewer that I didn't plan on moving out of my parents' place in the first few years. Besides, the distance isn't that great; it's only 60km (=37 miles) of highway, how bad could it be? Apparently that reply was good enough since I was given the job a few days later.
Well, that commute grew old rather quickly though. Turns out that those 60km of highway are one of the most saturated pieces of asphalt in Belgium, with up to 20 to 30km (12 to 18 miles) of systematic traffic jams every damn single day.

And when you have to take the car to work, the options you have to spend that time useful are rather limited; the only things I could think of at the time were listening to podcasts and reflecting on the things of life. The latter made me hate my situation even more, so that wasn't an optimal pastime.

I actually never had the courage to calculate how much time I wasted in traffic those three years. Today, I'm in a far more comfortable situation, so plucking up courage wasn't that hard anymore. With 52 weeks in one year, and 5 days in a week, there are 260 business days in one year. In Belgium, we don't work on all of those though, so I had to subtract 25 vacation days, and 6 holidays; bringing down the number of working days to 229. My rather optimistic guess is that I was on the road for somewhere around 140 minutes each day. So 229 days multiplied by 140 minutes comes down to a total of 32060 minutes, or 534 hours, or 66 working days. Gasp! 66 working days per year gone to waste. I knew the numbers were going to be bad, but this is even a lot worse than I expected.

Easing this burden isn't trivial though, and I can only think of a few feasible options, excluded changing jobs:
  • Travel outside rush hours: leave for work very early or really late. I experimented with the former for quite some time, but I never really got used to it. It also seems a bit counterproductive to have your rhythm be too different from that of your team members. I'm guessing your family life will suffer as well, but I can't be the judge of that.
  • Bring the office to you. In this technological day and age there are hardly any sound arguments not to encourage working from home. Yet, the classical enterprise shies away from embracing it and seems to be more comfortable sticking with the status quo than improving working conditions for their employees. 
  • Relocate. This option might be drastic, yet it's the one with the biggest probability of success.
  • If possible, use public transport. Even if your total time enroute increases, you will have more time to do something productive while you take away some of the frustration that comes along with commuting by car.

Earlier this year, I moved from one of the (work-wise) more remote corners of Belgium to a far better located area; the heart of Antwerp. To top that, I'm now living and working within walking distance of the train station, so I'm taking the train on a daily basis. In total I'm still on the go more than two hours, but I can now spend 75% of that time usefully. That's 43 working days extra to spend each year! I've made a habit out of using that commute time for self-study; reading, working on side projects and writing. This seems to have considerably affected my state of mind for the better. I get home at night, and I've already spent a considerable amount of time challenging myself intellectually. I now no longer stress about practicing less 'productive', but very enjoyable activities; such as playing the guitar, working out or doing stuff around the house. I get to have both now.

There are only so few hours in a day, having to spend a considerable amount of that time just to get somewhere seems such a waste. Every day we try tools and techniques which save us a few minutes, and should help us improve the quality of our day-to-day lives, yet we often disregard optimization of the biggest time hog of them all. It goes without saying that those numbing long commutes by car aren't something that I'll ever decide to go back to lightly.

When I read my Twitter feed in the morning, I see a lot of other Belgian people complain about their commute. Is this a Belgian thing only? Which means of transport do you use for the commute? How long are you on the road each day? Have you done the math? 

More comments on Hacker News.

Thursday, November 1, 2012

NancyFx and bundling with Cassette

Working on a new side project built using NancyFx, I wanted to bundle and minify my css and script resources. Looking into the options, Cassette (*) seemed the most obvious option.

Since I struggled with the implementation a little bit, I documented the process below.

1. Cassette.Nancy package

Add the Cassette.Nancy package to the project.
PM> Install-Package Cassette.Nancy
Attempting to resolve dependency 'Cassette'.
Attempting to resolve dependency 'AjaxMin (= 4.60)'.
Attempting to resolve dependency 'Nancy'.
Attempting to resolve dependency 'Nancy.ViewEngines.Razor'.
Attempting to resolve dependency 'NLog'.
Successfully installed 'AjaxMin 4.60.4609.17023'.
Successfully installed 'Cassette 2.0.0'.
Successfully installed 'NLog 2.0.0.2000'.
Successfully installed 'Cassette.Nancy 2.0.0'.
Successfully added 'AjaxMin 4.60.4609.17023' to Project.
Successfully added 'Cassette 2.0.0' to Project.
Successfully added 'NLog 2.0.0.2000' to Project.
Successfully added 'Cassette.Nancy 2.0.0' to Project.
2. Bundle configuration

The Nuget package adds the CassetteBundleConfiguration class to the root of your project.

I'm lucky to have my assets structured rather simply, which makes setting up the bundles take little to no effort.

public class CassetteBundleConfiguration : IConfiguration<BundleCollection>
{
    public void Configure(BundleCollection bundles)
    {
        bundles.AddPerSubDirectory<StylesheetBundle>("Content");
        bundles.AddPerSubDirectory<ScriptBundle>("Content");
    }
}
Each sub-directory now becomes a bundle.

To arrange the files in the bundles, I added a bundle.txt file that defines the order to each sub-directory. For the css sub-directory it looks like this.
bootstrap.css
bootstrap-responsive.css
docs.css
3. Using bundles

To use these bundles in my view, I first reference them, for then to render them in their optimal location - css in the head section, and scripts just before the closing body tag.
@using Cassette.Nancy

@{
    Bundles.Reference("Content/css");
    Bundles.Reference("Content/scripts");
}

@Bundles.RenderStylesheets()
@Bundles.RenderScripts()
4. First try

When you now browse to your application, and view the source, you'll notice that Cassette is already in play, yet the assets are not bundled nor minified.



5. Enable optimization

Apparently when using NancyFx, you need to explicitly enable optimization.
By default, the output from Cassette is not optimized. When output is optimized, Cassette modules are combined (bundled) based on the configuration and sent to the client as a single lump per bundle instead of lots of individual files.
When you're using ASP.NET MVC, this is dependant on the compilation debug switch.

The best place to enable this, is probably in your bootstrapper.
public class Bootstrapper : DefaultNancyBootstrapper
{
    public Bootstrapper()
    {
        Cassette.Nancy.CassetteNancyStartup.OptimizeOutput = true;
    }
}
6. Second try

This time around, the assets do get bundled and minified.



* It wasn't until today that I noticed the asset in Cassette.