Lets compare tools!

Continuous Integration Tool Comparisons

Version Control

Comparison of version control tools
TFS Subversion Mercurial SourceSafe
Pros Industry standard. Works very well. No learning curve from SS. Works very well, easy learning curve from SS. Faster than TFS. Integrates well with most issue trackers. Very fast. Designed for geographically dispersed teams. Superior branching merging and conflict resolution. None. It is capable of destroying a dev teams best efforts, eats babies and causes cancer.
Cons Bandwidth hungry on each developer commit and when getting latest versions. Can be slow if the connection isn’t great. Merging conflicts can be painful. Merging conflicts is less than optimal. Steeper learning curve for devs who’ve never used it. Some training required. This is bleeding edge. It’s a turd in every way. Microsoft never used it internally for dev work and are still embarassed by its existence.

Issue Tracking

Comparison of issue tracking tools
TFS Bugzilla Jira
Pros Industry standard. Works very well. Integrates by default with VS IDE. Industry standard. Works very well. Works very well. Easy web interface.
Cons The web interface (for testers, BA’s and other non-developers) takes a little while to get used to. The web interface (for everyone) takes a little while to get used to. Commercial product. There is a license cost.

Build, Test and Publish Automation

Comparison of build, test and publish automation tools
TFS CruiseControl.Net TeamCity
Pros Industry standard. Works very well. Industry standard. Works very well. Open Source and free. Easy to configure. Easy to maintain. Uses developer machines to produce builds and can be hosted on a shared server.
Cons Resource hungry. Needs 2 dedicated servers. One for TFS, one for its database. Commercial product. Costs a kings ransom. Configuration is done in XML files and is quite complex. Needs a dedicated (perhaps virtual) server as it does its own builds. Commercial product. Costs $2,000 (USD).

comment on Lets compare tools!

A walk in the swiss park

So, I've been here a little over a week now. I have a little better picture of life in the land-locked island and I've had some conversations that have profoundly affected my outlook. Mostly my experiences are reinforcing the hope that Jo, Ken and Selena will join me out here when they can. This is a place where wholesome living inspires people and families in fundamental ways. More on that later, but I want to describe the experiences and interactions that are influencing my thoughts.

My first Monday here began with my last hotel breakfast, dressing convincingly (I thought) for my new job, an 8 am checkout and then waiting around outside the office so that I could turn up spot on 10 minutes before the agreed 9 am start. You only get one shot at a first impression and for the first time in a while, this one was important to me. If my new boss was impressed with the tie and my clumsy impression of an ironed shirt, then he didn't show it. He is one of those no-bullshit, straight to the point kind of people and after introducing me to everyone we went out onto the balcony where he launched into the informal part of the interview that we hadn't had opportunity to deal with over the phone. I assume he chose the balcony so that if he needed to dispose of me quickly, it would be easy to make it look like an accident. The questions came thick and fast but weren't technical. Why do you have a yank accent, stupid hair, dark skin and an English passport? Why are you in Switzerland? Do you know what you're doing? Those weren't his actual words but you get the idea. In the event, he postponed judgment and while the balcony is always there, I haven't become acquainted with its function.

The company builds tools that help manufacturer’s measure efficiency. More to the point, the company was initially a boutique development shop built single handedly by my new boss. It became so successful that he was inevitably given an offer too good to refuse, to sell it to its new American owners. He's justifiably proud of the achievement but explains the history in a way that is matter-of-fact and there is no hint of arrogance. The clients are the biggest international household brands and the stakes are high. The customers buy our products because they want efficiency improvements and my boss understands efficiency better than anyone I've met. So here I am in the land of efficiency, working for clients who want efficiency more than anything and working with a team of efficiency experts. I wonder what single quality will impress this crowd. I can guarantee you that bullshit will not go far in this environment. Those who know me know I've got a challenge ahead. The upshot is that I left England to get away from bullshit so in some ways, the opportunity is tailor-made. I don't want to talk about the job anymore, so suffice it to say that I'm happy with it, my technical skills are appropriate and in my first week I have delivered value that justifies my remuneration. Oh and I haven't been scraped off the pavement under the bosses balcony - yet. One practical discovery I have made about the Swiss work environment is that the dress code is smart casual. Stylish jeans and a comfortable but professional shirt seem to be the norm. My tie only proved that I have never worked here before.

The other goings-on are that I have met someone who has taken it upon himself to find suitable long term employment prospects for me here in Switzerland. You might have noticed that I'm not naming names here and that's simply because I haven't asked anyone if they mind being written about. I'm just writing about my experiences and I'll leave the significant people anonymous. In the case of this particular guy (we'll call him Val for simplicity), though we've only met a couple of times, I'm convinced he knows what he's doing. Val has had his share (and then some) of life's ups and downs and his personality shows both his strength and his hard-knocks derived wisdom. We celebrated his birthday on Saturday which involved all of the things Val takes seriously. In his words "I take very seriously wine, women and war". The evening began with a few drinks in one of Fribourgs terrace cafes. We were joined by Val's daughter, his closest friends and their children. We were entertained by a group of lads, and their cow who claimed to be able to sing in any language so our request for Portuguese was unwise as the resulting jibberish was impossible (for us) to identify and we were obliged to hand over princely sums to fund their continued festivities. We were rewarded with a condom for each adult and a raunchy cartoon for Val and co's wives and teenage daughters. I don't know if any part of either the cow, the singing, the condoms or the cartoons are a Swiss tradition or if we were just witness to the randomness of the universe but the important thing is that we were entertained and in a way that I have never experienced before. Fribourg is where the Swiss come for Raclette and the restaurant most renowned for serving it is where we ate. Everyone under 20 left the restaurant seconds after entering because the smell of the cheese did not agree with their refined palette. They recovered by eating a few doors down at a fine American establishment where meals are of the happy variety. We, on the other hand, were treated to something called Steak au Paris which translates exactly as "buttery sex on a silver platter". It was delicious and I cried. Val's friend demonstrated the seriousness the Swiss employ when choosing wine and it is difficult to say whether it was me or the restaurants bank manager who was more delighted by his expert approach and our reckless abandon with the waiters favourite phrase: "one more please". The barmaid was no match for Val's charms and he completed his 3 W's when a neighbouring table roused the warrior with an insult directed at someone Val describes as his very best friend. The man is clearly as good as his word and I look forward to working with him when my current contract expires.

It seemed like a good idea when I agreed to it but I misunderstood what is meant by "going for a walk" when the phrase is used in Switzerland. Four hours of sleeping off the evening’s festivities, I tossed an apple given to me by the landlady, what was left of a day old loaf of bread, a block of gruyere and a stack of cured, sliced meat of unidentified but presumably animal origin into the hiking rucksack I found in Bern on Saturday. I neglected to add a jacket because the heat wave here has exceeded 30 degrees pretty consistently every day. I laced up some proper hiking boots (which turned out to be the smartest thing I did all day) and joined the whole of the rest of Switzerland on a 7 am train to the Alps. I was met in Bern by a collection of ex-pats who were about to open a whole new can of whoop-ass-crazy for me to enjoy. The train delivered us to the foot of the beautiful but imposing mountains at Frutigen. A succession of busses wound us up the mountain roads to Adelboden and a cable car completed the "you should have brought more than a t-shirt" message by hauling us through the atmosphere and dropping us at the foot of an ancient glacier. We trundled through the clouds and bullied big Swiss cows out of our path until we reached a farmers hut where an old man who quite literally has his head in the clouds all the time was grinning from ear to ear while he redirected us from the obvious path we had taken to the little rut behind his house that left the plateau and disappeared into the mist above us. I was suspicious immediately but our guide seemed to trust the grinning patriarch and our new course was set. What followed was what I hope will become the first of many challenging adventures in these mountains. We hauled-ass up steep, rocky inclines traversing muddy slopes, streams and snow-covered ledges. We climbed for several hours and passed through layers of mist and clouds. Every now and then the wind would blow a cloud off of you and you would see either a spectacular plateau or canyon below or occasionally a glimpse of the white rock tops hundreds of metres above. Every scene was epic but the truth is that this is the most exercise I have had since that time years ago when I had to go to the shop without the car. My breathing resembled a hyper-ventilating donkey, giving birth to a breached hippo whilst overcoming one of those laughing fits you get when your mate delivers the punch line on a filthy joke and you've just filled your mouth with spicy kebab and beer. Pretty picture, innit? In spite of it all, I was motivated by the fact that to every one else we were literally out for a stroll. Some others had moments where an obstacle proved challenging but overall the Swiss hiking tradition sees walks like this as a fortnightly excursion and an opportunity to take in the views while getting some of that fresh air and exercise that give this country the decade and a half of extra life expectancy it enjoys. Sixteen hours after setting out, I took my boots off, drank a glass of Chianti and sank into the deepest sleep I've had in forever.

Perhaps you can see from my first week of Swiss experiences that I am happy to be faced with some new challenges. That the accomplishments are as rewarding as they are genuinely difficult to achieve. That as a person, I needed to be presented with non-routine obstacles, a job that will push me out of apathy and a lifestyle that involves physical challenges. I guess we'll see. I find myself telling all my new friends and acquaintances about my family and about how much I'll enjoy their company if they get out here. I know they'd love it too. Jo has her own reasons to have an affection for Switzerland as she lived here and already knows most of what I'm discovering. Kenny was born just down the road from Fribourg town in Riaz. I think it would be a big deal for him to move here but honestly, I can see a vibrancy and a joie de vivre in the young people here that I know he would connect with. Bean loves adventure and discovery, I couldn't help but think that if she were looking at the rocky vistas I saw yesterday, she'd see a playground full of climbing frames. She woulda breezed that walk where I had a hippo too.

Adelboden to Kandersteg

comment on A walk in the swiss park

Day 2 - Spainzerland

Sunday's here in Fribourg are quiet. Everything is closed except for the 50% of establishments that serve beer or ice cream. If you need shampoo and don't want to use sherbet, you're out of luck. I alternated between taking long walks and sleeping off yesterdays boozing instead. I also wandered into the ravine that the town overlooks. At the bottom I find that everyone has stripped off and jumped into the river. It's a striking scene. Next time you're sipping your Evian you can picture the town of Fribourg playing in your bottle.

I confirmed yesterday's discovery that the signs outside of cafes that read "Sandwich 5FR" are just a ruse to get you sat down and ordering beer. It is in fact impossible to get a sandwich or find a cafe with a working kitchen after 1pm and before evening. I ended up spending a kings ransom on some fois grois because I was ravenous and it is the only thing a chef can be motivated to produce when he has already begun his afternoon of smoking, drinking and eating ice cream. One or all of these activities seem to preoccupy the town till nightfall when food is back on the menu and Spain has started to destroy the dreams of Dutchmen everywhere.

When their brothers in South Africa complete the task every Spaniard in Fribourg gets into a car, turns on the horn and hazards, drapes their wives and daughters out the windows and drives like a Pamplona Bull through the high street. Charming. I suspect they're doing the same back in Reading and everywhere else. I guess they deserve it being new to winning and all that.

I found the building where my new employer is based so that makes arriving on time for my first day of work tomorrow more likely. I'm anticipatorily excited about it all. The office entrance looks a little depressing but I'll read the intro before judging the book by it's cover. If my suspicions are correct, I'll spend most of my time with clients and at their offices so who cares what our office looks like right? I'm more concerned with the business culture, decision making processes and especially what sort of team I'll be working with. Bring on the morning!

comment on Day 2 - Spainzerland

Switzerland - Impressions from day one

There's a new chapter in my life with a move to Switzerland and a new job. It's my first time here and I arrived with some misconceptions. For the most part, I'm a blank slate and just want to see what this place is all about.

I've had conflicting reports about what to expect. Some friends tell me that I'll get bored quickly because it's a country of rules and fines where everything is done by the book. Others have said there's real depravity here?!

The flight out of Heathrow was scheduled for 6:45 am but left at 8:15 after the BA engineers fixed the steering. I suggested that we could have just flown in a straight line and to hell with the steering but the pilot said his training didn't cover that. - Shame. Arriving in Geneva the Swiss put on a much better show, laying on an entire double decker train in honour of my arrival. Within 20 minutes of landing, I was speeding effortlessly alongside lake Geneva which has (as you probably know) an impossibly impressive, alpine backdrop. The view is so perfect, it seems fake.

I spent a few minutes trying to find 2nd class accomodation to match my ticket but there isn't any. Some carriages are blue and some are red but all the seats are bigger than English first class. The red carriages cost twice as much as the blue and so far as I can see, the only diference is that in red carriages, you don't have to share space with the likes of me. Not that you'd have to in blue either, if you want a whole carriage to yourself, you can have that too. Apparently, the Swiss don't share the British obsession of making passengers envy the luxury enjoyed by a tinned sardine.

I could spend a lot of time describing the difference in trains here and there but suffice it to say, the Swiss have ruined Southwest Trains for me forever. I suggest that all British rail executives should be allowed a single journey on a Swiss train before commiting compulsory mass Hare Kare.

No-one here is impressed by the English language. So it feels like I've just confessed to being dim when communicating with the locals. It goes something like this:
(me) Bonjour!
(local) Bonjour, bla di bla ti da la bla (<- this is real french)
(me) argh!
(local) Ahh, Sie sind deutscher.
(me) no
(local) Le mie scuse Signore, tu sei italiano. (<- they sing and gesticulate this bit)
I make no sound, stare at the floor and sweat a little
(local, looking agitated) No recibimos muchos españoles aquí.
(me) I'm sorry. Do you speak English?
(local) Ohhhhh, I see, you are an idiot. Yes, never mind, I speak idiot too! I once went to idiot island, they had strange ideas about what a train is.
(me) at least we drive on the right side... oh... no you're right... we are idiots. I forget what I wanted to ask you.
(local) Whatever, can I buy you a beer?

I haven't even been here 24 hours and I've already eaten at a table for 8 new friends, been to a Jazz festival, got roaring drunk and I'm looking forward to the next 24.

Fribourg
Elaine (on Sunday, July 18, 2010 at 11:43 PM):
I'm so glad you wrote this.........so ignore my request for info. Did you get some accomod.? Hope you're OK......sounds like you are doing just fine. Love you loads Rob XX Mom

comment on Switzerland - Impressions from day one

When random is too consistent...

A while back I posted an extension method for shuffling Lists in C# using the Fisher Yates Shuffle. The algorithm is valid but my implementation relied on the default random number generator in .Net (System.Random) which is flawed since it generates a predictable sort of random.

Here's a correction that uses a more random sort of random from System.Security.Cryptography:
public static void Shuffle<T>(this IList<T> list)
{
var provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
var box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
var k = (box[0] % n);
n--;
var value = list[k];
list[k] = list[n];
list[n] = value;
}
}

Here's a simple test you can run to demonstrate the difference in the algorithms:

using System;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace AlphabetSoup
{
class Program
{
static void Main()
{
var alphabet = GetAlphabet();
foreach (var c in alphabet)
Console.Write(c);
Console.Write('\n');
Console.Write('\n');

Console.WriteLine("System.Random:");
for (int i = 0; i < 10; i++)
{
alphabet = GetAlphabet();
alphabet.QuickShuffle();
foreach (var c in alphabet)
Console.Write(c);
Console.Write('\n');
}
Console.Write('\n');
Console.Write('\n');

Console.WriteLine("System.Security.Cryptography:");
for (int i = 0; i < 10; i++)
{
alphabet = GetAlphabet();
alphabet.Shuffle();
foreach (var c in alphabet)
Console.Write(c);
Console.Write('\n');
}
Console.Write('\n');
Console.Write('\n');

Console.ReadKey();
}

static List<char> GetAlphabet()
{
var alphabet = new List<char>();
for (int c = 65; c < 91; c++)
alphabet.Add((char)c);
return alphabet;
}
}
static class Ext
{
public static void Shuffle<T>(this IList<T> list)
{
var provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
var box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
var k = (box[0] % n);
n--;
var value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static void QuickShuffle<T>(this IList<T> list)
{
var rng = new Random();
var n = list.Count;
while (n > 1)
{
n--;
var k = rng.Next(n + 1);
var value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}

Here's the output on my machine:
Tom (on Thursday, August 12, 2010 at 12:00 PM):
The parameterless constructor for Random seeds with the system clock, so if your loop time is under a millisecond it'll be using the same seed for subsequent calls to QuickShuffle(), hence the repetition.

From an implementation perspective it's pretty nasty that the randomness of the RNG is based on something like that. I've never been sure why they didn't make the whole thing static and have done with it (thread safety, perhaps?).

comment on When random is too consistent...

What can I do for Haiti?

I want a way to help someone in Haiti. How can I, as an individual help another individual, in that tragedy. I don't want to contribute money to some organisation that will deliver a band-aid to some injured person. I want a way to contribute to the healing of the human spirit in Haiti. Should I go there? Is there someone there who I can help in a meaningful way?

comment on What can I do for Haiti?

My Cars

Tonight, I was thinking about all the cars I've gotten through over the years and I thought it might make an interesting project to find pictures of all of 'em. So, for fun, I'm gonna do it here on my blog. I might have to revisit and edit the post a few times to complete the whole story.


Here they are in the order that I had 'em:


Ireland



  • '81 Ford Fiesta - In '93, I was hitch-hiking in Ireland and after standing in the rain for what felt like hours, I bought an old red, rusty Fiesta for £80. It had been staring at me from across the road. I was 17 and didn't even have a driving license.

  • '81 Landcruiser BJ40, 3.0 D SWB - I wanted a Landcruiser since I was 7. When I found this one on a beach in Wicklow, I promptly swapped the Fiesta, my camera and £490 which was £10 short of all the money I had to my name.

  • Triumph Acclaim - £25 Scrapyard special, complete with wobbly gear stick (not the one in the pic, though it was red). I bought this because I needed to collect a replacement engine for the BJ40 from a breaker who was on the other side of Ireland. It survived the trip and I was relieved to drop it back at the scrapyard.


Canada



  • Pontiac Firefly - Generously given to me by Jo's brother in Montreal although it was a sorry little car and I ran it right into the ground. Cost me $20 to get rid of it when I was done.

  • Isuzu 4wd King cab pickup - Big black monster. Bought on finance when I couldn't really afford it. Missed a few payments and had to give it back when we moved to Ontario. Shame.

  • Chevrolet S10 pickup - Also given to me by Jo's brother. This one had belonged to a boy racer and had fat corvette racing alloys with slick tyres, a noisy muffler and tinted windows. The truck bed was useful though as I was a painter and tradesman at the time.

  • 2 VW Jetta's which I swapped the chevy for. Both nice little cars. The wife crashed one and I forgot where I parked the other. Never found it.


UK



  • '83 Ford Fiesta - Total heap bought for £160 on arriving back in Cornwall. Lots of rust. Lasted 6 months tops.

  • Fiat Uno - Suprisingly fun. Bought cheap from a friend. Jo ran this one into the ground.

  • VW Golf - Made several trips across France. Lot's of fun, till it fell apart from abuse and accidents.

  • VW Jetta, 1.8 - Expensive car. Crashed & scrapped on the first day I had it.

  • Rover hatchback - Ugly and complete rubbish. Had to go to the garage for fixin' every week.

  • Audi 80, 2.0 - Nice car but the 3 speed auto transmission was not ideal for my long journeys. Eventually, the head cracked and the car was toast.

  • Peugeot Estate - No idea what model this was. It was diesel, had 200,000 miles on the clock and cost £100. I bought it instead of a train ticket because I was far from home. I got a couple long journeys out of it and gave it to my brother who abandoned it in the heathrow car park. I got letters for a few months afterwards asking me to come get it, then one more saying not to bother as it had been destroyed.

  • Ford Mondeo, 2.0 TD - Motorway cruiser. One of my friends said it made me look like the scummy man from the Arctic Monkeys song. I lost many points driving this up and down the M5. When I eventually lost my license I gave it to my brother. He crashed it and it was no more.

  • '97 Rover 416i - Quick, but fussy when wet. Got broadsided by an old Volvo on the Basingstoke bypass. Then got left for 3 months in the airport carpark while I was in Costa Rica. When I got back, I paid someone to take it away.

  • '02 Skoda Octavia Estate, 2.0. Newest car I ever owned. Very comfortable but abused by previous owners. After I spent loads fixing everything and got it running like clockwork, I got it stuck whilst towing a heavy load in an awkward steep driveway. A friend tried to help but burned out the clutch. Then a mad Irish woman towed me 100 miles with a rope wrapped round the bumper. The series of unfortunate events distorted the shape of the car and I lost interest in it.

  • '79 Landcruiser BJ40, 3.0 D SWB - Attempting to rekindle a dream, I bought this unfinished project which doubled as the heavy load I spoke of earlier. I also failed to finish the project and it is now sitting in someone else's pile of unfinished projects.

  • '97 Landcruiser KZJ90, 3.0 TD SWB - Fuel guzzling beast which I now bomb up and down the M4 with.


comment on My Cars

Why Google is incapable of "Do no evil"

I like Google services. From a productivity perspective, there is an endless river of tools and services raging out of the Mountain View Chocolate Factory. I consume most of these services and for the most part, recommend them to others. But today, I am taking a new stance because Google has gone too far. Picture this scene:

Joe Bloggs has a GMail account that contains every email he has sent or received since the "Gmail is different. Here's what you need to know" mail he received in 2003. It includes order confirmations for everything he has bought online since then. We won't even think about the scope of how much can be understood about Joe from the travel reservations, book, film and music titles he has bought, gifts he has sent to friends and family and the plethora of personal information from all those receipts. His mail account also includes memories of personal moments like intimate letters expressing love, anger, depression and stress. It includes job applications and correspondence with business associates. It's a treasure trove of the communications that have affected his life for the last 6 years.

As well as the email account, Joe uses Google Reader to keep abreast of his personal and professional interests. - Feeds that link him to the people and sources he respects. He stores his credit and debit card information in Google Checkout. Joe keeps important and collaborative records in his Google Docs account. His appointments are all organised under Google Calendar. Everyone he knows is catalogued under Google Contacts. His Google Groups account provides more insight into the communities and interests that make him tick. Joe's YouTube account keeps a history of the sort of entertainment that he enjoys. His Picasa web albums contain photographs of him, his friends and his family. His Google Web History profile can remember every word he ever typed into the only search engine he ever uses.

If you are not already terrified by the picture painted above, add to the mental catalogue that next year Joe is going to get a new Google Android powered phone. His phone will use Google Maps and Latitude to communicate his every geographical move to Mountain View. His phone features Google Voice which will transcribe every telephone call and voicemail into a communication log. The smart phone links his other social networking accounts to his Google account and imports the private profiles of all of his contacts.

At some point I realised that the information I entrust to the Oompa-Loompa search empire is considerably more than I would entrust to any individual. It's time to do something about this. As much as I enjoy the convenience and price of Google services, I simply cannot stomach the potential for privacy violation that they have created.

comment on Why Google is incapable of "Do no evil"

Some fascinating graphs of World Bank Data


Source: Official Google Blog: World Bank public data, now in search


comment on Some fascinating graphs of World Bank Data

Deep cloning without serialization.

A client asked me to write an object cloning implementation today that did not use serialization. Sometimes you cannot mark the types you need to clone as serializable.

I ended up with the code below. In the future, I'll modify it to use IL or something to lose the reflection performance hit.

It's implemented as an extension method so that it should work for any object. Enjoy!



using System;
using System.Collections;
using System.Reflection;

public static class Extensions
{
///
/// Clones the specified object.
///

///
/// The object.
///
public static T Clone(this T obj)
{
var type = obj.GetType();
var clone = Activator.CreateInstance(type);
var fields = clone.GetType().GetFields();
int fieldIndex = 0;
foreach (FieldInfo fieldInfo in type.GetFields())
{
fields[fieldIndex].SetValue(clone, fieldInfo.FieldType.Implements("ICloneable") ? ((ICloneable)fieldInfo.GetValue(obj)).Clone() : fieldInfo.GetValue(obj).Clone());
if (fieldInfo.FieldType.Implements("IEnumerable"))
{
var enumerable = (IEnumerable)fieldInfo.GetValue(obj);
if (fields[fieldIndex].FieldType.Implements("IList"))
{
int i = 0;
IList list = (IList)fields[fieldIndex].GetValue(clone);
foreach (var item in enumerable)
list[i++] = item.Clone();
}
else if (fields[fieldIndex].FieldType.Implements("IDictionary"))
{
IDictionary dictionary = (IDictionary)fields[fieldIndex].GetValue(clone);
foreach (DictionaryEntry entry in enumerable)
dictionary[entry.Key] = entry.Value.Clone();
}
}
fieldIndex++;
}
return (T)clone;
}

///
/// Returns true if the type implements the specified interface, otherwise false.
///

/// The type.
/// Name of the interface.
///
public static bool Implements(this Type type, string interfaceName)
{
return type.GetInterface(interfaceName, true) != null;
}
}

comment on Deep cloning without serialization.

Hosting multiple WCF Services under a single Windows Service

I am often asked how to host multiple services under a single windows service and through trial and error have been streamlining this process for clients.

I thought I'd blog an example here for reference:

class Program {
static void Main() {
if (Environment.UserInteractive) {
ServiceManager serviceManager = new ServiceManager();
serviceManager.OpenAll();
Console.ReadKey();
serviceManager.CloseAll();
}
else
ServiceBase.Run(new WindowsService());
}
}

public class WindowsService : ServiceBase
{
public static string WindowsServiceName = "Windows Service Name";
public static string WindowsServiceDescription = "Windows Service Description";
public static string WindowsServiceUsername = @".\username";
public static string WindowsServicePassword = "password";

private readonly ServiceManager serviceManager = new ServiceManager();

private readonly IContainer components = new Container();

protected override void Dispose(bool disposing) {
if (serviceManager != null) serviceManager.CloseAll();
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}

public WindowsService() {
ServiceName = WindowsServiceName;
CanStop = true;
}

protected override void OnStart(string[] args) {
base.OnStart(args);
serviceManager.OpenAll();
}

protected override void OnStop() {
serviceManager.CloseAll();
base.OnStop();
}
}

public class ServiceManager {
readonly List serviceHosts = new List();

public void OpenAll() {
OpenHost();
OpenHost();
...
}

public void CloseAll() {
foreach (ServiceHost serviceHost in serviceHosts)
serviceHost.Close();
}

private void OpenHost() {
Type type = typeof(T);
ServiceHost serviceHost = new ServiceHost(type);
serviceHost.Open();
serviceHosts.Add(serviceHost);
}
}

///
/// Enables application to be installed as a Windows Service by running InstallUtil
///

[RunInstaller(true)]
public class WcfServiceHostInstaller : Installer {
public WcfServiceHostInstaller() {
Installers.Add(new ServiceInstaller
{
StartType = ServiceStartMode.Automatic,
ServiceName = WindowsService.WindowsServiceName,
Description = WindowsService.WindowsServiceDescription
});
Installers.Add(new ServiceProcessInstaller { Account = ServiceAccount.User, Username = WindowsService.WindowsServiceUsername, Password = WindowsService.WindowsServicePassword });
}
}


And some configuration:

- Here, the binding & behaviour configuration is shared across services but you may need different configurations for different types of services.
- I use different ports for different services, but you don't have to.

<system.serviceModel>
<services>
<service behaviorConfiguration="DefaultBehavior" name="Namespace.Service1">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="TCPBindingConfig" name="TCPEndpoint" contract="Namespace.IService1" />
<endpoint address="mex" binding="mexTcpBinding" bindingConfiguration="" name="TcpMetaData" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8001/Namespace/Service1" />
</baseAddresses>
</host>
</service>
<service behaviorConfiguration="DefaultBehavior" name="Namespace.Service2">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="TCPBindingConfig" name="TCPEndpoint" contract="Namespace.IService2" />
<endpoint address="mex" binding="mexTcpBinding" bindingConfiguration="" name="TcpMetaData" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8002/Namespace/Service2" />
</baseAddresses>
</host>
</service>
...
</services>
<bindings>
<netTcpBinding>
<binding name="TCPBindingConfig" maxBufferSize="5242880" maxReceivedMessageSize="5242880">
<readerQuotas maxStringContentLength="5242880" />
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="DefaultBehavior">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="21" maxConcurrentSessions="50" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Snakefoot (on Tuesday, December 01, 2009 at 01:38 PM):
Is it possible to start a WCF service for each service defined in the app.config ?
Anonymous (on Thursday, November 12, 2009 at 07:21 AM):
This is great. I searched for a long time to find something like this. There don't seem to be any decent WCF examples available.
jtango (on Wednesday, October 28, 2009 at 11:54 PM):
Fantastic post. Helped me out a lot. Thanks

comment on Hosting multiple WCF Services under a single Windows Service

Shuffle: an extension method of random uselessfulness

UPDATE: I have updated this method here: http://thegrenade.blogspot.com/2010/02/when-random-is-too-consistent.html

Have you ever needed to randomly sort a list in C#? Add this to your nifty extensions library:

/// <summary>
/// Shuffles the specified list (Extension Method for any IList<T>).
/// </summary>
/// <remarks>
/// Algorithm described at: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
/// </remarks>
/// <example>list.Shuffle();</example>
public static void Shuffle<T>(this IList<T> list)
{
Random rng = new Random();
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}

comment on Shuffle: an extension method of random uselessfulness

Akamai Download Manager Sucks!

I wasted a lot of time today trying to download Windows 7 from MSDN using the pile of rubbish that is the "alternate download site" for "top downloads" at MSDN.

The regular download mechanism using Microsoft File Transfer Manager is disabled today while everyone tries to download Windows 7. For me the process was to get a javascript error when clicking on the Windows 7 download link.



If I manually change the URL in the linked page to use HTTPS I can get further (if I accept that MSDN is using an invalid security certificate).



And when Akamai Download Manager finally loads and I am prompted to accept another invalid certificate, It just errors out without giving an explanation.







In summary: Akamai, you suck. And Microsoft, seriously, we expect better.
Akamai victim (on Wednesday, March 03, 2010 at 04:55 PM):
Twice it downloaded to 99% and then gave an error!

Trying again third time.
Kevin (on Friday, October 23, 2009 at 01:46 AM):
I have had much the same experience, but with Microsoft Partner Action pack, you can't download any of the products you paid for because the peice of garbage Akami download manager says 2.3 GB file will take you 86 hours to download. makes me want to go back to the Unix world.
Barfield (on Wednesday, August 26, 2009 at 12:22 PM):
Just get a torrent file and check the md5 hash/filesize/filename.

Its SO much easier and faster

comment on Akamai Download Manager Sucks!

Source control shootout

I like to think that I keep up with changes in the continuous integration space but somehow I missed an important shift in source control thats been taking place over the last year and a half. While I was busy promoting subversion and TFS as SourceSafe replacements, the community has abandoned centralised source control alltogether in favour of distibuted variants. Mercurial, like Git and Bazaar is a distributed version control system and needs no central server control. If a server wants revisions of a project it simply clones or pulls revisions from clients it is interested in.

Large open source projects have used the model for many years with notable successes like the linux kernel but a recent shift by Mozilla to Mercurial as well as Google Code's adoption of Mercurial was what caught my attention. Having now caught up on my research of the subject, I feel like I've arrived late to the party. I'm not going to say too much about what I've learned but the long and short of it is this:

If you're still using centralised source control, stop it. At the time of writing, you have two options: Git or Mercurial. I leave it as an exercise for the reader to understand why if you don't already know...

comment on Source control shootout

Using expression trees for more elegant code

Caching WCF responses without making a mess in the code base is often problematic due to connection pooling limitations and the requirement to close connections after use.

Many implementations end up something like: attempt to retrieve entity from cache > if absent, instantiate client proxy > retrieve entity from service > close connection > add entity to cache.

Problems arise because it is possible that entities for a single solution may need to be supplied by numerous services and the service method signatures may differ for various entities or collections of entities. This makes it difficult to create a generic wrapper capable of populating any number of varying entity types let alone the associated CRUD calls for entity persistence, etc. Entity identifiers may differ in type between services or even within a service. When a client side caching mechanism is required, the code becomes cluttered with the numerous caching calls and connection instantiations and tear-downs.

Any one who has worked on large WCF driven apps knows that this kind of clutter is butt-ugly and a pain to maintain or add features to.

Expression Trees to the rescue:

public static class Services
{
/// <summary>Gets the item.</summary>
/// <typeparam name="TProxy">The type of the proxy.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TIdentity">The type of the identity.</typeparam>
/// <param name="expression">The expression.</param>
/// <param name="proxy">The proxy.</param>
/// <param name="id">The id.</param>
/// <returns></returns>
/// <remarks>Method first checks the cache for an entity with a matching id, if found, it is returned.
/// If the cache does not contain the entity, the wcf service method in the supplied expression tree
/// is used to obtain the entity which is then added to the cache.</remarks>
/// <example>Product p = GetItem((client, identifier) => client.GetProduct(identifier), new CatalogServiceClient(), 123);</example>
private static TEntity GetItem<TProxy, TEntity, TIdentity>(Expression<Func<TProxy, TIdentity, TEntity>> expression, TProxy proxy, TIdentity id)
where TEntity : class
where TProxy : ICommunicationObject
{
TEntity item = Cache.GetItem<TEntity, TIdentity>(id);
if (item == null)
{
try
{
var originalDelegate = expression.Compile();
item = originalDelegate.Invoke(proxy, id);
}
finally { proxy.Close(); }
Cache.AddItem<TEntity, TIdentity>(item);
}
return item;
}
}

comment on Using expression trees for more elegant code

Running .Net 2 web applications alongside .Net 4

I recently installed the Visual Studio 2010 RC on an XP dev machine and found that it reconfigured IIS to default to ASP.Net v4. That's not terrible, but it did stop me being able to debug my .Net 2 & 3 applications under IIS and I started getting the old "Unable to start debugging on the web server" error message.

It's simple enough to fix however. Just run the following command for each virtual directory that needs to run under .Net 2:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis /s W3SVC/1/ROOT/<YourVirtualDirectoryName>

comment on Running .Net 2 web applications alongside .Net 4

Quirky psexec behaviour when run from MSBuild.

Part of a deployment process I have been working on requires that our build server deploy some WCF services to a remote host. This is accomplished by hosting the WCF services within a Windows service that listens for TCP connections.

We ran into some quirky behaviour with psexec hanging during the execution of InstallUTil on the remote host. I presume that installutil was prompting for some sort of confirmation and not getting it. I added the -d switch to the psexec command and then noticed that psexec started returning inconsistent exit codes. It turns out that -d causes the PID of the remote process to be returned as the exit code as well as suppressing any interactivity. I can see this being useful in many psexec scenarios but it makes msbuild think something has gone wrong and this means that I also needed to add an IgnoreExitCode attribute to my exec tag like so:

<Exec Command="psexec -d /accepteula \\RemoteHostName C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe /i /LogFile=D:\Logs\ServiceInstallation.log D:\Services\WcfServiceGroupHost.exe" IgnoreExitCode="true" />

comment on Quirky psexec behaviour when run from MSBuild.

Running Visio SaveAsWeb in a web application or service

There are many reasons why you should not run Microsoft Office applications from inside a web application or service. But if you really want to do it anyway, here's what you need to know to use Visio SaveAsWeb on Windows Server 2003.


Create a new windows user. The Application Pool that your web application runs under will be using the Network Service account in its identity settings by default. Change this so that your Application Pool uses a custom identity account that you have created.


Add your new user to the local IIS_WPG group. Verify that Local Security Policy > Local Policies > User Rights Assignment > Log on as a service grants this role to IIS_WPG.


Run DCOMCNFG and navigate to Component Services > Computers > My Computer > DCOM Config > Microsoft Office Visio Drawing > Properties > Security > Launch and Activation Permissions > Customize > Edit. Add the IIS_WPG group and allow Local Launch & Local Activation rights.


Add the following code to somewhere appropriate in your application:


using System;
using Microsoft.Office.Interop.Visio;
using Microsoft.Office.Interop.Visio.SaveAsWeb;

public static class Visio
{
public static bool Convert(string pathToVsd, string pathToHtml, string pageTitle)
{
bool result;
try
{
InvisibleAppClass visio = new InvisibleAppClass();
VisSaveAsWeb saveAsWeb = (VisSaveAsWeb)visio.Application.SaveAsWebObject;
saveAsWeb.AttachToVisioDoc(visio.Documents.Open(pathToVsd));
VisWebPageSettings webPageSettings = (VisWebPageSettings)saveAsWeb.WebPageSettings;
webPageSettings.TargetPath = pathToHtml;
webPageSettings.PageTitle = pageTitle;
webPageSettings.DispScreenRes = VISWEB_DISP_RES.res768x1024;
webPageSettings.QuietMode = 1;
webPageSettings.SilentMode = 1;
webPageSettings.NavBar = 0;
webPageSettings.PanAndZoom = 0;
webPageSettings.Search = 0;
webPageSettings.OpenBrowser = 0;
webPageSettings.PropControl = 0;
saveAsWeb.CreatePages();
visio.Quit();
result = true;
}
catch (Exception ex)
{
//Put your error logging code here:
//Log.WriteEntry(string.Format("Failed to convert {0} to {1}.", pathToVsd, pathToHtml), ex);
result = false;
}
return result;
}
}

Following is a usage example:


protected void UploadButton_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile && FileUpload1.FileName.ToLower().EndsWith(".vsd"))
{
string documentName = FileUpload1.FileName.Substring(0, FileUpload1.FileName.Length - 4);
string newFilename = string.Format("{0}-{1:yyyyMMddHHmmss}", documentName, DateTime.Now);
string uploadFolder = Server.MapPath("Diagrams");
string pathToVsd = uploadFolder + "\\" + newFilename + ".vsd";
string pathToHtml = uploadFolder + "\\" + newFilename + ".htm";
FileUpload1.SaveAs(pathToVsd);
if(Visio.Convert(pathToVsd, pathToHtml, documentName))
Response.Redirect("Diagrams/" + newFilename + ".htm", true);
}
}

comment on Running Visio SaveAsWeb in a web application or service

LINQ vs Lambda vs loop - A performance test

I was wondering about the benefits of using Linq over Lambda or vice versa for data filtering. I noticed that some of Scott Gu's recent MVC posts contain examples like:


var products = from p in northwind.Products where p.Category.CategoryName == "Beverages" select p;

Personally, I prefer the Lambda construct:


var products = northwind.Products.Where(p => p.Category.CategoryName == "Beverages");

This is largely a personal preference issue, but being regularly involved in architectural discussion you often get asked to justify your personal preferences. So I checked the interweb for some metrics and came across an older post about LINQ over loops.


Back in 2007, HÃ¥vard Stranden posted some performance metrics for LINQ vs Loop using a Visual Studio 2008 beta. His test concluded that there was a performance hit in using LINQ over loops but that it was within acceptable ranges. Anders Hejlsberg and others pointed out that the test would benefit from removing some unnecessary casting.


I have modified the tests to remove the unnecessary casting, include metrics for using Lambda, and take advantage of the improvements to the framework in Visual Studio 2008 SP1. I experimented with several different approaches because the various methods have dependencies and return constraints. LINQ and looping depend on IEnumerable collections (IEnumerable) and return the same, Lamda depends on Genercic Lists (IList) and returns the same. To account for this, I decided that the methods could have their test data provided in the format they need it in before the stopwatch starts. This decision favours Lambda but real world scenarios where Lambda expressions are used also rely on this constraint. For the same reason, I removed the List instantiation in Stranden's loop test because this handicaps the loop implementation since a real world looping example doesn't need to instantiate a List in order to filter on some property data.


The test results show that the framework is now seriously biased in favour of Lambda implementations. Presumably, because of changes to Generic internals.


The test code as well as the results on my PC are shown below.


Test:


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace IterationPerformanceTest
{
class Program
{
public static void Main(string[] args)
{
Trace.Listeners.Add(new TextWriterTraceListener(File.CreateText("Output.txt")));
RunTests();
Trace.Flush();
}

static void RunTests()
{
const int SIZE = 10000;
const int RUNS = 1000;
Stopwatch stopwatch = new Stopwatch();
Random r = new Random();
int[] ints = new int[SIZE];
for (int i = 0; i < SIZE; i++)
ints[i] = r.Next(int.MinValue, int.MaxValue);
List intList = new List(ints);

//LINQ
stopwatch.Start();
for (int t = 0; t < RUNS; ++t)
{
IEnumerable result = (from int i in ints where i < 10 select i);
}
stopwatch.Stop();
Trace.WriteLine(string.Format("LINQ: {0}, avg. {1}", stopwatch.Elapsed, new TimeSpan(stopwatch.ElapsedTicks / RUNS)));
stopwatch.Reset();

//LAMBDA
stopwatch.Start();
for (int t = 0; t < RUNS; ++t)
{
IEnumerable result = (intList.Where(i => i < 10));
}
stopwatch.Stop();
Trace.WriteLine(string.Format("LAMBDA: {0}, avg. {1}", stopwatch.Elapsed, new TimeSpan(stopwatch.ElapsedTicks / RUNS)));
stopwatch.Reset();

//LOOP
stopwatch.Start();
for (int t = 0; t < RUNS; ++t)
{
foreach (var i in ints)
{
bool match = (i < 10);
}
}
stopwatch.Stop();
Trace.WriteLine(string.Format("Loop: {0}, avg. {1}", stopwatch.Elapsed, new TimeSpan(stopwatch.ElapsedTicks / RUNS)));

}
}
}

Results:


LINQ:   00:00:00.0019557, avg. 00:00:00.0004552
LAMBDA: 00:00:00.0000574, avg. 00:00:00.0000133
Loop: 00:00:00.0601094, avg. 00:00:00.0139906
Grenade (on Monday, August 24, 2009 at 09:10 PM):
Thanks @devoured_elysium! I stand corrected.
devoured elysium (on Monday, August 17, 2009 at 01:02 PM):
This is wrong my friend. The for loop will be always faster. What you are missing is that linq/lamba is doing deferred queries, meaning only when you do .ToArray() for example, will the actual execution being done.

comment on LINQ vs Lambda vs loop - A performance test

Running your .Net Web Application under a custom user account on Windows Server 2003

Some web applications will benefit from running under a different user account to the default 'Network Service' account. I had to set this up today to support some server side Visio COM execution.


The user account is specified under the Identity tab for the Application Pool properties and whatever user you use will need access to the following resources & groups:



  • Add to group: IIS_WPG

  • Run %windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis -ga domain\username

  • Local Security Policy > Local Policies > User Rights Assignment > Log on as a service


comment on Running your .Net Web Application under a custom user account on Windows Server 2003

Static methods and classes are always evil

So, I'm looking for a new gig and I take a call from an agency that represents a prestigious .Net development team that's paying well and using all the latest toys. Fantastic! The agent reckons I'm suited to the role and sends me a copy of the "perfume test". It's a technical test with an interesting twist. The constraints are rather few and far between and the test description is really just a description of a business problem. "Write your own solution to the problem and make sure you use good design practice." I'm told that I have 48 hours to write my solution but I finish in 4 and send it back. It's a complete TDD solution with all the required unit tests and even goes the extra mile with a few extra bells and whistles to spruce it up. I was feeling pretty confident and gave them a couple days to respond.

No news... I call them and am told "Your solution was great but you made use of a static class, we don't like that. It's hard to test. You're out of the running."

Uh oh. Well, if ever anyone wanted a good reason why you should never use static classes, the definitive answer is: It makes you unemployable. Q.E.D.
fadzlan (on Saturday, May 02, 2009 at 12:47 PM):
Wow... Just wow.

Sorry to hear that. I believe it is kind of shortsighted to reject anyone just by ONE simple metrics, while everything else is following the latest, the standard stuff (assuming).

Don't they have an architect that mandates design strategy at that kind of shops? A simple instruction on not to do static methods would suffice.

Ah well. To each his/her own. I suppose test/interviews like that are two way street.

comment on Static methods and classes are always evil

Apache, Mono and Virtual Hosts

I run some ASP.Net applications under mono on a Debian host. Mono has come a long way in the last few years and is now making progress in support for C# 3.0 and even WCF. This adds appeal to the platform and increases the scope of it's usefullness.


One thing I had trouble finding documentation for and ended up accomplishing through trial and error was setting up my Mono applications to run in an Apache (2) Virtual Host. The most difficult bit turned out to be the configuration of my sites-available file in /etc/apache2/sites-available. So I've decided to share it here:


<VirtualHost *>
ServerName example.com
ServerAlias www.example.com example.net www.example.net example.co.uk www.example.co.uk
DocumentRoot /var/www/example.com
ServerAdmin serveradmin@example.com
ErrorLog /var/log/apache2/example.com-error_log
#RewriteEngine on
#RewriteRule ^/([A-Za-z]*)/?$ /?Page=$1 [PT]
AddMonoApplications example "/:/var/www/example.com"
MonoUnixSocket example /tmp/.example_mod_mono_server2
MonoServerPath example /usr/lib/mono/2.0/mod-mono-server2.exe
AddType application/x-asp-net .aspx .ashx .asmx .ascx .asax .config .ascx .svc
MonoApplicationsConfigDir example /etc/mono-server2
MonoPath example /usr/lib/mono/2.0:/usr/lib:/usr/lib/mono/2.0
<Directory "/var/www/example.com">
MonoSetServerAlias example
SetHandler mono
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
</VirtualHost>

As you can see, I have commented out the RewriteEngine and RewriteRule directives but there is enough there for you to see what new tricks we .Net devs have to play with when we write code for a Mono deployment...

Grenade (on Tuesday, December 02, 2008 at 10:35 AM):
Hi leftbrainlogic,
I only commented out the rewrite stuff because it's not useful to everyone who is just after a VirtualHost config. It does work for me. Apache needs to have mod rewrite enabled. I don't remember what the [PT] tag is for but it should be documented in mod rewrite.
leftbrainlogic (on Saturday, November 29, 2008 at 02:37 AM):
I just came back to say thank for sharing this information sir! It's 2.30AM and I've been trying to solve my problem the whole day. I added [PT] to the end of my RewriteRules and now they work! No I just need to find out what [PT] means. Thanks again!
leftbrainlogic (on Saturday, November 29, 2008 at 02:30 AM):
May I ask why you commented out the rewite section. I am trying to figure out why rewriting isn't working on my installation. have you commented out your rewrites because they don't work?

comment on Apache, Mono and Virtual Hosts

Help find Philip McNeil

The last few days have been an emotional roller coaster. We heard on Monday that Jo's brother has not been heard from or seen since early October when he disappeared from Yellowknife.

There are good indications that he is alive but piecing together the facts from an overwhelmingly large selection of tiny clues has been exhausting.

I have learned so much this week about human nature that my head feels like it's going to explode. Listening to second and third hand reports is useful but you can't beat going straight to the source to get the facts. It seems that when someone relates an incident or a conversation to you, they can't help but include their own interpretation or understanding of an event and when the relay has already passed through more than one chinese whisper the facts can get seriously twisted.

The whole week has been spent speaking to everyone we can find who has spoken to or seen Phil in the weeks leading up to his disapearance and organising the effort to find him.

We put up a simple website to colate some of the information gathered so far at: www.philipmcneil.com.

Jo left for Yellowknife this morning. She's due to meet up with Julie when she arrives and start interviewing people face to face as well as recruiting a team to help her search for Phil.

I love you Jo. Good luck! Find him quickly.
rose (on Friday, November 14, 2008 at 11:08 PM):
Thanks so much for setting up that site, Rob. The suspense is intense beyond words. Best of luck, Jo & Julia! I wish there were more those of us who are farther away could do to help.

comment on Help find Philip McNeil

Driving in Tico Country

Driving in Costa Rica is pretty awesome. The landscapes are varied and breathtaking. Making a wrong turn almost always results in some kind of adventure whether it's discovering the capabilities of your offroader or finding a great place to eat and taste foods you've never tried. I ended up on a rope bridge a couple weeks back that came complete with two sets of wooden planks spaced roughly the distance that a set of car wheels have between them apart and all the thrills and wobbles of riding a rollercoaster that you can steer.

Today, however, I discovered that it doesn't matter what your driving skills are when I managed to execute an emergency stop on a wet highway to avoid colliding with cars ahead of me that had somehow become stationary in a very short distance. I had all of 2 or 3 seconds to enjoy my near miss before seeing in the rear view mirror that the 2 wheel drive Hyundai behind me didn't have a prayer of stopping without using his bonnet and my boot as his crumple zone. I made a vague effort to remove my car from the road but the combination of the rain soaked tarmac and old Kumho tires on the doomed car to my rear proved a versatile adversary and the inevitable crunching and screeching ensued. Everyone was ok and aside from being jolted into a new level of awareness all six of the Hyundai's passengers emerged a little shaken and embarrassed but good spirited enough to know that it could have been a lot worse. The Costa Rican police were on the scene quickly and had everything documented before I could say pura vida.

Needless to say the Hyundai was a write off while my trusty Terios only lost its cosmetic bumper cover and right indicators. In future I'll be reaching for the hazards a bit quicker and relaxing a little slower when I've just avoided an accident. And I'm enjoying the convenience and disposability of rental cars a little more today.

comment on Driving in Tico Country

Recession? --Bah, humbug!

My take on the conversation about Britain following the US into recession is that ours is a very different economic situation to that of our cousins on the other side of the pond.

American house prices fell because the American housing market had more houses for sale than house buyers with money were creating demand for. Building and planning laws in the US are a lot different to those in the UK so when people over extended themselves to buy houses they couldn't afford and were unable to make the mortgage payments the market became saturated and prices dropped and the whole vicious cycle of bankruptcies ensued.

In the UK, our situation is more of a correction. We too had lots of people buying houses they couldn't afford, so that when interest rates rose, a few camel's backs were broken. The difference here is that when these properties were returned to the market, they were snapped up by investors who still had deep pockets.

UK house prices are not about to drop, they are going to continue rising. We still don't have enough housing on the market and that isn't about to change quickly. As long as that's true (and I predict it will remain true for a few decades still), home owners can rest assured that their investment is safe. Meanwhile all those folks who fell off the ladder are about to get some hard knocks schooling about buying a house they can afford.

comment on Recession? --Bah, humbug!

About...

Search

Styleshout

The site layout and css templates used on this site are based on a free template from Styleshout. I'm a programmer not a designer --everyone keeps reminding me.

Mono

Mono Powered This site was built in Visual Studio 2008 using Asp.Net and C# 3.5. It is hosted on a Debian, Apache web server using Mono.

I develop in C# because it is simple, powerful, modern and I just like it. I like using generics and I like the flexibility of the language.

The traditional downside to developing web applications on the .Net platform is the associated licensing costs for the various servers required by a typical web application.

This site is a performance and scalability experiment in combining some enterprise functionality (like enterprise library) with an open source hosting environment to see how viable the combination can be.