<?xml version="1.0" encoding="UTF-8" ?>    <rss version="2.0">
        <channel>
            <title>Robert Gray</title>
            <description></description>
            <copyright>robertgray.net.au</copyright>
            
            <link>http://www.robertgray.net.au.aspx</link>
            <lastBuildDate>Sat, 27 April 2013 00:00:00</lastBuildDate>
            <pubDate>Sat, 27 April 2013 00:00:00</pubDate>

                <item>
                    <title>Using HTML5 video and knockoutjs</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/using-html5-video-and-knockoutjs</comments>
                    <description>My current project makes use of knockout.js for a good portion of its user interface.&#160; I also have the need to display video to the end user.&#160; The site is HTML5 meaning I can make use its new video element.&#160; The spec defines the video format as an MP4 container, using H.264 for video and AAC for audio.&#160; The first problem is, like so many HTML elements, different browsers have gone down different container and codec routes.&#160; The main remaining culprit is FireFox, which from version 3.5 to 4.0 uses the Ogg container and from 4.0 up uses WebM (which is also supported by Chrome).&#160; The video element allows for a number of sources to define fallbacks.&#160; Generally the video/mp4 type is the first source, then video/webm, then video/ogg, then the non HTML fallback of Flash, Silverlight, etc.  Another problem with the standard HTML5 video tag is the rendering of the controls.&#160; Each browser is free to style the controls however they choose, resulting in a slightly different UI.&#160; You can make your own controls by using HTML markup, some CSS, and some javascript to interact with the video API.&#160;  An easier way is to use an player that takes care of whether or not to use the video element, standardising the UI, providing fallbacks.  I initially chose video.js as my library and got it work pretty easily with knockoutjs by making my own custom bindings to interact with it’s API.&#160; However, I was getting some problems and one of the guys at work suggested I try JW player from LongTail, as we’ve been using it on our Ausgamers site without problems.  JW Player differs a bit from videojs, in that you only need to create a div and then insert the appopriate javascript.&#160; Using the most basic hosted option, a minimal page with player looks like  &amp;lt;html&amp;gt; &amp;lt;head&amp;gt;   &amp;lt;script src=&quot;http://jwpsrv.com/library/YOUR_JW_PLAYER_ACCOUNT_TOKEN.js&quot; &amp;gt;&amp;lt;/script&amp;gt; &amp;lt;/head&amp;gt; &amp;lt;body&amp;gt;   &amp;lt;div id=&quot;my_player&quot;&amp;gt;&amp;lt;/div&amp;gt;   &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;     jwplayer(&quot;my_player&quot;).setup({       file: &quot;/uploads/example.mp4&quot;,       height: 360,       image: &quot;/uploads/example.jpg&quot;,       width: 640     });   &amp;lt;/script&amp;gt;   &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;  &#160;  The player will be instantiated and ready to play example.mp4.&#160; JW player creates a bunch of html elements as well as the video element and sets the src attribute of video to /uploads/example.mp4.&#160; It will also detect you can play HTML5 video with the supplied format and if not fallbacks.&#160; I think it even uses VLC player if you have it installed, to play the mp4 in firefox for example.&#160; Anyway, just know it creates what you need to get video going.&#160; Naturally, if your browser can’t play the file for whatever reason, it still won’t work.  My site has a number of videos, navigated to by using Next and Previous, which load the data async, using knockoutjs to bind.&#160; Unsurprisingly there are no inbuilt bindings for this.&#160;&#160; A custom binding is the answer to updating the video source and image when the Next/Previous buttons are clicked.  I’ve defined jwPlayer, playerId, and posterUrl knockout bindings.&#160;&#160; All three are used in the jwPlayer binding.&#160;  It’s pretty simple to get setup with knockout.&#160; You just call the code in the above script from within the binding.  ko.bindingHandlers.jwPlayer = {   init: function(element, valueAccessor, allBindingsAccessor) {     var videoUrl = ko.utils.unwrapObservable(valueAccessor());     var allBindings = allBindingsAccessor();     var mysources = [];     if (videoUrl[0]) mysources.push({ file: videoUrl[0] });     if (videoUrl[1]) mysources.push({ file: videoUrl[1] });     if (videoUrl[2]) mysources.push({ file: videoUrl[2] });         var options = {       playlist: [{         image: allBindings.posterUrl(),         sources: mysources       }],             height: 450,       width: 800,          };     jwplayer(allBindings.playerId).setup(options);   },   update: function(element, valueAccessor, allBindingsAccessor) {     var videoUrl = ko.utils.unwrapObservable(valueAccessor());     var allBindings = allBindingsAccessor();     var mysources = [];     if (videoUrl[0]) mysources.push({ file: videoUrl[0] });     if (videoUrl[1]) mysources.push({ file: videoUrl[1] });     if (videoUrl[2]) mysources.push({ file: videoUrl[2] });          var playlist: [{         image: allBindings.posterUrl(),         sources: mysources       }];               jwplayer(allBindings.playerId).onReady(function() {       jwplayer(allBindings.playerId).load(playlist);     });   } };  &#160;  The above code is a little more complex than the most simple situation in first snippet.&#160; I’m making use of playlists to include the various sources.&#160; JWPlayer will use the file extension to determine the type (within reason. See the list of supported types on their website).&#160; You can also include a type property in each source.&#160;  The init function sets up the player with the initial playlist.&#160; The update function will load the new playlist when the data in the view model changes.  Initially I had data bound to the div element with id my_player but I found the init function was being called but never the update function. JWplayer was getting rid of knockout!&#160; The workout around was to create a parent div and nest div#my_player inside it.&#160; My HTML declaration looks like this:  &amp;lt;div data-bind=&quot;jwPlayer: videos, playerId: &#39;my-video&#39;, posterUrl: posterImageUrl&quot;&amp;gt;   &amp;lt;div id=&quot;my-video&quot;&amp;gt;Loading the Video Plugin...&amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt;</description>
                    <link>http://www.robertgray.net.au.aspx/posts/using-html5-video-and-knockoutjs</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/using-html5-video-and-knockoutjs</guid>
                    <pubDate>Sat, 27 April 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Why Vettels ignoring team orders was a dog move</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/why-vettels-ignoring-team-orders-was-a-dog-move</comments>
                    <description>I blogged about why team orders are ok just after the Alonso/Massa incident at Germany 2010. &#160;My opinion hasn&#39;t changed. &#160;You can read about my thoughts here . &#160;  The incident between Red Bull drivers Sebastian Vettel and Mark Webber at last weekends Malaysian GP has ignited forums, blogs, facebook and twitter around the globe, as fans of the sport and fans of each of the drivers vboice their opinions on who is to blame and on team orders themselves. &#160;  For the casual F1 fan, and the general public common consesus is that the from Vettel was the right one. &#160;After all, in racing shouldn&#39;t the fastest driver win?  It&#39;s never that simple though, for Webber was not slower than Vettel. &#160;This is &#160;a multi-variable topic, so I&#39;m focussing on this incident.  Melbourne, a week ago  The scene needs to be set a little with the performance of the Red Bull in Melbourne last week. They were clearly the fastest car over a single lap and were nearly unbackable odds for a win, but they found out (first race of the season so it was new to them and everyone else) that their tyres degrade faster than some of the other cars, and that cost them valuable position. They worked hard over the last week to figure out their tyre wear problems and hopefully devise a strategy to not have a repeat performance.  Now at Malaysia  This race, Webber started 6th and was 2nd by lap two, behind Vettel. He then got past Vettel after the first round of stops. I sat watching the race, one eye on the tv and one on my timing app and Webber had the measure of Vettel throughout the race. Every time they pitted, he came out a little bit further in front.&#160;  Vettel made a radio complaint that Webber was too slow and holding him up. The very next lap Webber was 0.7s faster then Vettel (a lot). The reality was Webber was doing exactly enough to stay in the lead, while keeping his tyre wear to a minimum.  F1 has a limit of 8 engines per year, so the teams are also focussed on not pushing the engine hard. After the last pitstops it was Webber that emerged in front of Vettel. At that point BOTH drivers were told to turn their engines down (this changes the engine mapping - electronics - to reduce engine power and fuel consumption and increase life). This is actually a common strategy used by teams after the last round of stops when they have a comfortable gap. Webber made reference to a pre-race understanding in his post-race comments. Based on history, that generally means &quot;maintain position after the last stops&quot;. Vettel admits to hearing the message to slow down but chose to ignore it. Webber heard the message and did as he was instructed. It wasn&#39;t the case that Webber was slower. He proved through the entire race he easily had the pace. The team boss has admitted Vettel heard the message then turned his engine mapping up to full - giving him maximum possible engine power, while Webber was running at a minimum. With that he was able to blast past Webber and take the win.&#160;  If anything it was the team order that lost Webber the race, because he did as he was ordered and Vettel did not.&#160;  There is also a very &quot;colourful&quot; history between these two guys, starting in Istanbul 2010 when Vettel speared into the side of Webber, who was at the time leading the race. As a result both cars were taken out and the team took no points.  From a team point of view it didn&#39;t matter in what order the drivers finished, as the team would get the same amount of points. Points are critical because the budget for the next year is based on how many championship points are won in the current season. More points equals more money. A lot more money. With that in mind, teams are very careful to ensure their drivers (a) don&#39;t crash into each other and (b) make it to the end. Thats where team orders come into play.&#160;  What Vettel did was ignore the direct instructions of his employers, shaft his agreement with his &quot;teammate&quot;, and not just light, but add extra gunpowder to the powderkeg that has been waiting to explode. It was a really shitty thing to do allround. He knows it too.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/why-vettels-ignoring-team-orders-was-a-dog-move</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/why-vettels-ignoring-team-orders-was-a-dog-move</guid>
                    <pubDate>Tue, 26 March 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Selecting the previous event from a schedule</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/3/selecting-the-previous-event-from-a-schedule</comments>
                    <description>My app has the ability to define a daily schedule which is done by selecting the desired days of the week, Monday through Sunday and the time of the event; either in the Am, Pm, or at any time during the day.&#160; Multiple schedules can co-exist as long as no two schedules share the same Day and Time combination (e.g. Sunday-Am or Tuesday-Any).&#160; Each Schedule contains a list of ordered Activities that must be carried out at times dictated by the Schedule.&#160; This is known as an Event and is the concrete performing of the scheduled activities.  &#160;    &#160;  My schedule stores this information in 10 bit fields in sql server, represented as boolean fields in my data model.&#160; It’s easy to store the fields, and covered my user story at the time, a time when no details about retrieving or display events were available.&#160; In the database a Schedule has one or more Activties and an Activity can be part of one or more Sessions.  My app needs to display the currently active Event, if there is one, and if there is not it is to display the previously performed Event.&#160; On the initial consideration I thought this a difficult think to represent in code concisely.&#160; I didn’t want to write a mess of nested if-then-else statements to check each boolean flag (Monday to Sunday and Am,Pm,Any) as I stepped back in days if there was no currently happening Event.  Remembering that there are multiple Schedules, I need to find the appropriate Schedule in my collection of Schedules then I display all the Activities associated with the Schedule.   If only one Schedule is defined, return that Schedule  Otherwise check if there are any Schedules that define Activities for the current time period  Otherwise find the Schedule that defines the Activities previously performed.   &#160;  The first two were easy.&#160; For #1 if there is only one Schedule return it.&#160; For #2 build a query to determine if there is a currently active Schedule.  &#160;  public virtual Session GetCurentSchedule() {   // Typically there will only be one schedule so we&#39;ll just return that schedule.   if (Schedule.Count() &amp;lt;= 1)     return Schedule.FirstOrDefault();     // Though if there are more one schedule grab the schedule that is currently &quot;Active&quot;         if (Schedule.Any(ActiveScheduleFilter().Compile()))     return Schedule.FirstOrDefault(ActiveScheduleFilter().Compile());   // If there is no currently active Session grab the schedule that was last used.   return Schedule.OrderBy(x =&amp;gt; x.TimeElapsedSinceLastScheduledEvent()).FirstOrDefault(); }  // Remember business rules state no two schedule on at the same time. private Expression&amp;lt;Func&amp;lt;Schedule, bool&amp;gt;&amp;gt; ActiveScheduleFilter() {   var predicate = PredicateBuilder.True&amp;lt;Schedule&amp;gt;();   switch (DateTimeHelper.Today.DayOfWeek)   {     case DayOfWeek.Monday:       predicate = predicate.And(s =&amp;gt; s.OnMonday);       break;     case DayOfWeek.Tuesday:       predicate = predicate.And(s =&amp;gt; s.OnTuesday);       break;     case DayOfWeek.Wednesday:       predicate = predicate.And(s =&amp;gt; s.OnWednesday);       break;     case DayOfWeek.Thursday:       predicate = predicate.And(s =&amp;gt; s.OnThursday);       break;     case DayOfWeek.Friday:       predicate = predicate.And(s =&amp;gt; s.OnFriday);       break;     case DayOfWeek.Saturday:       predicate = predicate.And(s =&amp;gt; s.OnSaturday);       break;     case DayOfWeek.Sunday:       predicate = predicate.And(s =&amp;gt; s.OnSunday);       break;           }   predicate = DateTimeHelper.Now.Hour &amp;gt; 11 ? predicate.And(s =&amp;gt; !s.InAm) : predicate.And(s =&amp;gt; !s.InPm);   return predicate; }  &#160;  The curly problem was finding the Schedule when there isn’t one active.&#160; What I needed was a way for each Schedule to tell me when it would have previously occurred and select the Schedule that had most recently occurred.&#160; That’s where the TimeElapsedSinceLastScheduleEvent come into play.&#160; This method will tell me the time elapsed (TimeSpan) since the Schedule last triggered an Event.  protected virtual bool InternalAny {   get   {     // If the event is not scheduled for Am or Pm it IS scheduled for Any.     return InAny || (!InAm &amp;amp;&amp;amp; !InPm);   } } /// &amp;lt;summary&amp;gt; /// Gets the time since the last scheduled event. /// If today is Tuesday 14:30 and events are scheduled /// on Monday and Thursday (Any) then the time since last event /// would be the time elapsed since the expiration of the previous event. /// In this case, Tuesday 12:00am / Monday 23:59:59 /// &amp;lt;/summary&amp;gt;     public virtual TimeSpan TimeElapsedSinceLastScheduledEvent() {               var currentTime = DateTimeHelper.Now;     if (ScheduledDays.Any(x =&amp;gt; x == currentTime.DayOfWeek))   {     // There is a schedule for today.        if (InternalAny)     {       // And can be done at any time today.       return new TimeSpan();     }     if (((InPm || InternalAny) &amp;amp;&amp;amp; currentTime.Hour &amp;gt; 11) || (InAm &amp;amp;&amp;amp; currentTime.Hour &amp;lt; 12))     {       // There is a schedule for NOW - return no elapsed       return new TimeSpan();     }           }   var previousEvent = LastSevenDaysEvents.FirstOrDefault();   if (previousEvent != null)   {     return currentTime - previousEvent.EndDate;   }   return new TimeSpan(); }  &#160;  I calculate an Event (there is no concrete event implementation – yet) by determining all Events for the last seven days (a Schedule only defines a 7 day period).  /// &amp;lt;summary&amp;gt; /// Gets the event dates for all events in the last 7 days. /// Doesn&#39;t get the current event. /// Ordered with the most recent event first. /// &amp;lt;/summary&amp;gt; public virtual IEnumerable&amp;lt;Event&amp;gt; LastSevenDaysEvents {   get   {     var events = new List&amp;lt;Event&amp;gt;();         var now = DateTimeHelper.Now;     var currentTime = now;     while ((now - currentTime).TotalDays &amp;lt; 8)     {       if (ScheduledDays.Any(x =&amp;gt; x == currentTime.DayOfWeek))       {         if (InAm || InternalAny)         {           var @event = new Event() {StartDate = currentTime.Date};           if (InAm)                           @event.Duration = new TimeSpan(12, 0, 0);                                                       if (InternalAny)             @event.Duration = new TimeSpan(1, 0, 0, 0);           events.Add(@event);         }         if (InPm &amp;amp;&amp;amp; !InternalAny)         {           var @event = new Event()             {               StartDate = currentTime.Date.AddHours(12),               Duration = new TimeSpan(12, 0, 0)             };                     events.Add(@event);         }       }       currentTime = currentTime.AddDays(-1);     }     return events.Where(w =&amp;gt; w.EndDate &amp;lt; now).OrderByDescending(w =&amp;gt; w.StartDate);   } }  The events are returned with the first event being the most recently occurred (note that means in the past!)&#160; ScheduleDays is simply the days that are set for this Schedule  protected IEnumerable&amp;lt;DayOfWeek&amp;gt; ScheduledDays {   get {     var list = new List&amp;lt;DayOfWeek&amp;gt;();     if (OnMonday) list.Add(DayOfWeek.Monday);     if (OnTuesday) list.Add(DayOfWeek.Tuesday);     if (OnWednesday) list.Add(DayOfWeek.Wednesday);     if (OnThursday) list.Add(DayOfWeek.Thursday);     if (OnFriday) list.Add(DayOfWeek.Friday);     if (OnSaturday) list.Add(DayOfWeek.Saturday);     if (OnSunday) list.Add(DayOfWeek.Sunday);     return list.OrderByDescending(x =&amp;gt; (int) x);   } }  Finally, once I have the correct Schedule I can show the date it was last used (either today or some day in the past)  public virtual DateTime? LastEventDate {   get {     var timeSinceLastEvent = TimeElapsedSinceLastScheduledEvent();     if (Math.Abs(timeSinceLastEvent.TotalMilliseconds - 0) &amp;lt; 0)     {       // Currently in an active event.       if (InternalAny || InAm)         return DateTimeHelper.Today;       if (InPm)         return DateTimeHelper.Today.AddHours(12);     }     var @event= LastSevenDaysEvents.FirstOrDefault();         return @event != null ? @event.StartDate : (DateTime?)null;   } }  I’m not sure it’s the best way to solve this problem (also, the problem itself seems a little strange to me – like why aren’t I storing concrete implementations of events and then simply retrieving the latest one – but at the moment there is no user story for this – Agile gone mad?)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/3/selecting-the-previous-event-from-a-schedule</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/3/selecting-the-previous-event-from-a-schedule</guid>
                    <pubDate>Sat, 02 March 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Euro Truck Simulator 2 Review</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/3/euro-truck-simulator-2-review</comments>
                    <description>For a long time I&#39;ve wanted a driving game where I can just drive. &#160;Not race, or rally, or do silly little street races; just get in a drive - clock up the kilometers on the road and let my mind wander to other things. &#160;Off the bat though, I should admit I am a fan of driving. &#160;Just getting in the car in real life and going for a drive for an hour or three or more. &#160; If you&#39;re in the right mood, driving is relaxing. &#160;You can play your favourite songs and just enjoy the physicality of turning the wheel, pushing the brake, accelerator, clutch, and changing gears - all to create a smooth, consistent, almost rhythmic zen state with the black stuff.    &#160;  In Game  The starting premise of Euro Truck Simulator 2 is simple, you know how to drive big rigs, you obviously like driving, and you aspire to own and work in your own trucking company. &#160;As you buckle up for your first job your shown your shabby tin shed garage and told of your lack of ownership of the diesel destroyers of road. &#160;But that&#39;s ok. &#160;You know how to drive and there are plenty of trucking companies offering contract driving gigs from and to the locations of your choice.  ETS2 isn&#39;t a full blown simulator. &#160;It won&#39;t take you 3 hours to transport your load from Dover to Calais, longer if you take the Ferry. &#160;English channel journeys are fast forwarded, with your truck either being floated across or travelling subterrainean. From my own investigates the world seems to zip by at about 1 kilometer per 2 or so seconds, even though the impression of speed is still accurate. &#160;I guess even I would find sitting on a 80km stretch of highway boring in realtime. &#160;There are some nice scenery to look at on the way though, especially if you&#39;re not native to Europe. &#160;I haven&#39;t driven much through mainlaind Europe but I have enjoyed the white cliffs of Dover in England and it&#39;s mainland tunnel exit, Calias. &#160;  The attention to detail is pretty good too. &#160;I love driving in the rain and the rain effects. &#160;The noise on windscreen and the noise of my wipers is soothing, as it is in real life wet weather cruising. &#160;Just remember to turn your lights on or you&#39;ll have to pay $150. &#160;Another thing I found out. &#160;And pay attention to your fatigue levels. &#160;You&#39;ll get fined pretty heavily for missing then. &#160;And the fines will keep coming until you find an appropriate place to pull over and rest. &#160;  I liked the levels of traffic on the highway. &#160;There were enough cars to keep me interested but not too many that I felt I was driving in peak hour (though I do hope that does happen!). &#160;I&#39;ve read the game also has real life landmarks. I&#39;ve not bean to the channel tunnel entrance but a quick look at google images results tells me that it looks the same in game as it does in real life. &#160;cool! :)  &#160;  Other features  ETS2 makes use of Internet radio streams in game. &#160;You can select (via the menu) the stream you want, from hundreds, or add your own, and listen to the real radio while you clock up the miles. &#160;  &#160;  Technical Stuff  Getting technical, the game completely supported my combination of G27 wheel and Fanatec Clubsport pedals. &#160;Before playing I wondered how it would handle truck gearboxes having more than 6 gears. &#160;It handles it quite elegantly, using a user-designated button to shift between the two ranges 1-6 and 7-12. &#160;There are also two more gear range styles, but this works well enough in my opinion. &#160;Generally 1-6 is good for navigating tight turns in the city, but once you get about 25 mph you&#39;re going to need 7th and beyond. &#160;By mapping my range selection button to button 0 on my G27 H-shifter (the first button on the left) &#160;I was able to quickly bump that button when I desired to move between the ranges.  The game also works well with SoftTH and triple screens, better than many other driving games out there, and second only to iRacing. &#160;Although it does annoyingly seem to require changing back to 1920x1200 and then to my widescreen res each time I start the game. &#160;But that&#39;s no biggie for the joy of triple screens.  &#160;    The Good The Bad     Really does feel like you&#39;re trucking your way across Europe  Good method to get more than 12 gears  Diesel engine sounds and operation  Internet Radio  Realistic fines  Recognisable landmarks from real-life  Great graphics      Trucks stops too quickly  Steering feels very light when on the highway.  No in-cabin options to turn radio on and off  In-cabin engine noise is too low. &#160;I want to hear the diesel roar!  See other comments       &#160;  Suggestions  I really do enjoy the game, and did from the first 5 minutes. &#160;While driving in game I thought of another component of real life trucking, communications with other drivers. &#160;This is missing from this game. &#160;Yes, I realise this is a single player game and therefore implementing this would be quite difficult, though it could still work in a single player environment. &#160;I&#39;d like to be able to jump on my simulated CB radio and talk to other trucks, or at least listen. &#160; I have a few friends who would enjoy this game, and I think being able to drive through the game while chatting to your mates who are doing the same would be awesome. &#160;So would be a MMO or multiplayer option, where me and my friends all work for the same trucking company.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/3/euro-truck-simulator-2-review</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/3/euro-truck-simulator-2-review</guid>
                    <pubDate>Fri, 01 March 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Encapsulating repository queries</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/encapsulating-repository-queries</comments>
                    <description>I’ve recently started structuring my repositories a little differently.&#160; I’ve moved away from creating a method per query on my repository (and interface) and instead providing a query parameter in my Get&#160; / Find methods (and their data paging variants).&#160; Here’s how a repository would look using the previous method.  public interface IRepository&amp;lt;T&amp;gt; where T : class, IDomainObject {   T GetById(int id);   IEnumerable&amp;lt;T&amp;gt; GetAll();   void Save(T entity);   void Delete(T entity); } public interface IUserRepository : IRepository&amp;lt;User&amp;gt; {   bool Exists(string username, int id);   bool EmailExists(string email, int id); } public class UserRepository : AbstractRepository&amp;lt;User&amp;gt;. IUserRepository {   public UserRepository(ISessionManager sessionManager) : base(sessionManager) { }     public bool Exists(string username, int id)   {     return Query.Any(u =&amp;gt; u.Username == username &amp;amp;&amp;amp; u.Id != id);   }     public bool Exists(string email, int id)   {     return Query.Any(u =&amp;gt; u.Email == email &amp;amp;&amp;amp; u.Id != id);   } }  &#160;  Seems pretty typical (I’m using Nhibernate and Query is a IQueryable of Session property on the base repo). Ecapsulating my queries in their own classes I get a the following code to do the same thing  public interface IQueryFilter&amp;lt;T&amp;gt; where T : class, IDomainObject {   Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; GetFilter(); } public class ExistingUserEmailAddressFilter : IQueryFilter&amp;lt;User&amp;gt; {   public string Email { get; set; }   public int UserId { get; set; }   public Expression&amp;lt;Func&amp;lt;User, bool&amp;gt;&amp;gt; GetFilter()   {     return user =&amp;gt; user.Email == Email &amp;amp;&amp;amp; user.Id != UserId;   } } public class ExistingUserEmailAddressFilter : IQueryFilter&amp;lt;User&amp;gt; {   public string Username { get; set; }   public int UserId { get; set; }   public Expression&amp;lt;Func&amp;lt;User, bool&amp;gt;&amp;gt; GetFilter()   {     return user =&amp;gt; user.Username = Username &amp;amp;&amp;amp; user.Id != UserId;   } }  The IQueryFilter concrete classes live in my domain layer.  My query class contains the parameters it needs to build the query.&#160; I also use PredicateBuilder – a cool helper class that is great when you need to build up more complex queries, such as when the query is conditional on values of the properties of your query object. e.g.&#160; A query for and advanced search screen.  public static class PredicateBuilder {   public static Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; True&amp;lt;T&amp;gt;() { return f =&amp;gt; true; }   public static Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; False&amp;lt;T&amp;gt;() { return f =&amp;gt; false; }   public static Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; Or&amp;lt;T&amp;gt;(this Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; expr1,                           Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; expr2)   {     var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast&amp;lt;Expression&amp;gt;());     return Expression.Lambda&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt;(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);   }   public static Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; And&amp;lt;T&amp;gt;(this Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; expr1,                          Expression&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt; expr2)   {     var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast&amp;lt;Expression&amp;gt;());     return Expression.Lambda&amp;lt;Func&amp;lt;T, bool&amp;gt;&amp;gt;(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);   } }  &#160;  Then, in my AbstractRepository I have the methods for using the filter via IQueryable:  public AbstractRepository&amp;lt;T&amp;gt; where T : class, IDomainObject {   // Other stuff removed...     public IEnumerable&amp;lt;T&amp;gt; GetByFilter(IQueryFilter&amp;lt;T&amp;gt; filter)   {     return Query.Where(filter.GetFilter()).ToList();   }     protected IQueryable&amp;lt;T&amp;gt; Query   {     get { return Session.Query&amp;lt;T&amp;gt;(); }   } }  Now whenever I need a new query for my database I inherit a class from IQueryFilter.&#160;  This might seem like more work and perhaps it is.&#160; I’ve gone this way because it’s been done before and because I can easily test my query logic.&#160; Note the simple query above to search for an existing email address.&#160; To test this I created a few simple unit tests (2 infact)  [TestFixture] public class ExistingUserEmailAddressFilterTests {   protected ExistingUserEmailAddressFilter filter;   [SetUp]   public void SetUp()   {     filter = new ExistingUserEmailAddressFilter();   }   [Test]   public void Given_the_user_already_has_this_email_address_the_email_doesnt_already_exist()   {     var user = new List&amp;lt;User&amp;gt;       {         new User {EmailAddress = &quot;test@test.com&quot;}.WithId(1)       };     filter.EmailAddress = &quot;test@test.com&quot;;     filter.UserId = 1;         Assert.IsFalse(user.Any(filter.GetFilter().Compile()));   }   [Test]   public void Given_another_user_already_has_this_email_address_the_email_already_exists()   {     var user = new List&amp;lt;User&amp;gt;       {         new User {EmailAddress = &quot;test@test.com&quot;}.WithId(1)       };     filter.EmailAddress = &quot;test@test.com&quot;;     filter.UserId = 2;     Assert.IsTrue(user.Any(filter.GetFilter().Compile()));   } }   WithId is a test helper allowing me to set the protected (setter) Id property so I can test.  public static class TestHelpers {   public static TModel WithId&amp;lt;TModel&amp;gt;(this TModel model, int id) where TModel : IHasId   {     var idProp = model.GetType().GetProperty(&quot;Id&quot;, BindingFlags.Instance | BindingFlags.Public);     idProp.SetValue(model, id);     return model;   } }  The effort to test these queries if I put them on the repository would be rather difficult.&#160; Admittedly this isn’t perfect, because the Linq provider for your ORM may generate different (or not be able to generate) SQL for your query. Still, this problem exists if I do the query in the Repository.  Am I mad?&#160; Thoughts?</description>
                    <link>http://www.robertgray.net.au.aspx/posts/encapsulating-repository-queries</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/encapsulating-repository-queries</guid>
                    <pubDate>Tue, 19 February 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Updated Web Essentials 2012 and now Visual Studio 2012 crashes?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/updated-web-essentials-2012-and-now-visual-studio-2012-crashes</comments>
                    <description>Did you just install Web Essentials 2012 version 2.5.1 and now Visual Studio 2012 crashes? &#160;This just happened to me. &#160;To fix:  &#160;     Open Visual 2012 but don&#39;t load any solutions / projects  Open Extensions and Updates from the Tools menu  Disable Web Essentials 2012  Delete the .suo file for your solution  Open the solution in Visual Studio  See that it loads and VS doesn&#39;t crash  Enable Web Essentials 2012  Restart VS 2012 and reload your solution.  Happy times!</description>
                    <link>http://www.robertgray.net.au.aspx/posts/updated-web-essentials-2012-and-now-visual-studio-2012-crashes</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/updated-web-essentials-2012-and-now-visual-studio-2012-crashes</guid>
                    <pubDate>Tue, 19 February 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>A knockoutjs binding for twitter bootstraps popover</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/a-knockoutjs-binding-for-twitter-bootstraps-popover</comments>
                    <description>I’ve just added twitter bootstraps’ popover to my project, using it to confirm delete’s of items bound using knockoutjs.&#160; I wanted a way to easily setup popovers in a knockoutjs world and to provide some level of customisation using knockout.   Declare a popover using knockoutjs – declaratively  Completely customise the contents of the popover (html)  Close the popover from a button/link within the popover body or custom html.  Allow only one popover to be displayed at any one time   &#160;  &#160;  I definitely did not want to write a bunch of javascript each time I wanted a popover or have to call the following to configure popovers:  $(&#39;.mypopovers&#39;).popover(options);  Declaring a popover using knockoutjs  Declaring a popover with knockoutjs involves creating a knockout binding and from within that binding registering the DOM element as a popover  ko.bindingHandlers.popover = {   init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {             var options = ko.utils.unwrapObservable(valueAccessor());     var defaultOptions = { };     options = $.extend(true, {}, defaultOptions, options);     $(element).popover(options);   } };  This will at it’s most simplest, create the popover.&#160; You can either set properties using the options object, or using data attributes.  &amp;lt;a data-bind=&quot;popover: { title: &#39;My Title&#39; }&quot;&amp;gt;&amp;lt;/a&amp;gt;                  The above would create an empty popover with title “My Title”.&#160; Lets get a bit more complex though, and allow setting of the contents of the popover to any HTML.  &#160;  Completely customise the contents of the popover (html)  There were two routes I could have taken here, either set the HTML to display in the view model, or define the HTML in the view and pass in a container id that the HTML will be extracted from.&#160; I chose the later.  &amp;lt;div id=”popover-html” style=&quot;display:none&quot;&amp;gt;                     &amp;lt;div style=&quot;width:140px; margin:auto&quot;&amp;gt;                         &amp;lt;button class=&quot;btn btn-primary&quot; data-bind=&quot;click: deleteTemplate&quot;&amp;gt;Yes&amp;lt;/button&amp;gt;     &amp;lt;button class=&quot;btn&quot; style=&quot;margin-left: 10px&quot;&amp;gt;No&amp;lt;/button&amp;gt;   &amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt;                                 &amp;lt;a class=&quot;pull-right&quot; data-bind=&quot;popover: { contentHtmlId: “popover-html”, title: &#39;Delete Template&#39;, html: true }&quot;&amp;gt;&amp;lt;i class=&quot;icon-remove&quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;  I’ve defined my own knockout binding property contentHtmlId . This binding is used to know where to extract the html from that will be inserted into my popover. I also need to set html to true.  Some logic is needed within the binding to extract the html and insert it into the popover. The part I stumbled with was setting the knockout data binding once the html was created. Because the popover is created as a part of calling ko.applyBindings for the view, inserting HTML with declared bindings into the popup will not bind those bindings; it must be done manually.  ko.bindingHandlers.popover = {   init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {             var options = ko.utils.unwrapObservable(valueAccessor());     var defaultOptions = { };     options = $.extend(true, {}, defaultOptions, options);     var htmlContent = &#39;&#39;;     var containerId;     if (options.contentHtmlId) {             containerId = &#39;popoverHtml-&#39; + options.contentHtmlId;       htmlContent = &quot;&amp;lt;div id=&#39;&quot; + containerId + &quot;&#39;&amp;gt;&quot; + $(&quot;#&quot; + options.contentHtmlId).html() + &quot;&amp;lt;/div&amp;gt;&quot;;       options.content = htmlContent;     }        $(element).popover(options);             ko.utils.registerEventHandler(element, &quot;click&quot;, function () {             if (options.contentHtmlId) {         var thePopover = document.getElementById(containerId);         if (thePopover)           ko.applyBindings(viewModel, thePopover);       }     });   } };  If a contentHtmlId is supplied the binding retrieves the html within that element and assigns it to the popovers’ content option. html should also be enabled in the options, or the html will be escaped. Next it needs to set the bindings on the newly inserted HTML, just in-case there were any bindings declared. This is done in the click event of the element. You may notice in the HTML definition that I’ve declared data-bind=”click: deleteTemplate” . This is what needs to be bound the above code. I ran into a problem where I was originally calling data-bind=”click: $parent.deleteTemplate” because the parent of the item I was deleting (a Template) is responsible for deleting the Template and removing the deleted Template from it’s collection of Templates. However, viewModel passed into my init function is only a viewModel of the Template, there is no way a $parent binding will work. To get around this limitation I had to add a deleteTemplate function to my Template and a reference to its parent. I then called parent.deleteTemplate(self) from within the Template view model.  Close the popover from a button/link within the popover body or custom html  Next I needed to be able to close the popover from the “No” button within the popover.&#160; To do that I needed some way to identify the button that would act as the close and then hook up the close event.  Declare the close button:  &amp;lt;button class=&quot;btn&quot; data-popoverclose=&quot;true&quot; style=&quot;margin-left: 10px&quot;&amp;gt;No&amp;lt;/button&amp;gt;  Update my custom binding to find the &quot;close&quot; button and attach an event that will call hide on the popover:  ko.bindingHandlers.popover = {   init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {             var options = ko.utils.unwrapObservable(valueAccessor());     options = $.extend(true, {}, defaultOptions, options);     var htmlContent = &#39;&#39;;     var containerId;     if (options.contentHtmlId) {             containerId = &#39;popoverHtml-&#39; + options.contentHtmlId;       htmlContent = &quot;&amp;lt;div id=&#39;&quot; + containerId + &quot;&#39;&amp;gt;&quot; + $(&quot;#&quot; + options.contentHtmlId).html() + &quot;&amp;lt;/div&amp;gt;&quot;;       options.content = htmlContent;     }         $(element).popover(options);             ko.utils.registerEventHandler(element, &quot;click&quot;, function () {             if (options.contentHtmlId) {         var thePopover = document.getElementById(containerId);         if (thePopover)           ko.applyBindings(viewModel, thePopover);       }       $(&#39;button[data-popoverclose]&#39;).click(function() {         $(element).popover(&#39;hide&#39;);       });     });   } };  Nearly there but I also only wanted one popover open at any one time. I didn&#39;t want to close all but one popover for every possible popover, just the ones that needed to be open only by themselves. To do that I attach a click event to the body that scans for all &quot;exclusive&quot; popovers and closes them.  ko.bindingHandlers.popover = {   init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {             var options = ko.utils.unwrapObservable(valueAccessor());     var defaultOptions = { exclusive: true };     options = $.extend(true, {}, defaultOptions, options);     var htmlContent = &#39;&#39;;     var containerId;     if (options.contentHtmlId) {             containerId = &#39;popoverHtml-&#39; + options.contentHtmlId;       htmlContent = &quot;&amp;lt;div id=&#39;&quot; + containerId + &quot;&#39;&amp;gt;&quot; + $(&quot;#&quot; + options.contentHtmlId).html() + &quot;&amp;lt;/div&amp;gt;&quot;;       options.content = htmlContent;     }     if (options.exclusive) {       $(element).attr(&quot;exclusive&quot;, &quot;&quot;);             // Setup event to close any open &#39;exclusive&#39; popovers.       var $visiblePopover;       $(&#39;body&#39;).on(&#39;click&#39;, &#39;[exclusive]&#39;, function() {                 var $this = $(this);         if ($this.data(&quot;popover&quot;).tip().hasClass(&#39;in&#39;)) {           // if another was showing hide it           $visiblePopover &amp;amp;&amp;amp; $visiblePopover.popover(&#39;hide&#39;);           $visiblePopover = $this;         } else {           $visiblePopover = &#39;&#39;;         }       });     }     $(element).popover(options);             ko.utils.registerEventHandler(element, &quot;click&quot;, function () {             if (options.contentHtmlId) {         var thePopover = document.getElementById(containerId);         if (thePopover)           ko.applyBindings(viewModel, thePopover);       }       $(&#39;button[data-popoverclose]&#39;).click(function() {         $(element).popover(&#39;hide&#39;);       });     });   } };  By default I&#39;m assuming that all popovers are exclusive, as set in the defaultOptions. Then, if exclusive is turned on for this element I add an exclusive attribute which will be used by the body click event to select the appropriate popovers to hide.  All Done!  There are a few improvements that could and should be made. One such is only creating the body click event once.  Update  There was a bug in my initial implementation above. That was, when you close a popover via the No button then try to open that popover again (without a page refresh) the popover does not open.  To get around that problem I upated my click handler on the popover trigger. &#160;Rather than relying on a body event I now close all the&#160; other popovers in the click to open event of a popover. The new block of code now looks like this (and works!)  ko.utils.registerEventHandler(element, &quot;click&quot;, function () {               $(&#39;*&#39;).filter(function() {     if ($(this).data(&#39;popover&#39;) !== undefined) {           return !$(this).is($(element));               }     return false;   }).popover(&#39;hide&#39;);                if (options.contentHtmlId) {     var thePopover = document.getElementById(containerId);     if (thePopover) {       ko.applyBindings(viewModel, thePopover);               }   }          $(&#39;button[data-popoverclose]&#39;).click(function() {     $(element).popover(&#39;hide&#39;);   }); });</description>
                    <link>http://www.robertgray.net.au.aspx/posts/a-knockoutjs-binding-for-twitter-bootstraps-popover</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/a-knockoutjs-binding-for-twitter-bootstraps-popover</guid>
                    <pubDate>Thu, 07 February 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>iRacing Star Mazda Round 1 2013.1 review</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/2/iracing-star-mazda-round-1-20131-review</comments>
                    <description>For my first race for the Vortex Star Mazda team I was competing at Spa-Francorschamps iRacing circuit. &#160;This season I&#39;m in division 3, with another Vortex teammate, Ryosuke.  My preparation this week has been a little interrupted, with affairs in real life taking a priority. &#160;At the start of the week (Tuesday night) I was doing 2:18.4&#39;s using my setup from last season. By Saturday morning, after maybe 2 hours of practice, I lowered my time down to a 2:16.9 on the same setup. &#160;It was all about throttle control and getting the right lines and that&#39;s something that only time on track could give me. &#160; I saw still a whole second off Ryosuke&#39;s time. &#160;At least his time was very quick in the overall standings.  I tried Tony Rappusard&#39;s setup from the Forums, which ran slightly more wing front and back (21/22), but I couldn&#39;t get any faster and it felt less stable than my 20/20 wings - comparing the two sets, his seemed to have a lot more rebound damping front and back.  I qualified on Saturday afternoon with a 2:16.7 which at the time put me at the top of the Vortex Racing Star Mazda guys. &#160;I knew Ryosuke was doing 2:16.0&#39;s so my qualifying time was little consolation. &#160;Before I could go racing I needed to know how my race pace was, so after qualifying I spent about 90 minutes practicing with race fuel loads. I didn&#39;t change the setup any and managed consistent 2:17.3&#39;s, which when I looked at the fastest race laps of guys competing in the splits I thought I&#39;d be in, that compared pretty well. &#160;By this stage I was consistently in the top couple of drivers in every practice session. &#160;I dind&#39;t want to race yet, as I was very tired - I thought it best I leave the racing until Sunday.  Sunday morning came and I lined up for my first race in pole position by over a second on 2nd place. Unfortunately I didn&#39;t stay there off the line and we battled up down the straight and into the subsequent corner. &#160;I backed out, not wanting to cause an incident and confident I had enough pace to retake the lead before the 13 lap race was over. &#160;As we came to the final chicane (second last corner) I was right up on his gearbox. &#160;I made a better exit from that corner and pulled along side into La Source, the final turn. &#160;We both made it through clean but he dropped it on the exit (as I&#39;d done a few times in practice) and speared across the track clipping my rear left wheel and completely pushing it up and taking me out of the race. &#160;I didn&#39;t want my round to end on that low note, so I thought I may as well do the full four (where only the best one of four will count).  Race 2 and I was again on pole, but this time by a slim margin. &#160;I finished 2nd this race, to someone who didn&#39;t qualify, but was faster, and I had a decent little battle with the guy 2nd on the grid. &#160;This race also netted me 127 points, my best race of the round.  Race 3 and I started 2nd to a guy a full second faster than me, so I knew I had little chance of winning. &#160;I got by on the first lap and led for a few laps but his superior speed was too much and I didn&#39;t waste time driving defensively. &#160;On the same lap I lost 1st I also lost 2nd to the 2nd place grid sitter from the previous race. &#160;We were very close in speed and I did hold him up maybe just a little, I felt, in the previous race, so I made it easy for him to get by this time. I hoped I would be able to tuck up behind him and use my vastly superior straight line speed to blast by him again. &#160;Unfortunately I didn&#39;t get it right and as I ran close to him coming into Les Combes, we touched for a 0x. &#160;He was ok thankfully but I slipped off track and lost a few positions while I recovered. &#160;It did make for some fun racing though as I hunted down the two drivers in front of me, Mark Sheppard and the top ranked driver in the race. &#160;I was catching them both but come off the track just as I&#39;d caught up to within 2 seconds. &#160;This allowed for the driver behind me to close right back up again and we battled it out for the last lap. &#160;With him right behind me, I ceded the place at the end of the 2nd last lap, knowing I had far greater straight line speed. &#160;As we shot along Kemmel Straight, I pulled of the draft and shot back into 5th place, and held the place until the end of the race - getting a thanks from the driver for a near race long tow around the track :)  Race 4 and I again started from 2nd, this time to my fellow Moggill Area resident and namesake, Scott Gray. &#160;We raced within 2.0 seconds for the entire race. &#160;I didn&#39;t quit have enough speed to get in the tow until the very end when he probably made a mistake (although I led for a few seconds on the first lap when I got by him on Kemmel and into Les Combes - a recurring theme ;)). &#160;Two laps from the end I&#39;d reduced the 2 second deficit to 1.3 seconds and could see in iSpeed that I was starting to get the advantage of being in the slip-stream when I made a mistake into turn 12 and lost the rear. &#160;I still managed to finish 2nd, albiet 9 seconds back and I thoroughly enjoyed the good clean race. &#160;  Here are the highlights of my Round.  http://www.youtube.com/watch?v=G8WLnYelEwY</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/2/iracing-star-mazda-round-1-20131-review</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/2/iracing-star-mazda-round-1-20131-review</guid>
                    <pubDate>Tue, 05 February 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>When to use Response.CreateResponse versus HttpResponseException with Web Api</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/2/when-to-use-responsecreateresponse-versus-httpresponseexception-with-web-api</comments>
                    <description>When I first started using and learning .NET Web Api late last year I, like most I expect, used the MSDN documentation, Pluralsight training videos, and the Todo project that comes with the recent VS 2012 SPA (Single Page App) template addition. &#160;In this SPA template HttpResponseException is thrown from GET methods, while Request.CreateResponse is used from POST, PUT, DELETE methods. &#160;For example, using either of the following  throw new HttpResponseException(HttpStatusCode.NotFound); // or return Request.CreateResponse(HttpStatusCode.NotFound);  Both return the same data to the client, so they&#39;re basically equivalent right? &#160;I followed this practice with my code. Where I differ is that I use an Action Filter to control NHibernate Transactions. &#160;Returning Request.CreateResponse to indicate error conditions does not rollback any transaction created by the Action Filter. &#160;For transactions to be implicity rolled back an Exception needs to be thrown.  If you&#39;re modifying data in your PUT, POST, or DELETE (which everyone does) you&#39;ll need to throw HttpResponseException if you want any ambient transactions to be rolled back. &#160;You can still use Request.CreateResponse though, if you haven&#39;t modified anything within the transaction yet.  (GET&#39;s throw an exception when they don&#39;t return a HttpResponseMessage - otherwise you could also call Request.CreateResponse from within GET methods).  A simple solution is anywhere you are calling Request.CreateResponse and should be calling throw new HttpResponseException, just wrap the response in the exception.  // Make some changes to your ORM entity. // Check for validity or business rules on your entity if (entity.DoNotPersistMe()) {   var errorResponse = Response.CreateResponse(HttpStatusCode.BadRequest);   throw new HttpResponseException(errorResponse); }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/2/when-to-use-responsecreateresponse-versus-httpresponseexception-with-web-api</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/2/when-to-use-responsecreateresponse-versus-httpresponseexception-with-web-api</guid>
                    <pubDate>Tue, 05 February 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Triggering validation from a custom knockoutjs binding</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/2/triggering-validation-from-a-custom-knockoutjs-binding</comments>
                    <description>In a project I am working I rolled my own knockoutjs binding for the twitter bootstrap datepicker by @eternicode .&#160; I’ve had some “fun” getting the binding to play nice with Chrome and dates in Australia.&#160;  My datePicker binding is used to configure a text box for use of eternicode’s datepicker, without having to call it using jquery.&#160; It’s also used to synchronise the UI with the knockoutjs ViewModel.  To hook it up in I call the following:  &amp;lt;input name=&quot;dateofbirth&quot; type=&quot;text&quot; data-bind=&quot;datePicker: program.dateOfBirth&quot; placeholder = &quot;dd/mm/yyyy&quot; class=&quot;span2&quot; /&amp;gt;  &#160;  which says to bind the html element to the dateOfBirth property on my ViewModel, and configure the element to use eternicode’s datepicker.&#160; My custom binding is as follows:  ko.bindingHandlers.datePicker = {   init: function (element, valueAccessor, allBindingsAccessor) {         //initialize datepicker with some optional options     var options = allBindingsAccessor().datepickerOptions || { format: &#39;dd/mm/yyyy&#39;, autoclose: true };     $(element).datepicker(options);         //when a user changes the date, update the view model     ko.utils.registerEventHandler(element, &quot;changeDate&quot;, function (event) {       var value = valueAccessor();       if (ko.isObservable(value)) {         value(event.date);       }     });         ko.utils.registerEventHandler(element, &quot;change&quot;, function () {       var widget = $(element).data(&quot;datepicker&quot;);             var value = valueAccessor();             if (ko.isObservable(value)) {         if (element.value) {           var date = widget.getUTCDate();                               value(date);         } else {                     value(null);         }       }     });   },   update: function (element, valueAccessor) {           var widget = $(element).data(&quot;datepicker&quot;);     //when the view model is updated, update the widget     if (widget) {       widget.date = ko.utils.unwrapObservable(valueAccessor());       if (!widget.date) {         return;       }       if (_.isString(widget.date)) {         widget.setDate(moment(widget.date).toDate());         return;       }       widget.setValue();     }       } };  Now the background is out of the way, the problem…  I am using the knockout validation library by ericmbarnard , which works great, except that when the value of my dateOfBirth property (or any property that is data bound via datePicker), I do not ever get the UI notification that is presented with the other controls.&#160; I wanted a span element to be present after the datePicker control when its value is invalid.  After some digging I came to the following lines in the validation library source:  // override for KO&#39;s default &#39;value&#39; binding (function () {   var init = ko.bindingHandlers[&#39;value&#39;].init;     ko.bindingHandlers[&#39;value&#39;].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {     init(element, valueAccessor, allBindingsAccessor);     return ko.bindingHandlers[&#39;validationCore&#39;].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);   }; }());  &#160;  What the above is doing is taking the default init method that ships with knockout’s value binding and adding a call to the validationcore at the end.&#160; I checked my own code and when I bind to value, rather than datePicker, I get the desired message.&#160; It seemed to me that I needed to hook into the validation library from the init method of my custom binding and after looking at knockouts value binding implementation and the above code from the validation library, it seems using a modification of the above snippet would do this trick.&#160;  With this in mind, here is my code which now gives me the functionality I desired:  (function() {   var init = ko.bindingHandlers[&#39;datePicker&#39;].init;     ko.bindingHandlers[&#39;datePicker&#39;].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {     init(element, valueAccessor, allBindingsAccessor);     return ko.bindingHandlers[&#39;validationCore&#39;].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);   }; }());  Pretty simple. All I needed to do was copy the existing code and replace ‘value’ with ‘datePicker’ (my custom binding).&#160; Oh yes, please remember to put any code NOT in the knockout.validation.debug.js source, or it’ll get clobbered when you update to the latest version – and it won’t exist if you use the minified version in your release build.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/2/triggering-validation-from-a-custom-knockoutjs-binding</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/2/triggering-validation-from-a-custom-knockoutjs-binding</guid>
                    <pubDate>Fri, 01 February 2013 16:35:00 </pubDate>
                </item>
                <item>
                    <title>Cancer</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/1/cancer</comments>
                    <description>Last November (2012) my wife went into her Obstetrician for a post baby check-up now my son is four months.&#160; We both started to get a bit concerned when his receptionist rang a few times to check if my wife was keeping her appointment on December 13 and that keeping it was very important.&#160; Probably not the best sign.  At that appointment my wife was told her checkup revealed a few abnormalities on her cervix; she has cancer.&#160;&#160; My work Christmas weekend was the next day (all expenses paid weekend at a 5star resort on the Gold Coast) which she didn’t want to miss, otherwise she’d have had a cone biopsy the next day (December 14).&#160; As it was, she went in on the Monday, the day before her 32 nd birthday.&#160;&#160;  That week was horrible for her, as she waited for the results to confirm the extent of cancer.&#160; We had been pre-warned anyway and she was going to take the more radical option.&#160; That week we both spent stressing and on Chirstmas eve we went in for the results.&#160;  The diagnosis and treatment was as expected, a total hysterectomy as soon as the Obstetrician could perform, which would be first thing January 14, when he got back from a two week break.&#160; Again, otherwise it would have been earlier.  Cancer is one of those things you don’t mess with.  Unfortunately the operation did not go as smoothly as it could and my wifes’ bladder was damaged, resulting in her stomach being opened up and a catheter inserted.&#160;&#160;  On the Thursday after the operation last week the analysis of the uterus showed more cancer than initially discovered, which was the exact reason the hysterectomy was recommended, that and due to the glandular cause of the cancer, it was given that it would reappear in the uterus anyway.&#160;  It’s been a challenging month or so, and especially the last two weeks, where my wife has been basically bed-ridden, unable to use any torso muscles and in a lot of pain.&#160;  Something very humbling from all of this is the amount of help we have received from friends, family, and the wider community.&#160; Many people have donated their time, brought meals around for us both while my wife was in hospital and now she is home, the wider community have chipped in and got services for lawn care and house cleaning, and the kids have been baby sat by various people.&#160; It’s very moving, humbling, and just makes me want to give back more. (My wife was great too, before she went to hospital she’d prepared and frozen meals to last us the week).  The most important thing, something I cannot stress enough after recently looking down this barrel, and having to contemplate the potential early loss of my best friend, wife and the mother of my children is that it’s cervical cancer (and I assume it’s direct male analogue, prostate cancer) is not something to ignore.&#160; Checkups aren’t that frequent and early detection will more than likely save lives, as I’m sure it did with my wife.&#160; I shudder to think what may have happened had Ed not unexpectedly came along. &#160;The checkup may have been years down the road, when it was too late.&#160;  &#160;  Co-incidentally, I meant to type this up last week but delayed.&#160; Today I found a blog post by Scott Hanselman , one of the developers I admire, who is currently going through nearly the exact same thing with his wife.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/1/cancer</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/1/cancer</guid>
                    <pubDate>Fri, 25 January 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Ordering child items with NHibernate</title>
                    <author></author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/1/ordering-child-items-with-nhibernate</comments>
                    <description>It’s common to need to have an ordered list of something.&#160; In my application I have a Todo List which has ordered items that need to be done, Todo List Items.&#160; I want the user to be able to re-order the list of todo items as their priority changes.&#160;&#160; At first glance I thought the TodoListItem needed a Position property that denoted it’s position within the list.&#160; Using this implementation I would tell the TodoList to change the position of the TodoListItem to a particular position, which would likely mean other TodoListItems position would need updating.&#160;  For example, if a TodoListItem had position = 3 and I wanted to make it position = 5, I would need to move items at positions 5 and 4 down one.&#160; Similar adjustments would have to be made when items are removed.&#160; (Added items are added to the end of the list, meaning no other items need to change).  I got this solution working but I didn’t like that a developer could set todoListItem.Position and that would muck up other items in the list if he didn’t remember to change the effected items also.&#160; In short it was a leaky API.  Lists have an implied order so I wanted to make use of this and let NHibernate take care of updating the value of Position.&#160; I at first removed the Position property from TodoListItem, thinking it not necessary if I can calculate the position from the it’s place in the list.  To cut to the solution (which is what we’re all after), after some experimentation and failure I arrived at the following working solution.  Model  public class TodoList {   private IList&amp;lt;TodoListItem&amp;gt; _todoListItems;     public TodoList()   {     _todoListItem = new List&amp;lt;TodoListItem&amp;gt;();   }   public virtual IEnumerable&amp;lt;TodoListItem&amp;gt; TodoItems { get { _todoListItem; } }   public virtual int GetPositionOf(TodoListItem item)   {     return _todoListItems.IndexOf(item);   }   /* Other fields removed for brevity */ } public class TodoListItem {   public TodoList TodoList { get; set; }   public virtual int Position {     get     {       // Will Exception here if TodoList is null       // Use whatever gaurds suit your needs       return TodoList.GetPositionOf(this);     }     protected set { }   } }  &#160;  You’ll notice I’ve included the Position property on TodoListItem but it’s calculated from the TodoList (good!).&#160;&#160; What is a little unexpected (for me) is that I need to have a protected set method that does nothing.&#160; My guess is the Dynamic Proxy created by NHibernate needs this to persist the Position field.&#160; Certainly without it your application will nearly work, just the Position column in the table won’t be updated, and new TodoListItem’s cannot be added.  Now for the FluentNHibernate mappings.  Fluent NHibernate Mapping  public class TodoListMap : ClassMap&amp;lt;TodoList&amp;gt; {   public TodoListMap()   {     /* Other mappings omitted for brevity */     HasMany(x =&amp;gt; x.Items)       .AsList(part =&amp;gt; part.Column(&quot;Position&quot;)       .KeyColumn(&quot;TodoListId&quot;)       .Access.CamelCaseField(Prefix.Underscore)       .Inverse()       .Cascade.AllDeleteOrphan();   } } public class TodoListItemMap : ClassMap&amp;lt;TodoListItem&amp;gt; {   public TodoListItemMap()   {     /* Other mappings omitted for brevity */     // Needed or the Position will not save to the db     // Even though we never explicitly set it in our code     Map(x =&amp;gt; x.Position);     References(x =&amp;gt; x.TodoList).Not.Nullable();   } }  &#160;  I’m not sure HOW the Position field is being updated.&#160; There’s some stronger magic in play than I know.&#160; However, it definitely does work. Now the only way the order of Todo List Items can be set is via the TodoList owning entity and all other TodoListItems are updated as needed, with me having to manually updated their Position. MAGIC!</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/1/ordering-child-items-with-nhibernate</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/1/ordering-child-items-with-nhibernate</guid>
                    <pubDate>Thu, 24 January 2013 00:00:00 </pubDate>
                </item>
                <item>
                    <title>Has iRacing instigated it&#39;s own demise?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2013/1/has-iracing-instigated-its-own-demise</comments>
                    <description>The schedules for season 1 2013 are out on the iRacing forums and it is accurate to say members are going ballistic on both the forums and twitter, in a bad way.&#160; I’ve heard from some twitter regulars that Trucks, a C class Oval series, has also had similar treatment to that of the C class Road Star Mazda I race.&#160;  The main complaint is the switch from a 40 minute race to a 20 minute race.&#160; The C class Star Mazda now has shorter races than the D class Skip Barber, the exact step below it on the ladder up the open wheelers.&#160; It’s a move that flies completely in the face of iRacing being a simulator, and a service that as closely as possible matches the real series’.&#160;&#160;  Here’s the proposed race schedule for season 1:   Spa-Francorchamps&#160;– 10 laps  Okayama Short Course&#160;– 26 laps  Watkins Glen Classic Boot&#160;– 14 laps  Zolder&#160;Grand Prix&#160;– 15 laps  Motegi&#160;– 13 laps  Phoenix International Oval&#160;– 48 laps  Interlagos&#160;Grand Prix – 25 laps  Mid-Ohio Sports Car&#160;– 16 laps&#160;  New Hampshire&#160;South Oval – 23 laps  Zandvoort&#160;Chicane&#160;– 14 laps  Lime Rock Park&#160;– 26 laps  Suzuka Grand Prix&#160;– 12 laps.   That’s somewhat less laps per race than previous seasons.&#160; Another big complaint is the inclusion of two rookie tracks.&#160; Both Okayama short and Lime Rock are used in the rookie series.&#160; Even the Skip Barber and Mustangs (D class) race on the full Okayama circuit.&#160;  Common consensus is iRacing is trying to push drivers out of the hugely popular Star Mazda series and into the Lotus 79, the next step up the ladder.&#160; A lot of top Star Mazda drivers are talking of walking away from iRacing, disgusted and angry with the way iRacing are treating its loyal customers. While I’m not ready to quit over this (heck I just spent 4 days entering races and not racing them, dropping my iRating from 2600 to 1300 intentionally and I’m still going down..) I feel this is a dick move by the guys at iRacing and it contrasts all the points they are trying to sell iRacing with.&#160; Or at least they were trying to sell iRacing as THE serious sim.&#160; I have to question that now, with the introduction of various aides and touch capabilities of recent months.  It really does seem, based on comments on twitter and the iRacing forums, that the main reason people choose iRacing is because of the longer races and the realism.&#160; iRacing seems to be pushing this aside in what appears to be attempts to lure the “console” players into iRacing.&#160; Again, double taking on their original mission statement.  Watch this space. There’s A LOT of anger in the community over this.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2013/1/has-iracing-instigated-its-own-demise</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2013/1/has-iracing-instigated-its-own-demise</guid>
                    <pubDate>Tue, 15 January 2013 22:31:00 </pubDate>
                </item>
                <item>
                    <title>The oval experiment</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/12/the-oval-experiment</comments>
                    <description>It&#39;s that time of the Star Mazda season. Race 5 and Iowa. &#160;Iowa is an oval track and it&#39;s usually a wreck-fest. &#160;Partly because most of us roadies can&#39;t drive ovals and partly because a bunch of oval guys usually join in and cause chaos. &#160;Most decent driver treats it as a drop week and stays well clear of Iowa. &#160;Myself included, along with my usually SM buddies.  Ironically I decided to spend my time in the Rookie Oval Streetstock series and try and get my D license. I had to get my license up from R 2.64 to above R 4.00 for the autobump to D. &#160;At first my approach was to race hard but not qualify. &#160;That worked pretty well and managed decent results. &#160;But then I got in a few splits were I kept getting taken out by the spinners in the mid-pack on the opening few laps. &#160;My race pace and warmup pace meant I should be up the front, so I decided to qualify.&#160;  My first effort was a 24.320, which was good enough for about 4th or 5th in the splits I was getting placed in. &#160;I started from fourth for my first race after qualifying and ran in that place until I took 3rd after the leader ran high and left a gap. &#160; &#160;Unforunately for us both he ran high and then turned back in while I was right there. &#160;He hit me with a 0x and caused me no problems but took himself out. &#160;I guess he blamed me for his woes because he waited for me to come around then drove down into me and took me out. &#160;     In another race I passed another driver cleanly into turn 1 (they mostly all seem to run high and I just roll off the gas and stick it up the inside) . Unfortunately he got tapped by the guy right behind me and spun. &#160;What did he do? &#160;What the majory of rookie oval drivers do, sought retribution for this wrong. &#160;He waited for me to come around again, left room for me to pass then didn&#39;t slow down in the corner and took me out. &#160;I didn&#39;t even cause the accident.  I also requalified with 24.117, which was good enough for pole on two occassions as well as a number of front row starts. &#160;  I was leading another race with a 4.5second lead after 6 laps (that is a rather decent lead ;)) and the two guys decided to &#160;extract vengeance on each other. &#160;Driver A puts driver B into the wall by blatantly turning up on him. Both drivers hit the wall and come back down, straight into my path and I had nowhere to go. &#160;Race over.  What amazed me was even though I qualified I generally started from the pits, trying to be safer. It didn&#39;t make a lot of difference and I&#39;d still get caugt up on other peopls accidents. I tried leading from the front and that still meant I got caught up in stupidity.&#160;  The only approach that works is to drive with respect, slowing down for all accidents, letting other drivers through, staying off the radar of the crazy drivers. &#160;  By day two of my oval experiment I was into decent splits and most of this went away. From there it was pretty smooth sailing. &#160;Although, race two on Sunday was possibly the most humerous race I have ever been in. &#160;So many drivers were insulting each other. &#160;So many crashes, so many retaliation hits. &#160;I drove around thinking this race would make the perfect video. Unfortunately I forgot to save the replay :( &#160;  At least with road tracks there are wide open spaces to fly off into so when rookies get a bit hot under the collar and decide to take out someone, they generally don&#39;t grab half the field. &#160;  Oh and the rookie oval experience wouldn&#39;t be complete without being called the worst driver ever and being told to get the fuck off the track. &#160;Thanks  John A. Schulte . &#160;John, did you notice the &quot;worst driver in the world&quot; managed to get out of Rookie in two days, with 9 out of 19 races being incident free, with 2 wins, 2 poles, and a bunch of wins gone wanting through people taking me out?</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/12/the-oval-experiment</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/12/the-oval-experiment</guid>
                    <pubDate>Mon, 10 December 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>Bulk insert of rows with sql server</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/12/bulk-insert-of-rows-with-sql-server</comments>
                    <description>If you&#39;re a developer who started their trade before the ORM days, you&#39;ve likely had to insert data en masse into a sql server db , if for only test purposes.&#160; Usually this involves writting out INSERT INTO statements, one complete DDL command for each row.&#160; Seems rather crazy to repeat the columns you want to insert data into.&#160; There was a sort-of workaround where you could use SELECT and UNION ALL to put them together.  INSERT INTO [Region] ([Id], [Code],[Description],[SortOrder]) VALUES (1, &#39;LS&#39;, &#39;Lumbar Spine&#39;, 1) INSERT INTO [Region] ([Id], [Code],[Description],[SortOrder]) VALUES (2, &#39;NH&#39;, &#39;Neck &amp;amp; Head&#39;, 2) INSERT INTO [Region] ([Id], [Code],[Description],[SortOrder]) VALUES (3, &#39;KN&#39;, &#39;Knee&#39;, 3)  INSERT INTO [Region] ([Id], [Code],[Description],[SortOrder]) SELECT 1, &#39;LS&#39;, &#39;Lumbar Spine&#39;, 1 UNION ALL SELECT 2, &#39;NH&#39;, &#39;Neck &amp;amp; Head&#39;, 2 UNION ALL SELECT 3, &#39;KN&#39;, &#39;Knee&#39;, 3  &#160;  Since Sql Server 2008 (!) there has been a concept called Row Constructors which allows Sql Server developers to avoid this verbose syntax.  INSERT INTO [Region] ([Id], [Code],[Description],[SortOrder]) VALUES   (1, &#39;LS&#39;, &#39;Lumbar Spine&#39;, 1),   (2, &#39;NH&#39;, &#39;Neck &amp;amp; Head&#39;, 2),   (3, &#39;KN&#39;, &#39;Knee&#39;, 3)  &#160;  A whole bunch easier to see and saves the extra typing. If only I wasn’t 4 years late to the party…</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/12/bulk-insert-of-rows-with-sql-server</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/12/bulk-insert-of-rows-with-sql-server</guid>
                    <pubDate>Fri, 07 December 2012 21:50:00 </pubDate>
                </item>
                <item>
                    <title>Do yourself a favour go and race other series often</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/12/do-yourself-a-favour-go-and-race-other-series-often</comments>
                    <description>I think I&#39;m going through an iRacing blue patch. &#160;I started the season wanting to race V8&#39;s and dropped out from that series after two rounds, when I failed to make T1 at round 2. &#160;I don&#39;t want to ruin it for the people that are dead serious about that series.  I completely missed a race last week, when the Star Mazda&#39;s went to Zolder. &#160;I like the track and was quick but an operating system upgrade and a business weekend meant not enough time. &#160;This round about to end wasn&#39;t much better in the Star Mazda&#39;s. &#160;While I enjoy Mid Ohio, I&#39;ve never been particularly successful there and this week I sayed up too late hacking out code and was way too tired on Saturday to race effectively. I kept crashing. &#160;  My iRating dropped down into the mid 2200&#39;s now. &#160;Not so cool. &#160;And you know what? &#160;I shouldn&#39;t care and I think I am caring less. &#160;  With the lack of pace in the Star Mazda I tried my hand a few other cars. &#160;The Lotus 79 was at Sonoma, so I jumped in a practice session (did two actually). &#160;Initially I was nine seconds off the pace of the second slowest - I had a 1:34 they had a 1:25, but I took out some fuel and played with setup and familiarised myself with the awesome stopping power and grip of the Lotus and pretty soon I was turning 1:23&#39;s. &#160;The fastest time I saw, from guys I know to be FAST was a 1:19 and I think, according to my sector times, had I kept at it I would have been down to the 1:21&#39;s in another half an hour.  I also jumped in the Grand Touring Cup in the Jetta, banging around a track I very rarely get to drive, as it&#39;s not in any of my normal series rotations, Barber Motorsport Park. I like that track. &#160;After about 30 minutes I was just slower than the fastest Jetta time, and 2nd fastest overall. &#160;  Another series I had a go in was the inRacing Challenge, in the Pontiac Solstice. &#160;A lower powered car but it sounds cool. &#160;I like it and I again was setting competitive times. &#160;Not too shabby considering I haven&#39;t raced that car ever. I also had a bash in the MX5 Roadster at Lime Rock, reliving 18 months ago. Good fun.  The highlight was the 6 or so races I did in the Street Stock Series. I really enjoyed the racing, when I wasn&#39;t getting taken out, and not having it impact my A class road license was great. &#160;I&#39;m going to be doing more ovals for sure.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/12/do-yourself-a-favour-go-and-race-other-series-often</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/12/do-yourself-a-favour-go-and-race-other-series-often</guid>
                    <pubDate>Mon, 03 December 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>Accessing F1Speed via mobile devices</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/12/accessing-f1speed-via-mobile-devices</comments>
                    <description>This is a big one even if in the end it didn’t take long to implement.&#160; When I originally wrote F1Speed back in march 2012 one of the first requests from my fellow league members was to make a web version so it can be accessed using a mobile phone or tablet.&#160; It wasn’t big on my radar personally because I have four monitors.&#160; Even though it wasn’t big on my radar I wanted to know how to do it and hard it would be.  I started research this last night at about 6pm by typing “self hosted web server” into Google and that led me to the Nancy project.&#160; I’d heard about Nancy on Dot Net Rocks a while back but never done anything with it.  F1Speed is a Windows App using UDP sockets to receive data from Codemasters F1 series.&#160; (F1 2010/2011/2012 etc).&#160; These changes include the use of the following libraries   Nancy  knockoutjs  jquery  bootstrap  modernizr   Looks like a normal web stack right?&#160; It is.&#160; Nancy replaces ASP.NET, but otherwise we’re all good to go in my normal, comfortable web dev environment.&#160; It took me a little while to get going with Nancy, even though it is very simple. I lost a lot of time trying to put all my resources files, including my views under a subfolder called “Web”.&#160; I wanted my normal F1Speed dll’s and exe and then all the web content the Web folder, just for organisation sake.&#160; Nancy supports custom root paths by implementing the IRootPathProvider interface .&#160; I went down that route and got my views to render but no matter what I tried after searching the web and reading through the help over and over I could not access my static content to (js and css files).&#160; In the end I removed the Web folder and put it all back in route, and let the conventions of Nancy control everything and it all worked.  Getting started with Nancy is as simple as opening nuget and adding the Nancy package and the Nancy self hosting package.&#160; From there you need to configure the Nancy host within the static void Main method, the entry point of your windows app.  class Program {   private static NancyHost nancyHost;    [STAThread]   static void Main(string[] args)   {     Application.ApplicationExit += Application_ApplicationExit;     var webPort = ConfigurationManager.AppSettings[&quot;webport&quot;];     nancyHost = new NancyHost(new Uri(&quot;:&quot; + (string.IsNullOrEmpty(webPort) ? &quot;43920&quot; : webPort)));     nancyHost.Start();     Application.EnableVisualStyles();     Application.SetCompatibleTextRenderingDefault(false);     Application.Run(new Form1(new TelemetryLapManagerFactory().GetManager()));   }   static void Application_ApplicationExit(object sender, EventArgs e)   {     nancyHost.Stop();   }     }  &#160;  Here we’re just registering the address of the site, which in my case is a single page, the F1Speed dashboard.&#160; I stop Nancy once the application closes.  &#160;  Nancy’s routes are configured using Modules.&#160; A Module is similar to a Controller in MVC, at least it is the way I’ve gone.&#160; The F1Speed web page is pretty simple (as I’ve been saying). I’ve got the single page ‘Dash’ and an API, which publishes JSON data representing the content to display.&#160; Here is my DashModule implementation that derives from NancyModule (as all Modules must).  public class WebSpeedModule : Nancy.NancyModule {   public WebSpeedModule()   {          dynamic model = new     {        Title = &quot;F1 Speed Web Viewer&quot;     };     Get[&quot;/&quot;] = x =&amp;gt; View[&quot;dash.sshtml&quot;, model];       } }  Nancy has Get, Put, Delete, Post, (and another I forget), but they’re HTTP verbs… Basically you grab the Method / Verb you want and in it’s indexer you define the route. I only have 1 page.&#160; The most simple version of this delegate is to return a string representing the entire HTML to be rendered.&#160; I’m using the out-of-the-box view engine ( SuperSimpleViewEngine ).&#160; Which uses razor like syntax, binding to a dynamic object as model.&#160; And that’s what I’ve created above.&#160; I use this model to display the title. That’s it.&#160; All my data comes down from the API via Ajax calls and using knockoutjs.&#160; We’ll get to that.  Here’s my View, my sshtml file.  &amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&quot;en&quot;&amp;gt;  &amp;lt;head&amp;gt;   &amp;lt;title&amp;gt;@Model.Title&amp;lt;/title&amp;gt;   &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/content/css/bootstrap.css&quot; /&amp;gt;   &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/content/css/bootstrap-responsive.css&quot; /&amp;gt;   &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/content/css/f1speed.css&quot; /&amp;gt;   &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/content/scripts/modernizr-2.6.2.js&quot;&amp;gt;&amp;lt;/script&amp;gt;  &amp;lt;/head&amp;gt;  &amp;lt;body&amp;gt;   &amp;lt;div class=&quot;container-fluid&quot;&amp;gt;     &amp;lt;div class=&quot;row-fluid&quot;&amp;gt;      &amp;lt;div class=&quot;span3&quot;&amp;gt;       &amp;lt;h4&amp;gt;Speed Delta&amp;lt;/h4&amp;gt;       &amp;lt;div class=&quot;numeric-delta&quot; data-bind=&quot;text: speedDelta&quot;&amp;gt;&amp;lt;/div&amp;gt;      &amp;lt;/div&amp;gt;      &amp;lt;div class=&quot;span3&quot;&amp;gt;       &amp;lt;h4&amp;gt;Time Delta&amp;lt;/h4&amp;gt;       &amp;lt;div class=&quot;numeric-delta&quot; data-bind=&quot;text: timeDelta&quot;&amp;gt;&amp;lt;/div&amp;gt;      &amp;lt;/div&amp;gt;     &amp;lt;/div&amp;gt;   &amp;lt;/div&amp;gt;   &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/content/scripts/jquery-1.8.3.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/content/scripts/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/content/scripts/knockout-2.2.0.debug.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/content/scripts/dash-vm.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;script&amp;gt;    $(document).ready(function() {      ko.applyBindings(DashViewModel)    });   &amp;lt;/script&amp;gt;  &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;  &#160;  You can see @Model.Title displaying the model. If you’re displaying content a user can change then you should use the @!Model.Title syntax instead which will html encode the output of Model.Title.&#160; If you inspect the HTML you noticed I’m making use of the data-bind attribute.&#160; This is knockoutjs at play (you can see the reference at the bottom of the page).&#160; I’ve setup my knockoutjs ViewModel to poll my Web Api.&#160; Here’s my view model.  &#160;  var DashViewModel = function ($, ko) {   var self = this;   self.speedDelta = ko.observable();   self.timeDelta = ko.observable();   function init() {     getPacket();   }     function getPacket() {     $.ajax({       url: &quot;/api/packet&quot;,       dataType: &quot;json&quot;,       error: function() {         self.speedDelta = &quot;ERR&quot;;         self.timeDelta = &quot;ERR&quot;;       },       success: function(data) {                 self.speedDelta(data.SpeedDelta);         self.timeDelta(data.TimeDelta);         setTimeout(function() { getPacket(); }, 100);       },       contentType: &quot;application/json&quot;     });   }   init();   return {     speedDelta: self.speedDelta,     timeDelta: self.timeDelta   }; }(jQuery, ko)  &#160;  You can see getPacket() does an ajax call to /api/packet which is&#160; a JSON’d version of this class  public class DashViewModel {   public string SpeedDelta { get; set; }   public float TimeDelta { get; set; }     }  &#160;  Almost as simple as it can get.&#160; But how do we get the data from F1Speed.&#160; As you saw above my reference to Nancy is outside the Windows Form of the normal F1Speed, which hosts the UDP endpoint that receives data.&#160; The central piece is TelemetryLapManager .&#160; This processes the data from Codemasters and keeps track of all data.&#160; Normally the F1Speed windows form polls this manager every 1/20th of a second (update rate is 1/60th of a second, from Codemasters).&#160; When the timer event expires it reads whatever the current data is from the telemetry manager. Ok?&#160; So my API module needs to access this too, but how?&#160; I need some kind of dependency resolution.&#160; Fortunately Nancy comes with it’s own IOC, TinyIoc.&#160; I haven’t read much on it but it appears to automatically register all types as services.&#160; I didn’t know how to configure the container to say TelemetryLapManager is a singleton, and I also didn’t know how to get access to the Tiny Ioc container to inject the manager into windows form.&#160; What I did was create a simple factory that held a static to the telemetry manager and injected that factory into the Form.&#160; You can see it way up top in my first snippet.&#160; Here’s the class. Very basic:  public class TelemetryLapManagerFactory {   private static TelemetryLapManager manager;   public TelemetryLapManager GetManager()   {     if (manager == null)       manager = new TelemetryLapManager();     return manager;   } }  &#160;  Now the API module.&#160; It’s similar to the DashModule, except it takes a constructor dependency on TelemetryLapManagerFactory  public class ApiModule : NancyModule {   private readonly TelemetryLapManager _telemetryLapManager;     public ApiModule(TelemetryLapManagerFactory managerFactory) : base(&quot;/api&quot;)   {     _telemetryLapManager = managerFactory.GetManager();     Get[&quot;/packet&quot;] = parameters =&amp;gt;       {         var model = new DashViewModel           {             SpeedDelta = _telemetryLapManager.GetSpeedDelta(),             TimeDelta = _telemetryLapManager.GetTimeDelta()           };                 return Response.AsJson(model);       };   } }  &#160;  All I need to do in the API call is to grab the current info I need from the telemetry lap manager and stuff it into my model and send it across the wire.&#160; You’ll note I’m passing /api into the parent class and calling Get[“/packet”].&#160; You will also note that from the client side I’m calling /api/packet. That’s how the routing engine in Nancy works.&#160;  Tha&#39;t’s pretty much it.&#160; The binding in knockoutjs will update the UI whenever it’s view model properties get update.&#160; I’m using setTimeout to call the api and refresh every 100ms.  Total time was about 5 hours and that included losing about 2 hours trying to change the root.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/12/accessing-f1speed-via-mobile-devices</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/12/accessing-f1speed-via-mobile-devices</guid>
                    <pubDate>Sat, 01 December 2012 22:19:00 </pubDate>
                </item>
                <item>
                    <title>Configuring notifications in teamcity</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/configuring-notifications-in-teamcity</comments>
                    <description>Configuring notifications in teamcity is a pretty simple thing but a few of the guys at work haven&#39;t done it, and I only just set mine up so why not share!  By default on our installation everyone is notified when every build fails and when a build succeeds the first time after failure.&#160; The configuration looks like this:    Generally not every developer works on every project, certainly not at the same time.&#160; We have one larger team of 5 and a few teams of 2, and my team of one (for the moment).&#160; I surely don’t want to be notified whenever a project I’ll never work on has a build problem (other than the personal satisfaction of knowing someone other than me is breaking the build).&#160; It’s darn easy to setup TeamCity to do just this.  TeamCity notification rules are ordered so to turn off notifications when your environment is set to notify on all builds just add a rule for ‘All Projects’ and don’t select any notifications    Place that rule at the top to first turn off for all projects, then add the projects you are interested in and select the appropriate notification items.&#160; You will end up with something like:    Repeat these steps on all notification types you want to configure. (E.g. Email, System Tray, etc).</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/configuring-notifications-in-teamcity</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/configuring-notifications-in-teamcity</guid>
                    <pubDate>Wed, 28 November 2012 17:40:00 </pubDate>
                </item>
                <item>
                    <title>Zolder didn&#39;t go to plan</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/zolder-didnt-go-to-plan</comments>
                    <description>Week four was Zolder. &#160;A new track. Sweet.  A few weeks ago I got a new work PC, with windows 8 installed. &#160;Win8 is a great operating system and with the upgrade from win7 only $56 I couldn&#39;t resist. &#160;I had already put off the upgrade so it didn&#39;t interrupt my Road America week, which should have (but wasn&#39;t) been a big one. &#160;I bit the bullet in week four.  The upgrade didn&#39;t go seamlessly and I had a few problems that took me until late Friday night to sort out. &#160;That meant the only running at Zolder I got prior to the weekend was Tuesday. &#160;Not so great. The weekend was also a busy one for me in real life and I got virtually no track time. &#160;What this meant was that I didn&#39;t get to race or qualify at Zolder. At all.  That&#39;s a shame because I enjoyed the track and got my PB down to 1:27.3 with an optimal in the mid 1:26&#39;s. With some setup work I&#39;m sure I could have been inside the 1:26&#39;s PB, I looked like it on a few laps before stuffing up. &#160;  Oh well. &#160;A short update this week. &#160;Nothing on the V8Supercar series because I&#39;ve decided to not race it. &#160;The Star Mazda, and open wheelers is where my heart lays :)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/zolder-didnt-go-to-plan</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/zolder-didnt-go-to-plan</guid>
                    <pubDate>Wed, 28 November 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>jquery.validate and chrome doesn&#39;t validate australian dates</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/jqueryvalidate-and-chrome-doesnt-validate-australian-dates</comments>
                    <description>I&#39;m currently working on an ASP.NET MVC 4 website using unobtrusive jquery validation.&#160; My browser of choice is Chrome.&#160; In my app I have a required Date of Birth.&#160; ASP.NET MVC 4 hooks up the client side validation for me and whenever I enter a date in dd/mm/yyyy format that is not a valid date in mm/dd/yyyy fomat I get an error (eg 19/11/2012 - there is no 19th month).    To validate a date the jQuery validation plugin simply tries to call new Date(myDateString), as seen here;  // http://docs.jquery.com/Plugins/Validation/Methods/date date: function(value, element) {   return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); },  &#160;  See line 1098 in jquery.validate.js.  I need something a little more robust, so I created a parser.  function getDateFormat(formatString) {   var separator = formatString.match(/[.\/\-\s].*?/),     parts = formatString.split(/\W+/);   if (!separator || !parts || parts.length === 0) {     throw new Error(&quot;Invalid date format.&quot;);   }   return { separator: separator, parts: parts }; } function MyParseDate(date, format) {   var parts = date.split(format.separator),     date = new Date(),     val;   date.setHours(0);   date.setMinutes(0);   date.setSeconds(0);   date.setMilliseconds(0);   if (parts.length === format.parts.length) {     console.log(parts.length);     for (var i = 0, cnt = format.parts.length; i &amp;lt; cnt; i++) {       val = parseInt(parts[i], 10) || 1;       switch (format.parts[i]) {         case &#39;dd&#39;:         case &#39;d&#39;:           date.setDate(val);           break;         case &#39;mm&#39;:         case &#39;m&#39;:           date.setMonth(val);           break;         case &#39;yy&#39;:           date.setFullYear(2000 + val);           break;         case &#39;yyyy&#39;:           date.setFullYear(val);           break;       }     }   }   return date; }  In the above I am building up the date based on the value and format passed into MyParseDate.&#160; I’m going to always pass in a format of “dd/mm/yyyy” in my current locale, but I want that to change based on where the user is.&#160; getDateFormat() takes a date format string.  Then I need to override the jquery validation code with my own.  jQuery.validator.addMethod(&#39;date&#39;,         function (value, element, params) {                     if (this.optional(element)) {             return true;           }           var result = false;           try {                         var format = getDateFormat(&#39;dd/mm/yyyy&#39;);             MyParseDate(value, format);                         result = true;           } catch(err) {             console.log(err);             result = false;           }           return result;         });  &#160;  You’ll need to put in every page that uses dates.&#160; I’m using ASP.NET MVC 4 so I decided to add it to my jquery validator bundle, so I can be sure that wherever I’m using jquery validators, I’m getting my date validator.&#160; I also called the above method from $(document).ready();  bundles.Add(new ScriptBundle(&quot;~/bundles/jqueryval&quot;).Include(       &quot;~/Scripts/jquery.unobtrusive*&quot;,       &quot;~/Scripts/jquery.validate*&quot;,       &quot;~/Scripts/custom.jquery.validate.js&quot;));  To be fair, the RFC does say only ISO dates should be converted, which Chrome does do. &#160;What is alarming is that IE 10 (as tested on my Windows 8 machine) handles new Date(&#39;19/11/2012&#39;) and new Date(&#39;11/19/2012&#39;). &#160;I wonder what month I&#39;ll get on IE :S</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/jqueryvalidate-and-chrome-doesnt-validate-australian-dates</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/jqueryvalidate-and-chrome-doesnt-validate-australian-dates</guid>
                    <pubDate>Mon, 19 November 2012 22:52:00 </pubDate>
                </item>
                <item>
                    <title>So you say you keep getting crashed out?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/so-you-say-you-keep-getting-crashed-out</comments>
                    <description>&quot;Arghh I&#39;ve had a rubbish week. I keep getting taken out on track&quot; &#160;  This is a comment complaint every iRacer hears every now and then. &#160;Someone, it may even be you, is having a bad week or two or more. &#160;It seems every race they&#39;re in they are involved in some &quot;idiot&quot;&#39;s crash. &#160;You know what I say? What&#39;s the common denominator? That driver! &#160;  To be honest I started my iRacing career like this. I was getting into more incidents that I wasn&#39;t, when racing MX-5&#39;s &#160;It seemed almost every race I was getting hit by, or hitting someone, and it wasn&#39;t often the same person. &#160;Personally, I think it&#39;s because I came to the sport with an expectation I&#39;d be as good against real people as I was against AI. &#160;Thing is, AI don&#39;t have emotions, but people do. &#160;And people don&#39;t want to lose, they don&#39;t want to get overtaken, and they&#39;ll shut the door. &#160;They also crash. &#160;They push hard, maybe too hard, and then they come off. &#160;I remember I would brake as late as possible as often as possible. &#160;I&#39;d run on the ragged edge and I&#39;d attempt a pass at the faintest wiff of an opportunity. &#160;I took this behaviour with me into the Skip Barber series too.  Eventually I wisened up and I started looking at my own actions, and at my own approach. &#160;I started leaving a gap for people to get by if they came up alongside. &#160;I&#39;d not follow too closely behind cars I could see were on the ragged edge. &#160;It started to pay off. &#160;Incrementally I reduced the number of incidents I got caught up in. &#160;I&#39;m not the fastest of drivers, so it&#39;s not like I&#39;m out front where no one can touch me. Most every race I&#39;m dicing it in the pack. &#160;  What happened? &#160;I developed racecraft. &#160;It may have taken 200 races, but I got there. &#160;  There&#39;s another aspect to this: respect. &#160;Yes, we&#39;re only racing on a computer. &#160;It&#39;s not real life where we can get hurt but respect is still important. &#160;I&#39;ve found, as I&#39;ve improved my racecraft and my driving manners, I&#39;ve come to respect a good &#160;number of drivers. &#160;I know they won&#39;t weave down the straight. &#160;I can trust if I pull alongside them into a turn they&#39;ll leave me racing room. &#160;Just like the real guys. &#160;And I reciprocate. &#160;I&#39;ve leave an extra wide berth, probably too wide, for guys I know are clean - even though it&#39;s unlikely they&#39;ll lose it or go wide and push me off. &#160;  What I&#39;ve also found is it makes racing more enjoyable being able to confidently go toe-to-toe with another driver without fear of being bunted off the circuit. &#160;Gentlemanly, I like to call it; like what I imagine the beginnings of motorsport was. &#160;  That&#39;s not to say I don&#39;t take a defensive line into a corner if a car is following closely. &#160;I don&#39;t want to make it too easy. But, if that car pulls up alongside and then we both start braking, I&#39;ll make sure I leave room for us both to get through the turn. &#160;What I&#39;ve repeatedly found is if you do this, amazingly, you live to fight another day - so to speak - and often a repass can be made. &#160;That can&#39;t happen if you&#39;re both off into the armco.  I came across this today, when a less experienced driver got involved in the same accident as me in two consecutive races. &#160;We were both hit by someone else (and we both didn&#39;t hit each other) before the first turn at Road America and it ended both our races, both times. &#160;When he was complaining (rightly so) that he&#39;d had a tough week and that this was the top split and we shouldn&#39;t be having these dumb accidents, my response was that he probably should consider what he&#39;s doing at the start line. &#160;Both times he hit the rear end of someone else and both times someone else hit my rear end (ie nothing I could do, someone hit me from behind). &#160;If you&#39;re getting involved in accidents on the first lap, when cars are close together, then it&#39;s prudent to leave a larger gap going into turn one. A safety buffer if you will.  In the 380 odd races I&#39;ve competed in I&#39;ve learned to be cautious at the start. &#160;Sometimes it means I&#39;ll get overtaken by a someone less cautious, sometimes it even means I get divebombed by an overly opportunistic noob (as was the case in the first of the above accidents), but significantly more often than not I see accidents in time and I can react and safely nagivate my way through and continue on. &#160;  I was flattered today when half the grid vouched for my &quot;gentlemanliness&quot; (is that a word?!) when I was offering some... er.. unsolicited racecraft advice ;) &#160;  If you&#39;ve made it this far into the article I hope the two points you&#39;re taking away are:   Respect your fellow racers and they&#39;ll respect you back - you&#39;ll have a more enjoyable iracing experience and you&#39;ll be more successful more quickly ( I wish I could have understood this 350 races ago ;)).  If you&#39;re getting into a lot of accidents, start by looking at yourself and how you&#39;re going racing. &#160;You are the most easiest thing for you to change!</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/so-you-say-you-keep-getting-crashed-out</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/so-you-say-you-keep-getting-crashed-out</guid>
                    <pubDate>Sun, 18 November 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>Cycling out of bellbowrie</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/cycling-out-of-bellbowrie</comments>
                    <description>I don&#39;t mind a bit of cycling. &#160;Being a bellbowrie resident, it&#39;s difficult though to cycle to work, or to anywhere really (unless you&#39;re doing it offraod). &#160;If a cycle lane was put on my imaginary bridge from Birkin to Sumner Roads that would solve a few problems, giving easy access to cycle across the bridge and to the cycleway that runs along the Western Freeway, all the way into the CBD.  The Brisbane City Council does have a plan though. &#160;And it&#39;s a dumb one. &#160;How dumb? &#160;Well, check it out here  Moggill Road is one of the busiest scariest roads on Brisbane in peak hour and the proposal is to put a cycle path along it? &#160;This may be ok if the cycle lane a meter+ wide, and painted green. &#160;Make it obvious. Otherwise we&#39;ve got problems with trucks and cars still.  Another proposal is Pedestrian Riverfront Access from the end of Lather Road through to the start of Gem Rd at Kenmore. &#160;Why not make it a shared path, such as exists on Coronation Drive. &#160;I think it would see more usage from cyclists anyway. The bonus is the bikes would be off Moggill Road and being that the path is along the river, it would hopefully be relatively flat, providing fast access. A half decent cyclists could ride that at 25-30 km/hr, and I&#39;d think the path would be about 5 kilometers, giving a trip time of around 15 minutes, cutting about 10-15 minutes of the current trip time and massively reducing risk.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/cycling-out-of-bellbowrie</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/cycling-out-of-bellbowrie</guid>
                    <pubDate>Fri, 16 November 2012 08:02:00 </pubDate>
                </item>
                <item>
                    <title>Hurry up and build a bridge between bellbowrie and riverhills</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/hurry-up-and-build-a-bridge-between-bellbowrie-and-riverhills</comments>
                    <description>For years now traffic density has been increasing year on year out of Bellbowrie/Moggill. &#160;For all intents and purposes there are two exits from the &quot;peninsula&quot;. &#160;One is to turn left, up Mount Crosby road and head west to Ipswich, while the other far more popular route is along Moggill Rd and into the city. Most cars go through the already crowded Kenmore, and through multiple school zones.  There has been a number of proposed solutions over the years. &#160;When I first moved into the area in 2006 my estate agent informed me of a corridor of land that was reserved a few hundred meters from the house I bought, that may one day be home to a dual carriageway. &#160;She said that had been in the pipeline for 25 years though and nothing had happened. &#160; Formally known as the Moggill Pocket Arterial Road / Moggill-Warrego Highway Connection .  The two projects in recent years are the Goodna Bypass which would cut across the Bellbowrie/Moggill peninsula twice, with two bridges. &#160;That was going to be an upgrade of the Ipswich motorway but it feel through after its federal funding never materialised in 2008. &#160;Thank God.  Then there was the proposed bridge to replace the Moggill Ferry. &#160;In my opinion that wasn&#39;t going to solve the problem for the vast majority, and it also introduces a shortcut for Ipswich residents to get to Kenmore/Indroopopilly. No thanks!  The most recent development, as championed by the member for Moggill, Dr Bruce Flegg, is the Kenmore Bypass . The corridor of land has been bought, the planning is in an advanced stage. I think the project has stalled on funding though, after the 2011 flood. &#160;You can read more about it here . &#160;The bypass would divert cars a few kilometers before Kenmore and put them at the Fig Tree Pocket / Western Freeway junction. &#160;Pretty much perfect. Nearly...  One glaringly obvious option is a Bridge. &#160;Not a bridge from Moggill to Riverview (no one wants that). &#160;A bridge from Bellbowrie to Sumner Road at Riverhills would be ideal. &#160;What&#39;s more, it&#39;s believed that funding was given to the Brisbane City Council for this bridge, as part of the developlment of Bellbowrie getting greenlighted in the 1970&#39;s ( see here ). &#160; There&#39;s also talk of a Green Bridge to Wacol but I don&#39;t think that&#39;s the best either.  If it were up to me, the bridge would go from near Birkin Rd (the road the runs beside the shopping center) and across to Sumner Road, which would direct traffic onto the Western Freeway, or to Mt Ommaney. Currently to the drive to Mt Ommaney and that side of the river takes 25+ minutes, and many people do it, as Mt Ommaney has the largest shopping Centre in the area (at least while Indooroopilly is being expanded).  A bridge in this location would open up a range of important possibilities. &#160;One of those is the use of the Centenary State High School as an alternative to Kenmore State High School. &#160;Bellbowrie/Moggill has a massive community of under 6 year olds at the moment, and it 6 years those children are going to nee a Highschool to attend. &#160;If the bridge existed, Centenary SHS would be closer than Kenmore SHS and give the ability for teens to cycle to school and back.    Bellbowrie/Moggill now has a primary school of 700 kids and growing. A McDonalds is about to open. &#160;A local Tavern will also be complete in the next 6 months, along with the Shopping Centre doulbing in size, with a Woolworths and Department Store (hopefully Big W) as well as other smaller shops. &#160;Bellbowrie is booming again and with all the land developments also going on it&#39;s only going to surge in population more. &#160;We need something better than Moggill Rd. &#160;And so do the downstream residents (Kenmore etc)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/hurry-up-and-build-a-bridge-between-bellbowrie-and-riverhills</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/hurry-up-and-build-a-bridge-between-bellbowrie-and-riverhills</guid>
                    <pubDate>Fri, 16 November 2012 07:29:00 </pubDate>
                </item>
                <item>
                    <title>why am I only just finding out theres a racetrack near my house?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/why-am-i-only-just-finding-out-theres-a-racetrack-near-my-house</comments>
                    <description>A few minutes ago I was googling &quot;supermax&quot; , while watching the latest episode of the Mentalist (where Jayne was headed to investigate what happened to his only link to Red John...) &#160;Anyway, in my random googling I came across the fact that the Brisbane Correction Centre has an 18 cell &quot;supermax&quot; unit. The prison itself being a maximum security prison. &#160;Next thing I know bing maps is open and I see this:    Yes, that does look a lot like a race track... &#160;There&#39;s even a car going around it!    Though in closer detail it does look less like a race track and more like a typical road, as you can see by the various bits and pieces coming off it. &#160; Looking at it in detail on nearmap makes me think it&#39;s a driver training centre, going by the various configurations that could be set up. &#160;The photo below is from 11 May 2010 on nearmap and you can see that it was under construction.    Whats the big deal. &#160;Well, I live ... let me show you whereabouts I live... &#160;It&#39;s somewhere in the red zone above the track. &#160;I want a place where I can stupid quick and not get a speeding ticket. &#160;Now... to get access muhahaha</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/why-am-i-only-just-finding-out-theres-a-racetrack-near-my-house</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/why-am-i-only-just-finding-out-theres-a-racetrack-near-my-house</guid>
                    <pubDate>Fri, 16 November 2012 06:57:00 </pubDate>
                </item>
                <item>
                    <title>Underwhelming at Laguna Seca</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/underwhelming-at-laguna-seca</comments>
                    <description>By the time the weekend rolled around I&#39;d managed to lower my PB at Laguna Seca by a few 10&#39;s on last year, the same with my Optimal. &#160;I started with a PB of 1:19.8 and a opt of 1:19.1. &#160;I went racing on Friday night, without qualifying, with a 1:19.6 PB and a 1:18.9 optimal. &#160;I hadn&#39;t planned on racing but Andrew was on and was going to race, and I was going fast enough. &#160;  I hadn&#39;t yet qualified. &#160;That meant I started 9th (from 13 I think, with Andrew second last). &#160;I made it up to 5th by the 2nd lap but was stuck behind 4th. Right behind. He was ranked #11 so I was cautious about overtaking, and he was all over the road, especially through the corkscrew, where I was significantly faster. &#160;Overtaking thorugh the corkscrew is not the brightest idea. The problem then was that he was too fast elsewhere for me to mount a safe pass. &#160;So I sat back. &#160;  He slipped again through the corkscrew and I slowed right down to avoid. &#160;The guy behind me was more opportunist and took the chance to pass me, and got through. &#160;I was more just wanting to get to the end with no incidents. &#160;I didn&#39;t. &#160;  What I did do was spin once. &#160;It was a half spin, so not too bad. &#160;Worse though, I got pinged twice through the corkscrew for &quot;corner cutting&quot; , which cost me about 3 seconds each time. &#160;  Meanwhile the leading trio were bolting. Had I qualified I&#39;d have been third on the grid, and probably right behind the leaders most the way. &#160;Nathan Growden was third with a 1:20.5... The pole sitter, Tommy Nilsson was on a 1:18.6 so I posed no threat to him, with 2nd place on a 1:19.1  Andrew fared less well, coming undone about mid race and retiring. &#160;    &#160;  Race 2, On Saturday afternoon and I got a qualifying time in. &#160;Not my best time, but a 1:19.998 was at least under 1:20. &#160;It put me 2nd on the grid, by 0.009 seconds! &#160;Off the line we were closely matched and quickly pulled a gap back to 3rd place. &#160;I was in hot pursuit when on lap 4 he overcooked it through the corkscrew and run off to the right. &#160;Richard Felton2 lost time but didn&#39;t drop back to 3rd place. &#160;For me it was enough opportunity to take 1st and drive off into a 6 second lead.  To his credit Richard caught back up over the course of about 6 laps and we diced it out with a sub 1 second gap for a few laps, until on lap 14 my right rear clipped the curb on the 2nd last corner and I slid. &#160;Now it was Richards turn to get better drive out of the corner he made a pass before the last corner. &#160;I kept in hot pursuit spending the remaining laps never dropping back more than 0.5 from his tail. &#160;We crossed the line 0.4 seconds apart. &#160;  Great race!</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/underwhelming-at-laguna-seca</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/underwhelming-at-laguna-seca</guid>
                    <pubDate>Wed, 14 November 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>indicating required fields with twitter bootstrap and asp.net mvc 4</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/indicating-required-fields-with-twitter-bootstrap-and-aspnet-mvc-4</comments>
                    <description>I&#39;ve recently started a new project using ASP.NET MVC 4 and Twitter&#39;s Bootstrap. &#160;As per the designs I had to provide an indicator on the mandatory fields. &#160;Something that looks like:    Twitter&#39;s Bootstrap provides prepend and append css-classes in it&#39;s form, see here . &#160;This gives the ability to prepend and append most any text to various input fields, with the result something like the following:    In the above image there are both prepended and appended elements, set using the following html and css  &amp;lt;div class=&quot;input-prepend&quot;&amp;gt;  &amp;lt;span class=&quot;add-on&quot;&amp;gt;@&amp;lt;/span&amp;gt;  &amp;lt;input class=&quot;span2&quot; id=&quot;prependedInput&quot; type=&quot;text&quot; placeholder=&quot;Username&quot;&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;div class=&quot;input-append&quot;&amp;gt;  &amp;lt;input class=&quot;span2&quot; id=&quot;appendedInput&quot; type=&quot;text&quot;&amp;gt;  &amp;lt;span class=&quot;add-on&quot;&amp;gt;.00&amp;lt;/span&amp;gt; &amp;lt;/div&amp;gt;  While it would be simple but time consuming to manually mark every field, I&#39;m using Data Annotations in my ViewModel classes within ASP.NET MVC 4 and I&#39;d like to make use of that to automatically generate the required markup. &#160;All required fields are marked with the [Required] attribute.  One path could be to create a HTML Helper method that would render the appropriate html, but I don&#39;t need to go to such complexity. &#160;Instead I&#39;m going to create the required field indicates client side, with jQuery. &#160;ASP.NET MVC 4 adds the data-val-required attribute to every input field that is required and I&#39;m going to take advantage of that.  This solution could have been implemented in CSS only but I found some (older) browsers don&#39;t like the attribute and tend to ignore it. &#160;Also, I couldn&#39;t get the appended field to like up as well as I&#39;d have liked. &#160;My end result will look like this    My view (razor) looks like:  &amp;lt;div class=&quot;control-group&quot;&amp;gt;				 	@Html.LabelFor(m =&amp;gt; m.UserName, new { @class=&quot;control-label&quot;}) 	&amp;lt;div class=&quot;controls&quot;&amp;gt;					 		@Html.TextBoxFor(m =&amp;gt; m.UserName, inputSize)                   		@Html.ValidationMessageFor(m =&amp;gt; m.UserName) 	&amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt;  I need to use jquery to find the resultant element, an input element of type text with the data-val-required HTML5 attribute and add the markup bootstrap requires. &#160;For that I use the following simple jquery in the ready event, first wrapping the input field in a div and then adding the extra span for the required indicator  function addRequired(searchClass) {   $(searchClass).each(function () {     if ($this).parent().hasClass(&quot;input-append&quot;))       $(this).parent().addClass(&quot;input-prepend&quot;);     else       $(this).wrap(&quot;&amp;lt;div class=&#39;input-prepend&#39;&amp;gt;&quot;);   });    $(searchClass).before(&quot;&amp;lt;span class=&#39;add-on requiredmarker&#39;&amp;gt;&amp;lt;/span&amp;gt;&quot;); } $(document).ready(function() {   addRequired(&#39;input[type=text][data-val-required]&#39;);   addRequired(&#39;input[type=password][data-val-required]&#39;);   addRequired(&#39;select[data-val-required]&#39;); });  Finally, I have the following CSS for requiredmarker  .input-append .add-on.requiredmarker {         background-image: url(&#39;/Images/required.png&#39;);   background-repeat: no-repeat;   background-position: 50% 50%; }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/indicating-required-fields-with-twitter-bootstrap-and-aspnet-mvc-4</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/indicating-required-fields-with-twitter-bootstrap-and-aspnet-mvc-4</guid>
                    <pubDate>Tue, 13 November 2012 17:03:00 </pubDate>
                </item>
                <item>
                    <title>Ragged gets busy at Leguna Seca</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/ragged-gets-busy-at-leguna-seca</comments>
                    <description>Week 2 of the Star Mazda is Leguna Seca, home of the infamous &quot;cork-screw&quot;. &#160;Last season both Andy and I hit the track for a race and came together on the first corner. &#160;We ultimately finished 2nd and 3rd in that first race. I had a few more troubles, coming together with Steve Owen, getting reversed into off the grid by Paul Ezerski...  This season I&#39;m doing high 1:19&#39;s (again) with low 1:20&#39;s when I&#39;ve got a tankful and I&#39;m not pushing. &#160;Andrew Kiss is doing 1:23&#39;s and may benefit from a shared set :)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/ragged-gets-busy-at-leguna-seca</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/ragged-gets-busy-at-leguna-seca</guid>
                    <pubDate>Fri, 09 November 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>V8Supercars rock Road America in 2012</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/v8supercars-rock-road-america-in-2012</comments>
                    <description>Round one of season four hit Road America this week. &#160;For me, it was to be my first every V8supercar event, after finishing 2nd in division four of the other main C class series, the Star Mazda.  I&#39;d been practicing Road America since Tuesday night but I still wasn&#39;t ready. I didn&#39;t think I&#39;d be competitive, not based on the times I was running in practice. &#160;The top split guys were running 2:09&#39;s in quali. &#160;My best ever lap was 2:13.2 , wiht an optimal of 2:11.6, their race pace. &#160;  Race night on Monday the 5th of November and I logged in in time for the 8:45pm event, as did my teammate, Andrew Kiss. To my amazament there were 105 people registered for the race. I shouldn&#39;t be surprise though, given this slot is recognised as the big slot for the week. &#160;I made the 2nd top split, with a SOF of 2909. &#160;That, I think, is perhaps the 2nd strongest field I have ever participated in, and I had the (mis)fortune to be a rookie driver. &#160;The other time I encountered such a strong field I was an experienced Star Mazda campaigner.  I didn&#39;t qualifying, the only driver to not, so I started from the very rear. &#160;It wouldn&#39;t have mattered as the slowest qualifying time was 2:13.000 a time I have never bettered. &#160; I don&#39;t have a qualifying setup either. &#160;I had planned to start from pitlane, so starting from the rear is even better, because it meant I wouldn&#39;t have to wait for the entire field to go by.  Off the line I made a decent start and started catching the driver in front of me, as I usually do. &#160;I noticed a big cloud of dust further up the field and realised there must have been contact. There was. &#160;A few drivers were on the side of the road and some were in faced the wrong way. I navigated my way through the mess and somehow came out in 12th. &#160;  I passed a driver that had spun a bit later round that lap. &#160;A driver came up to pass me into turn 8, gave me a little tap (0x) and I let him through. &#160;I definitely didn&#39;t want to get into a scrap. &#160;  The #5 ranked driver sat close on my bumper though, having also spun but recovered. &#160;We battled for a lap and a half, and it was great, before he dropped his left front onto the grass at turn 11 (the kink) and spun. &#160;From then on it was smooth sailing. &#160;Of sorts.  One thing I did noticed before the race was that 75L is the maximum amount of fuel the car will carry. iSpeed tells me thats only good for 14.1 laps. &#160;The race is 18 laps. &#160;I have a problem. &#160;I figured we must all need to stop. I&#39;m pretty good with fuel, surely most others would be in the same position.  In the race I remembered Martin Brundle saying it&#39;s amazing how much fuel you can save by coasting up the braking marker instead of braking hard on it, as you would in a normal fast lap. &#160;Then you get on the power slower. &#160;And short-shift. &#160;I was getting off the throttle 30m earlier and braking a little later and getting on the throttle more gently but it didn&#39;t seem to making much of a difference. &#160;I was going to be 1.2 laps short. &#160;Sitting in 10th place on lap 13 I heard that the leader was pitting and assumed most everyone would pit, so I dived in too. &#160;Most people didn&#39;t pit and I exitted the pits in 13th, where I stayed for the rest of the race. &#160;I also had no one to draft off, being by myself for most the time. &#160;I think the leaders were able to draft to save fuel.  I set a fastest lap of 2:13.4 with a race average of 2:19. &#160;My fastest lap was about middle of the pack but my average race time was the lowest of all cars. &#160;Probably due to me no knowing what I was doing with fuel saving. &#160;Oh yeah, when I was in 10th I was catching 9th and 8th, until I had to save fuel...  Now I need to learn about fuel saving...  My teammate Andrew Kiss faired less well. &#160;He made it to third top split (1900 SOF) and battled through to 16th, with 10 incidents. &#160;Tough luck buddy.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/v8supercars-rock-road-america-in-2012</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/v8supercars-rock-road-america-in-2012</guid>
                    <pubDate>Tue, 06 November 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>The Ford V8supercar is not easy</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/11/the-ford-v8supercar-is-not-easy</comments>
                    <description>This season I&#39;m focusing my efforts on the V8&#39;s. &#160;I want to be competitive with the top SOF come next year and hopefully Bathurst. &#160;I want to get mentioned on the  V8supercar website &#160;and I want to be moderately competitive with the guys like Muggleton, Downs, Hamstead, and SVG. &#160;  It&#39;s a big ask and this week I found out just how much of an ask it is. &#160;On day one I was doing 2:20&#39;s. On day two I Was doing 2:14&#39;s. &#160;I&#39;m now doing 2:13&#39;s but not easily. &#160;My optimal is 2:11.2. &#160;Not too bad and my pace is good enough for about top 25% of any practice session. &#160;What are the top guys doing? High 2:09&#39;s. so I&#39;ve got to find nearly 4 seconds.  Realistically though, it wasn&#39;t any different when I started racing Star Mazda&#39;s nor the Skippies. &#160;It took my a whole season in the star mazda to close the gap down to less than 2s. &#160;Now it&#39;s about 1.5 average.  At least I&#39;m faster than my teammate Andrew :) &#160;  The biggest stress point right now is consistency. &#160;My optimal is 2 seconds faster than my PB. That&#39;s a massive gap. &#160;The top guys are around 0.2s off their optimals. &#160;My biggest shortfall is in the braking zone. &#160;I&#39;m getting self-owned most laps, trying to slow the car down. &#160;I can&#39;t yet hit apexes consistently. &#160;I&#39;m thinking of sitting this round out so I don&#39;t make a complete mess of my SR.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/11/the-ford-v8supercar-is-not-easy</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/11/the-ford-v8supercar-is-not-easy</guid>
                    <pubDate>Sun, 04 November 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>Mammoth Networks sponsor for Season 4 2012</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/10/mammoth-networks-sponsor-for-season-4-2012</comments>
                    <description>My new for season 4 2012 paints are up and I reckon they look hawt. I&#39;ve got a new sponsor, in the form of Mammoth Networks. &#160;The guys at Mammoth Networks &#160;have graciously supplied a free VPS to host this site (ok, it&#39;s not entirely free, I have to exchange 40 hours a week of software development skills for money and a &quot;free&quot; VPS).  This season I&#39;ll be competing in the Star Mazda, V8Supercar, and Lotus 49 series&#39;. &#160;I don&#39;t have a Lotus 49 paint yet, as there is no template...  &#160;     &#160;  Note the license plate :D  &#160;     &#160;  &#160;     &#160;  &#160;     &#160;  &#160;  I&#39;m pretty happy with the end result. &#160;Easily the best paint I have done to date.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/10/mammoth-networks-sponsor-for-season-4-2012</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/10/mammoth-networks-sponsor-for-season-4-2012</guid>
                    <pubDate>Mon, 29 October 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>Star Mazda Season 3 2012 wrap up</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/10/star-mazda-season-3-2012-wrap-up</comments>
                    <description>My 2nd sesaon in the Star Mazda series has come to end and it&#39;s been a good season but not great. &#160;At the start of the season I had two goals:   Finish in the top 50 overall  Win my Division (4)   I led Division 4 from round 4 until the final round. &#160;Only on the last day of racing of the entire season did I lose my number spot and finish number 2. &#160;Still, number two isn&#39;t bad, but I did want to finish at the top.      &#160;  &#160;  I&#39;ve made some progress...     &#160;  In the end, a poor result at a track I love, Watkins Glen, did me in. Looking at the race results between Robert Orton and myself, he raced only the once - with success (a 2nd place and 120 points). I had 14 races with varying success including two 133 point finishes and a few other finishes about 110. &#160;I also had a 8 DNF&#39;s and that is what broke me.  &#160;  Congratulations to Robert Orton though. &#160;I&#39;ve raced him a few times throughout the season and it has always been very close. &#160;Unfortunately we rarely raced head-to-head. &#160;Scott McIntyre was the same. &#160;He raced strongly the few times I raced him (always at Motegi if I recall correctly).  I did score a win at 7 of the 12 rounds though, so that was a big plus. &#160;I also saw my iRating increase from in the 1500&#39;s to nearly 2800. It&#39;s now down just below 2700 after the disaster of Watkins Glen. &#160;I&#39;ve also now been promoted to Division 2. &#160;That will be very tough and I don&#39;t think I stand a chance. &#160;I was generally competing against Mike Quayle, David Buda, Nathan Gowden and a few others, and we had a great time racing cleanly and fairly. &#160;Really enjoyed it.  Oh yeah, I finished 54th overall, not quit enough. &#160;Again that darn Watkins Glen let me down :(  I think I&#39;ll do another season in the Star Mazda, although I want to race the Lotus 49, and prep for next years assault on the V8 Supercar.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/10/star-mazda-season-3-2012-wrap-up</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/10/star-mazda-season-3-2012-wrap-up</guid>
                    <pubDate>Fri, 26 October 2012 10:00:00 </pubDate>
                </item>
                <item>
                    <title>Smashing it at Road America season 3 2012</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/8/smashing-it-at-road-america-season-3-2012</comments>
                    <description>I started off the week pretty calmly and using my standard race preparation: &#160;Run the track with my default setup (32 Front and 31 Rear wings), incidentally the same wing settings I&#39;ve used all season so far. &#160;The tuesday session is meant to be an acclimitisation session to familiarise myself with the track.&#160;  This week, Wednesday was our show holiday and on Saturday I am off to help my mother move, so I&#39;d lose that day of racing. &#160;Wednesday became my Saturday. Fortunately I was quick off the bat and my first race on Tuesday night was looking ok. &#160;I didn&#39;t have time to qualify but I was up a few spots on the first few laps and was sitting fourth when the leader went off and rejoined the track at a slow pace, right on the racing line and right in front of me. &#160;I moved to avoid and went wide around Carousel. A bad thing. &#160;My rear left hit the dirt and I spun off the track and into the wall. &#160;Race over. &#160;What a wasted opportunity!  Race two was worse. &#160;Again I hadn&#39;t qualified and I started 12th. &#160;I didn&#39;t make it past the first corner this time and was speared up the backside as the noob behind me completely missed his brake marker. &#160;I expect he was caught off by the last placed driver not slowing at all and spearing straight into the car in front of him and to my right. &#160;I was out of the race on the spot.  Race 3 and Wednesday morning. This time I decided to qualify. &#160;A good thing too because I led from pole and won the race, setting the fastest lap on the way and finsihing with zero incidents! &#160;It was a boring race though, as I didn&#39;t see anyone for the entire race, except for maybe a lapped car or two. I went very unchallenged.&#160;  Race 4 and more of the same thing. I started from pole (2:04.888) and ran away with it, winning by 40 seconds and finishing with zero incidents and the race fastest lap.  Race 5 and I qualified 3rd in a decent strength of field race, behind Paul A Nelson and some other dude, Steven Forster, I think. &#160;I got Forster after about 2 laps and grimly held on by just my fingernails to the back of Nelson. &#160;Riku the pro was fast approaching though, at about 1 second a lap. &#160;I let him through on about lap 13, not wanting to push over my ability in an effort to keep him behind me. &#160;At the end I finished 3rd 6seconds behind him and Paul Nelson. They finished 0.2s apart, with Paul just taking the win after running out of fuel on the last lap, after the last corner.  I rocked up to a Time Trial on Thursday night, with a slightly adjusted ride height and immediately did a 2:04.3 . &#160;I couldn&#39;t believe it! &#160;Next Time Trial and I did a 2:04.2. &#160;I jumped in a practice session just before bed last night and did a 2:04.1. &#160;I shared my setup with a guy that was doing 2:03.8&#39;s and he managed a 2:03.9 on it, exclaiming he couldn&#39;t believe a high downforce set went so fast on a low downforce track. &#160;He was running 18 front and rear to get his 2:03.8. &#160;  My set flies in the corners though. &#160;For my style, I&#39;ve got no understeer when I get on the gas when I want to. &#160;I&#39;m driving with utter confidence and it&#39;s gripping to the track fantastically. &#160;When it does let go I can control it with ease. &#160;A 2:04.1 puts me in the top 25 times in the world! &#160;Much better than my races to date.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/8/smashing-it-at-road-america-season-3-2012</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/8/smashing-it-at-road-america-season-3-2012</guid>
                    <pubDate>Fri, 17 August 2012 12:00:00 </pubDate>
                </item>
                <item>
                    <title>Star Mazda Round opener at Brands Hatch</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/8/star-mazda-round-opener-at-brands-hatch</comments>
                    <description>This week kicked off the new season 3 of 2012 for iRacing. &#160;I&#39;m again racing the Star Mazda series and after finishing 77th in the world on my first attempt I&#39;m aiming to improve my position. &#160;Top 50 would be fanstastic. &#160;I also understand the way points work a little better. &#160;It wasn&#39;t until mid last season I understood that the top 50% of results count each round. &#160;EG If I do one race, then it counts 100%. &#160;If I do two races then only my best result counts. &#160;If I do three races then the average result of my best two races count (so if I got 100 and 80 points, I&#39;d get 90 points). &#160;If I do four races then the average of the best two is still the result. &#160;Basically it&#39;s possible to improve on really bad results. &#160;Last season I didn&#39;t do this and if I had a bad result I&#39;d just count that. BAD.  At the start of last season I was had an iRating over 2000 and was in Division 3. &#160;I was always in the top SOF race. &#160;This is good because the higher the SOF the more championship points you get. &#160;Of course, it&#39;s harder to get championship points. &#160; With that in mind, I kinda let my iRating freefall in week 13, getting down to 1450. &#160;That put me in Division 4 for the season. &#160;Unfortunately there&#39;s typically two splits per race and I&#39;m in the slower, where the less consistent and racecrafty drivers race :(. &#160;What it does mean though is that I&#39;m more likely to start up the front and should have a better chance of winning, as I haven&#39;t got any slower.  Round 1 and Brands Hatch was no different. &#160;My best time is a 1:21.7. &#160;I qualified with a 1:22.0, which was good enough for 3 poles from four races &#160;The lower SOF races were all poles. &#160;My fourth races as a 2150 SOF, which is probably the second strongest field I&#39;ve been in. &#160;I qualified third for that and took 2nd place in a great round the outside move at turn 2, getting better traction off the turn and holding the inside line to turn 3. &#160;From there I held second place until the flag.  The other three races: Well, I was taken out in race 1 after a fast newcomer couldn&#39;t bide his time and pushed me off the track. &#160;The other two races I won comfortably, by 35 seconds and 59 seconds respectively. &#160;They were ok but boring as hell. &#160;I didn&#39;t have to do much of anything but focus on not getting bored and running off track.  I&#39;m also pretty happy with my consistency. &#160;My race laps were within 0.5 of each other for 90% of the laps and all bar the first race, where I was hit multiple times, I only recorded 1 incident, for cutting the kurb to fine.  By the end of the Round I had recorded a 100 points (I wanted to record at least 100 points each round) &#160;and increased my iRating to 1750 and my SR to the high two&#39;s.  All in all a very good round.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/8/star-mazda-round-opener-at-brands-hatch</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/8/star-mazda-round-opener-at-brands-hatch</guid>
                    <pubDate>Mon, 06 August 2012 22:37:00 </pubDate>
                </item>
                <item>
                    <title>Selecting WPF DataTemplates at runtime</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/6/selecting-wpf-datatemplates-at-runtime</comments>
                    <description>During this week I was working on improving the UX on an internal business application we&#39;ve been actively developing over the last 6 months. &#160;The existing UI utilised an ItemsControl with a DataTemplate containing a Grid. The result was an unattractive UI that was uneccessarily large. &#160;A big problem was the most common state of all controls within the DataTemplate was read only, which was accomplished by setting the IsEnabled property to false. &#160;The customer was complaining that the disabled controls were hard to read.  Unfortunately I don&#39;t have a &quot;before&quot; version to show here but it was indeed awful. &#160;My first attempt was to use a GridView for the read-only items and keep the existing ItemControl for entering new items in the grid, positioned above list of &quot;Terminated&quot; items. &#160;Unfortunately I ran into problems with the method we use to keep track of validation errors (there&#39;s a known &#39;bug&#39; in WPF which we keep bumping into with this). &#160;With now only 2 weeks before go-live I didn&#39;t have time to code a work-around for the problem, which would involve changes that would impact the entire application and require full regression test.  I decided to merge the two areas together into one GridView and use two different DataTemplates selected at run-time based on if the item could be modified or not.  In WPF there are three way to modified DataTemplates   Data trigger  Value converter  Template selector   &#160;  I chose to use a template selector, which involves subclassing DataTemplateSelector and overriding the SelectTemplate method to return the desired data template.  Because I&#39;m using a GridView I needed to create two data templates and a template selector for each GridViewColumn of potentially edittable data. &#160;Once created, each GridViewColumn&#39;s CellTemplateSelector property is set to the x:Key value of the appropriate template selector. &#160;When the GridView is populating each row it will use the specified template selector to determine the data template for each column.  I created a re-usable template selector called KeyedTemplateSelector which maps a data template to a particular value of a property.  [System.Windows.Markup.ContentProperty(&quot;Template&quot;)] public class TemplateMaping { 	public string Key { get; set; } 	public DataTemplate Template { get; set; } } [System.Windows.Markup.ContentProperty(&quot;DataTemplates&quot;)] public class KeyedTemplateSelector : DataTemplateSelector { 	public ObservableCollection&amp;lt;TemplateMaping&amp;gt; DataTemplates { get; private set; } 	public string KeyPropertyPath { get; set; } 	private Dictionary&amp;lt;string, DataTemplate&amp;gt; templateLookup; 	public KeyedTemplateSelector() 	{ 		DataTemplates = new ObservableCollection&amp;lt;TemplateMaping&amp;gt;(); 		DataTemplates.CollectionChanged += DataTemplates_CollectionChanged; 		templateLookup = new Dictionary&amp;lt;string, DataTemplate&amp;gt;(); 	} 	void DataTemplates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 	{ 		IList convertersToProcess = null; 		if (e.Action == NotifyCollectionChangedAction.Add || 			e.Action == NotifyCollectionChangedAction.Replace) 		{ 			convertersToProcess = e.NewItems; 		} 		else if (e.Action == NotifyCollectionChangedAction.Remove) 		{ 			foreach (TemplateMaping maping in e.OldItems) 				this.templateLookup.Remove(maping.Key); 		} 		else if (e.Action == NotifyCollectionChangedAction.Reset) 		{ 			this.templateLookup.Clear(); 			convertersToProcess = this.DataTemplates; 		} 		if (convertersToProcess != null &amp;amp;&amp;amp; convertersToProcess.Count &amp;gt; 0) 		{ 			foreach (TemplateMaping mapping in convertersToProcess) 			{ 				templateLookup.Add(mapping.Key, mapping.Template); 			} 		} 	} 	public override DataTemplate SelectTemplate(object item, DependencyObject container) 	{ 		FrameworkElement element = container as FrameworkElement; 		if (element != null &amp;amp;&amp;amp; item != null) 		{ 			string key = EvaluatePropertyPath(item, KeyPropertyPath); 			if (key == null) 				return null; 			DataTemplate templateKey; 			if (templateLookup.TryGetValue(key, out templateKey)) 			{ 				return templateKey; 			}         		} 		return null; 	} 	private static string EvaluatePropertyPath(object obj, string path) 	{ 		if (string.IsNullOrEmpty(path)) 			return null; 		object curObj = obj; 		foreach (var item in path.Split(&#39;.&#39;)) 		  { 			var property = curObj.GetType().GetProperty(item); 			if (property != null) 			{ 				curObj = property.GetValue(curObj, null); 				if (curObj == null) 					return null; 			} 			else 				return null; 		  } 		return curObj.ToString(); 	} }   Once you&#39;ve built the project containing this class, add a namespace to the desired xaml file.  xmlns:cts=&quot;clr-namespace:MySystem.Infrastructure.TemplateSelectors;assembly=MySystem.Infrastructure&quot;   You&#39;ll need to add a KeyedTemplateSelector for every column. &#160;Because I wanted to select templates based on the value of the boolean CanModify property, I use the following xaml, adding a TemplateMapping for each possible value in the CanModify parameter.  &amp;lt;cts:KeyedTemplateSelector x:Key=&quot;customerDetailTemplateSelector&quot; KeyPropertyPath=&quot;CanModify&quot;&amp;gt; 	&amp;lt;cts:TemplateMaping Key=&quot;True&quot; Template=&quot;{StaticResource activeCustomerColumn}&quot; /&amp;gt; 	&amp;lt;cts:TemplateMaping Key=&quot;False&quot; Template=&quot;{StaticResource inactiveCustomerColumn}&quot; /&amp;gt; &amp;lt;/cts:KeyedTemplateSelector&amp;gt;  The final step, once you&#39;ve defined a KeyedTemplateSelector for each column is to hook it up to each GridViewColumn CellTemplateSelector property in xaml.  &amp;lt;ListView.View&amp;gt; 	&amp;lt;GridView AllowsColumnReorder=&quot;False&quot;&amp;gt; 		&amp;lt;cctl:FixedWidthColumn Header=&quot;Customer&quot; CellTemplateSelector=&quot;{StaticResource customerDetailTemplateSelector}&quot; FixedWidth=&quot;230&quot; /&amp;gt; 		&amp;lt;cctl:FixedWidthColumn Header=&quot;Start Date&quot; CellTemplateSelector=&quot;{StaticResource startDateTemplateSelector}&quot; FixedWidth=&quot;150&quot; /&amp;gt; 		&amp;lt;cctl:FixedWidthColumn Header=&quot;End Date&quot; CellTemplateSelector=&quot;{StaticResource endDateTemplateSelector}&quot; FixedWidth=&quot;150&quot; /&amp;gt; 		&amp;lt;cctl:FixedWidthColumn Header=&quot;Frequency&quot; CellTemplateSelector=&quot;{StaticResource frequencyTemplateSelector}&quot; FixedWidth=&quot;150&quot; /&amp;gt; 		&amp;lt;cctl:FixedWidthColumn Header=&quot;Amount&quot; CellTemplateSelector=&quot;{StaticResource amountTemplateSelector}&quot; FixedWidth=&quot;120&quot; /&amp;gt;                   		&amp;lt;GridViewColumn Header=&quot;Status&quot; ccxtn:GridViewSort.PropertyName=&quot;Status&quot;&amp;gt; 			&amp;lt;GridViewColumn.CellTemplate&amp;gt; 				&amp;lt;DataTemplate&amp;gt; 					&amp;lt;cctl:ReferenceItemControl ItemsSource=&quot;{Binding Path=DataContext.ArrangementToPayStatuses,ElementName=LayoutRoot}&quot; 													SelectedValuePath=&quot;StatusNumber&quot; SelectedValue=&quot;{Binding Status}&quot; 													DisplayMemberPath=&quot;Status&quot; HorizontalAlignment=&quot;Stretch&quot; /&amp;gt; 				&amp;lt;/DataTemplate&amp;gt; 			&amp;lt;/GridViewColumn.CellTemplate&amp;gt; 		&amp;lt;/GridViewColumn&amp;gt; 		&amp;lt;GridViewColumn Width=&quot;300&quot;&amp;gt; 			&amp;lt;GridViewColumn.CellTemplate&amp;gt; 				&amp;lt;DataTemplate&amp;gt; 					&amp;lt;StackPanel Orientation=&quot;Horizontal&quot;&amp;gt; 						&amp;lt;Button Name=&quot;DeleteButton&quot; Content=&quot;Delete&quot; Command=&quot;{StaticResource deleteArrangementToPay}&quot; CommandParameter=&quot;{Binding }&quot; 						Style=&quot;{StaticResource arrangementToPayButtonStyle}&quot; Visibility=&quot;{Binding ElementName=DeleteButton,Path=IsEnabled,Converter={StaticResource boolVisConverter}}&quot;/&amp;gt; 						&amp;lt;Button Name=&quot;CreateButton&quot; Content=&quot;Create&quot; Command=&quot;{StaticResource createArrangementToPay}&quot; CommandParameter=&quot;{Binding }&quot; 						Style=&quot;{StaticResource arrangementToPayButtonStyle}&quot; Visibility=&quot;{Binding ElementName=CreateButton,Path=IsEnabled,Converter={StaticResource boolVisConverter}}&quot;/&amp;gt; 						&amp;lt;Button Name=&quot;TerminateButton&quot; Content=&quot;Terminate&quot; Command=&quot;{StaticResource terminateArrangementToPay}&quot; CommandParameter=&quot;{Binding }&quot; 						Style=&quot;{StaticResource arrangementToPayButtonStyle}&quot; Visibility=&quot;{Binding ElementName=TerminateButton, Path=IsEnabled,Converter={StaticResource boolVisConverter}}&quot;/&amp;gt; 						&amp;lt;Button Name=&quot;ConfirmButton&quot; Content=&quot;Confirm&quot; Command=&quot;{StaticResource confirmArrangementToPay}&quot; CommandParameter=&quot;{Binding }&quot; 						Style=&quot;{StaticResource arrangementToPayButtonStyle}&quot; Visibility=&quot;{Binding ElementName=ConfirmButton,Path=IsEnabled,Converter={StaticResource boolVisConverter}}&quot;/&amp;gt; 					&amp;lt;/StackPanel&amp;gt; 				&amp;lt;/DataTemplate&amp;gt; 			&amp;lt;/GridViewColumn.CellTemplate&amp;gt; 		&amp;lt;/GridViewColumn&amp;gt; 	&amp;lt;/GridView&amp;gt; &amp;lt;/ListView.View&amp;gt;  I haven&#39;t included it, but a good modification would be to provide a default template to use when no TemplateMapping can be found for the current value of the KeyProperty.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/6/selecting-wpf-datatemplates-at-runtime</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/6/selecting-wpf-datatemplates-at-runtime</guid>
                    <pubDate>Fri, 15 June 2012 23:33:00 </pubDate>
                </item>
                <item>
                    <title>Making F1Speed</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/5/making-f1speed</comments>
                    <description>I&#39;ve already blogged about how to receive data from Codemasters F1 2010/2011 (It also works with the Dirt series, same format at least :)).&#160; I haven&#39;t explained what I wanted this data for.  Being an avid sim racer I was on the lookout for ways to improve my driving style and car setup.&#160; Lap times alone are not very indicative because there&#39;s no indication where exactly I&#39;m losing or gaining time when I make a change to my driving style (corner entry, acceleration, braking etc) or car setup.&#160; The only data I&#39;ve got to go on is my lap time, and of course the less scientific &quot;gut feel&quot; of the car.  Way back in the 90&#39;s I played Microproses (Geoff Crammands) Grand Prix series.&#160; In the game itself existed a telemetry component that logged your laps and allowed you to view a number of different points of data from your car on a graph, such as a wheelspeen, car speed, brake and throttle pedal application, ride height and more.&#160; I used this information to determine the effect of my setup changes on lap time, and to see where around the track I was getting the biggest benefit (or suffering). F1 2011 is a great game but being marketed also at the console gamer and more casual market, it lacks the telemetry component.&#160; I also used an external programme written by a sim racing fan, F1PerfData, back in my GP2 days that was pretty good.&#160; I wanted something like that (or that) for F1 2011.  I found F1PerfView and it&#39;s data format. and after further investigation found that F1 2011 does export telemetry.&#160; My goal was to now convert that data from the F1 2011 format into something F1PerfView could use.&#160; On top of that I wanted an application that would run alongside F1 2011 and give me realtime feedback as I lapped the various circuits, returning data such as:   Current Speed  Current Lap time  Fastest Lap Time  Average Lap Time  Difference in speed between my fastest lap and current lap at my current point on the track  Different in time between my fastest lap and current lap at my current point on the track   &#160;  I wanted this information so I could instantly see how a setup change or change to my driving style impacted on lap times, without having to exit F1 2011 and load up F1PerfView.&#160; In essence I wanted faster turn-around in setup changes.  This is where F1Speed comes in.&#160; It&#39;s designed to run concurrently (on another monitor preferrably) with F1 2011 and receive data from it, both logging laps for use in F1PerfView and giving real-time feedback.   There are some other high-level goals of this app, mainly  Log data to a server so people can download others&#39; fastest laps for their own comparison and improvement  Save fastest lap to disk for sharing with your friends or league teammate.  Import a saved lap for comparisson.  Save fastest laps for loading in at leater sessions.   &#160;  To achieve this I&#39;ve developed an application in C# 4.0 that does just this and I&#39;m blogging about the decision I made in coming blog posts.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/5/making-f1speed</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/5/making-f1speed</guid>
                    <pubDate>Sat, 26 May 2012 04:51:00 </pubDate>
                </item>
                <item>
                    <title>CROSS JOIN with filter equals INNER JOIN on Sql Server</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/5/cross-join-with-filter-equals-inner-join-on-sql-server</comments>
                    <description>When querying with Linq you can either use the natural relationships between your entities or you can make your joins explicit. &#160;Take the following example from the three entities, Debt, DebtAddress, and Address . &#160;Debt&#39;s have multiple DebtAddresses . &#160;Each DebtAddress has one Address . &#160;A query to extract all addresses for a debt could look like either of the following.  1) Using existing relationships  from d in Debt from da in d.DebtAddresses select da.Address  2) or you can you explicit joins  from d in Debt join da in DebtAddressses on d.DebtId equals da.DebtId join a in Address on da.AddressId equals a.AddressId select a  The resultant SQL of each query uses CROSS JOIN for the existing relationships and INNER JOIN for the explicit joins. &#160;CROSS JOIN is a cartesian product of the two tables either side of the join, which at first seems like it would produce more rows than is necessary (database theory 101), so surely it&#39;s a mad alternative. &#160;However, Sql Server doens&#39;t suck and when it builds its execution plan it factors in the WHERE clause to get the same result as if using INNER JOIN. &#160;Obvious yeh!?</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/5/cross-join-with-filter-equals-inner-join-on-sql-server</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/5/cross-join-with-filter-equals-inner-join-on-sql-server</guid>
                    <pubDate>Tue, 22 May 2012 18:25:00 </pubDate>
                </item>
                <item>
                    <title>Star Mazda Road America Season 2 2012</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/5/star-mazda-road-america-season-2-2012</comments>
                    <description>This weeks race was held at Road America, a long fast circuit that I found pretty difficult to get my car to behave in the braking zones.&#160; Ultimately, my fastest quali time was a 2:05.2, while my fastest practice time was a 2:04.9, both not assisted by tow, which is a big factor at Road America, thanks to the three long straights.   The turns that gave me the most trouble were turns 5, 8, and 14.&#160; Turn 9, the Carousel is tricky and you need to roll off the throttle almost completely before getting back on, otherwise you understeer too much.&#160; Turn 14 was hard to be consistently quick through and it was vital to be quick through it because it sets up the longest straight on the track.&#160; I&#39;d swing out wide and roll through, off throttle, before taking an early apex and gassing it up as hard and early as a could once the car was straight.  Turn 12, Canada Corner is interesting.&#160; Most people on the forums are braking at about the 3 marker.&#160; I was pulling up between 2 and 3, which is where one of the fastest guys stop - but he was lapping 2 seconds a lap faster than me nearly (low 2:03&#39;s).  My races were disasters.&#160; I started 5th and 3rd respectively and both times gained 2 places off the start line.&#160; I seem to start much better than the others around me.&#160; I noticed this last week. My first race, at silverstone wasn&#39;t much good because I took off like I did in the Skip Barber and floored the throttle.&#160; All I got was wheelspin.&#160; In the Star Mazda I slip the cluch at about 5,000 rpm and ride it a bit to minimise wheelspin.&#160; Seems to work well as I&#39;ve made up two places every time.&#160; Race 1 (Sunday 2:30pm) I crashed out at turn 3.&#160; I took a cautious line and Andrew Richardson was on my right, but there wasn&#39;t a spotter call.&#160; I turned in to take the apex and plain ran him off the road.&#160; Race 2 (Sunday 4:30pm) I moved from 3rd to first then back to 3rd by the first corner, not wanting to cause problems, and I was on the inside.&#160; Someone came up the inside of me at turn 5 and the same thing happened.&#160; I turned in and he was there too and I spun, then Andrew Richardson againhit me and took us both out.  I did have a race 3 (Sunday 6:30pm) but it didn&#39;t count toward championship points. Pity, because I won it from pole and set an average race time of 2:06.8, with 0 incidents. An effort that would have won me race 2 by over 15 seconds and would have me placed 3rd in race 1, giving me an average of 120 points.&#160; That would have put me 17th in the world. Instead I&#39;m in 53rd place.&#160; Oh well that&#39;s the pain of racing I guess.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/5/star-mazda-road-america-season-2-2012</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/5/star-mazda-road-america-season-2-2012</guid>
                    <pubDate>Mon, 21 May 2012 05:44:00 </pubDate>
                </item>
                <item>
                    <title>Contesting iRacings Star Mazda series</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/5/contesting-iracings-star-mazda-series</comments>
                    <description>Season 2 of 2012 in iRacing should be a good one for me.&#160; I started iRacing back in january 2011 and attempted racing in it while mixing driving in F1 2010/2011.&#160; My main focus was always the Atomic F1 league for F1 2010/2011, but I really wanted to move on from F1 2011, where the racers are not as realistic, to the hardcore world of iRacing at the higher levels.  Previously I&#39;ve been racing the MX5, the rookie car, and the Skip Barber, the first open wheeler and considered to be the best training ground at the grass roots level.&#160; I spent three seasons 3 in the Skip Barber, not completely the full race schedule in a season until season 1, where I did only one race a few weeks, and others I did many (20+).  I finished #130 in the world the Skip Barber Series in season 1, a season where I also competed and spent the majority of my time practicing for the Atomic F1 league races.    My approach to the Star Mazda championshp is a little more focussed.&#160; I&#39;ve got my A class license now (the next step up is a &quot;Pro&quot; license, which is VERY rare and I won&#39;t bother even attempting to consider).&#160; My iRating is currently at 2319, sufficiently high that I always get the top splits, with the best and safest drivers, making for the most enjoyable and realistic racing.&#160; The bonus is also that the Star Mazda is a C license car, meaning it&#39;s unlikely any noobs will show up for races even on the occasion I get a low SOF.&#160; Back to my approach.... This season I&#39;m following my own guide for F1 2011 racing - here - and spending week days learning the track and getting my race setup right, and the weekend (saturday or friday night) for qualifying, followed by the race typically on a Sunday.&#160; I&#39;ll hopefully only do 1 race a week and it&#39;ll be a good one :)  iRacing calculates your championship for the week as the average of your race points, so if you have a bad race you&#39;ll lose points and drop places.&#160; I&#39;ve been fortunate enough to have to a good race each of the two weeks of the season so far, so didn&#39;t need to try again.  I&#39;m currently sitting at number one in Division 3 in the world, which I&#39;m pretty stoked with, and number 16 overall!      I&#39;m not sure I can keep that up, but so far it&#39;s been great.&#160; Who knows, I might even gain places as others above me do more races and perform worse...</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/5/contesting-iracings-star-mazda-series</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/5/contesting-iracings-star-mazda-series</guid>
                    <pubDate>Sun, 13 May 2012 03:58:00 </pubDate>
                </item>
                <item>
                    <title>Fizzbuzz is useless as an interview question</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/5/fizzbuzz-is-useless-as-an-interview-question</comments>
                    <description>…Almost.&#160; There’s a few places on the web that mention the FizzBuzz problem as a good problem to weed out useless applications for senior developer positions, citing that over 30% of university graduates can’t do this in 10 to 15 minutes.&#160; Seriously?&#160; What shoddy uni do they go to.&#160; Admittedly I’m not a graduate, but all the concepts needed to implement this in no time flat are taught in first year uni!  What’s the FizzBuzz problem? The problem goes like this: print out all the integers from 1 to 100, replacing every integer divisible by 3 with the word “Fizz”.&#160; Also replace every integer divisible by 5 with the word “Buzz”.&#160; If an integer is divisible by 3 and 5 replace it with FizzBuzz.  The module operator is the key to the solution.&#160; If a number is wholly divisible by another, it’s modulo is 0.&#160; EG 20 module 5 is 0 because 5 goes into 20 exactly 4 times.  Without further ado, here’s my 30 second solution.  for (int i = 1; i &amp;lt;= 100; i++) {   if (i % 3 == 0 &amp;amp;&amp;amp; i % 5 == 0)     Console.WriteLine(&quot;FizzBuzz&quot;);   else if (i % 3 == 0)     Console.WriteLine(&quot;Fizz&quot;);   else if (i % 5 == 0)     Console.WriteLine(&quot;Buzz&quot;);   else     Console.WriteLine(i); }  See what I mean? Trivial isn’t it.&#160; FizzBuzz is ok to give people with less than 1 year experience, just to show they can cut at least some code, but it’s not at all representative of tasks a senior developer would perform.  What does a senior developer typically do?   Write test plans  Gather requirements  Lead and mentor teams  Determine architectures  Cut code   A senior developer should at least be quizzed on patterns.&#160; I recently applied to two respectable software development companies for a senior develop position.&#160; Both required me to complete a small task prior to them even looking at my resume.&#160; This included writing test plans, and providing production ready code for a trivial application, but one that was complex enough to show knowledge and flair for development.&#160; E.G. the use of patterns, a layered architecture, use of S.O.L.I.D. principles, TDD or DDD or whatever takes your fancy.&#160; I think this approach is much more suitable for getting quality employees.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/5/fizzbuzz-is-useless-as-an-interview-question</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/5/fizzbuzz-is-useless-as-an-interview-question</guid>
                    <pubDate>Sat, 12 May 2012 08:50:00 </pubDate>
                </item>
                <item>
                    <title>Coding fun converting monetary value to words</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/4/coding-fun-converting-monetary-value-to-words</comments>
                    <description>I’m going to gloss over the fact that I’m looking for another job already, after being at my current employer for 7 weeks thus far.&#160;&#160; Let’s just say they should have indicated the package included super…  I’ve dropped my resume in at a few jobs in the last two weeks.&#160;&#160; One of them is TechnologyOne.&#160; They were one of only two companies that asked for a coding submission with the application (the other being Readify – which i haven’t attempted yet).&#160; TechnologyOne’s task was to write an app that converted a monetary amount into works, such as you’d find on a cheque.&#160;&#160; For example, 123.45 would be converted to ONE HUNDRED AND TWENTY-THREE DOLLARS AND FORTY-FIVE CENTS.&#160; It’s basically a repeat of an assignment I had to complete in Turbo Pascal in Highschool and as such shouldn’t be too difficult.&#160; They preferred that this time it be done in either VB.NET or C# (which suits me fine – I don’t have a copy of Turbal Pascal lying about ).  I jotted down some ideas on paper first and noticed that the grouping of three digits is the same in word form, with the difference being that each&#160; Thousands Block is suffixed with thousand , million , billion , etc.&#160; With that in mind, the starting point for my solution was to develop a class that would handle the conversion of a number between 1 and 999 to words.&#160; To that end I wrote the following class:  using System; namespace ChequeDisplay.Logic {   /// &amp;lt;summary&amp;gt;   /// Represents a three digit grouping of &#39;thousands&#39;.   /// These &#39;blocks&#39; are repeated mlutiple times in a large number.    /// Each represnts at most 999.    /// e.g. 999,999,999 has three ThousandsBlocks - &#39;999&#39;,&#39;999&#39;,&#39;999&#39;   /// &amp;lt;/summary&amp;gt;   public class ThousandsBlock   {     private readonly string numberString;     private readonly int numberValue;     public ThousandsBlock(string numberString)     {       this.numberValue = int.Parse(numberString);       if (numberValue &amp;gt; 999) throw new ArgumentOutOfRangeException(&quot;numberString&quot;, &quot;number must be less than 1000&quot;);       this.numberString = numberValue.ToString(&quot;000&quot;);           }     public int Value { get { return numberValue; } }     /// &amp;lt;summary&amp;gt;     /// Converts block of a thousand to words.     /// e.g. a value of 436 will be converted to words of     /// &#39;FOUR HUNDRED AND THIRTY-SIX&#39;     /// &amp;lt;/summary&amp;gt;         public string ToWords()     {       string result = string.Empty;       int remainingValue = Value;       if (remainingValue &amp;gt; 99)       {         remainingValue = remainingValue - ((remainingValue / 100) * 100);         result += NumberToWordLookup.DigitWords[numberString.Substring(0, 1)] + &quot; HUNDRED&quot; + (remainingValue &amp;gt; 0 ? &quot; AND &quot; : string.Empty);       }       if (remainingValue &amp;gt; 19)       {         remainingValue = remainingValue - ((remainingValue / 10) * 10);         result += NumberToWordLookup.TensDigitWords[numberString.Substring(1, 1)] + (remainingValue &amp;gt; 0 ? &quot;-&quot; : string.Empty);       }       if (remainingValue &amp;gt; 9 &amp;amp;&amp;amp; remainingValue &amp;lt; 20)       {         result += NumberToWordLookup.TeensDigitWords[numberString.Substring(1, 2)];       }       else if (remainingValue &amp;gt; 0)       {         result += NumberToWordLookup.DigitWords[numberString.Substring(2, 1)];       }       return result;     }     public override string ToString()     {       return ToWords();     }   } }  The other part of a monetary number are the cents.&#160; They’re handled pretty much the same and I supposed I could have performed some superclass extract refactoring to pull out duplicated logic in both, but I didn’t….&#160; Lets see the CentsBlock class.  using System; namespace ChequeDisplay.Logic {   public class CentsBlock   {     private readonly int numberValue;     private readonly string numberString;     public CentsBlock(string centsString)     {       this.numberValue = int.Parse(centsString);       if (numberValue &amp;gt; 99) throw new ArgumentOutOfRangeException(&quot;centsString&quot;, &quot;Cents must be less than 100&quot;);       this.numberString = numberValue.ToString(&quot;00&quot;);     }     public int Value { get { return numberValue; } }     public string ToWords()     {       string result = string.Empty;       int tempValue = Value;       if (tempValue &amp;gt; 19)       {         tempValue = tempValue - ((tempValue / 10) * 10);         result += NumberToWordLookup.TensDigitWords[numberString.Substring(0, 1)] + (tempValue &amp;gt; 0 ? &quot;-&quot; : string.Empty);       }       if (tempValue &amp;gt; 9 &amp;amp;&amp;amp; tempValue &amp;lt; 20)       {         result += NumberToWordLookup.TeensDigitWords[numberString.Substring(0, 2)];       }       else if (tempValue &amp;gt; 0)       {         result += NumberToWordLookup.DigitWords[numberString.Substring(1, 1)];       }       return result;     }     public override string ToString()     {       return ToWords();     }   } }   Finally we need a class to wrap these two concepts. This class should have a notion of cents and a collection of thousand blocks, as many as needed to represent the number. It should also be responsible for adding the words “Dollars” and “Cents”, and the thousands identifiers, “thousands”, “millions”, etc. Lets meet CurrencyText   using System; using System.Collections.Generic; using System.Linq; namespace ChequeDisplay.Logic {   /// &amp;lt;summary&amp;gt;   /// Encapsulates a monetary amount represented by english words   /// &amp;lt;/summary&amp;gt;   public class CurrencyText   {     private readonly CentsBlock cents;     private readonly List&amp;lt;ThousandsBlock&amp;gt; dollars;     public CurrencyText(string numberString)     {       dollars = new List&amp;lt;ThousandsBlock&amp;gt;();       if (!numberString.Contains(&quot;.&quot;)) numberString += &quot;.00&quot;;       var dollarsAndCents = numberString.Split(new [] {&quot;.&quot;}, StringSplitOptions.RemoveEmptyEntries);       cents = new CentsBlock(dollarsAndCents[1]);       if (dollarsAndCents[0] != &quot;0&quot;)       {         var thousandDollarChunks = dollarsAndCents[0].Split(new[] { &quot;,&quot; }, StringSplitOptions.RemoveEmptyEntries);                         foreach (var thousand in thousandDollarChunks)         {           dollars.Add(new ThousandsBlock(thousand));         }       }     }     /// &amp;lt;summary&amp;gt;     /// Converts the numerical value represented by the instance of CurrencyText     /// to english words.     /// &amp;lt;/summary&amp;gt;         public string ToWords()     {       var numberString = string.Empty;       var total = 0;       for (var index = 0; index &amp;lt; dollars.Count; index++)       {         var block = dollars[index];         var thousandsSegmentNumber = dollars.Count - index;         if (block.Value &amp;gt; 0)         {           if (total &amp;gt; 0) numberString += &quot; &quot;;           numberString += block.ToString() + (thousandsSegmentNumber &amp;gt; 1 ? &quot; &quot; + NumberToWordLookup.ThousandsBlockWords[thousandsSegmentNumber] : &quot;&quot;);         }         total += block.Value * (thousandsSegmentNumber * (thousandsSegmentNumber &amp;gt; 1 ? 1000 : 1));       }       numberString = numberString.Trim() + (total &amp;gt; 1 ? &quot; DOLLARS&quot; : total == 1 ? &quot; DOLLAR&quot; : &quot;&quot;);       if (cents.Value &amp;gt; 0)       {         if (dollars.Sum(d =&amp;gt; d.Value) &amp;gt; 0)           numberString += &quot; AND &quot;;         numberString += cents.ToString() + &quot; CENT&quot; + (cents.Value &amp;gt; 1 ? &quot;S&quot; : &quot;&quot;);       }       return numberString;     }     public override string ToString()     {       return ToWords();     }   } }  Seems pretty simple, but how do we get input in?  using System; using System.Globalization; using ChequeDisplay.Logic; namespace ChequeDisplay {   class Program   {     static void Main(string[] args)     {       bool isCurrency = false;       decimal currency = 0;       while (!isCurrency)       {         Console.Write(&quot;Enter the value of the cheque: &quot;);         var inputCurrency = Console.ReadLine();                 isCurrency = decimal.TryParse(inputCurrency, NumberStyles.Currency, new CultureInfo(&quot;en-AU&quot;), out currency);         if (isCurrency)         {           if (currency &amp;lt;= 0)           {             Console.WriteLine(&quot;Please enter an amount greater than 0&quot;);             isCurrency = false;           } else if (((int)(currency * 100)) /100 != currency)           {             Console.WriteLine(&quot;Please enter a monetary amount with two decimal places&quot;);             isCurrency = false;           }         }         else         {           Console.WriteLine(&quot;Please enter a monetary amount with two decimal places&quot;);         }       }       var currencyString = new CurrencyText(currency.ToString(&quot;c&quot;).Substring(1)).ToWords();       Console.WriteLine(currencyString);       Console.WriteLine(&quot;Press any key to exit&quot;);       Console.ReadKey();     }   } }  I also used a helper class to do the digit to word conversion and improve readability in the important classes  Great! but now I need to verify that it all works. Take a deep breath...  using Microsoft.VisualStudio.TestTools.UnitTesting; using ChequeDisplay.Logic; namespace ChequeDisplay.Tests {   [TestClass]   public class CentsBlockFixture   {     [TestMethod]     public void ToString_can_handle_zero_amount()     {       var block = new CentsBlock(&quot;0&quot;);       Assert.AreEqual(string.Empty, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_cent()     {       var block = new CentsBlock(&quot;1&quot;);       Assert.AreEqual(&quot;ONE&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_tens()     {       var block = new CentsBlock(&quot;20&quot;);       Assert.AreEqual(&quot;TWENTY&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_teens()     {       var block = new CentsBlock(&quot;15&quot;);       Assert.AreEqual(&quot;FIFTEEN&quot;, block.ToString());     }         [TestMethod]     public void ToString_can_handle_ones()     {       var block = new CentsBlock(&quot;3&quot;);       Assert.AreEqual(&quot;THREE&quot;, block.ToString());     }         [TestMethod]     public void ToString_can_handle_tens_and_ones()     {       var block = new CentsBlock(&quot;46&quot;);       Assert.AreEqual(&quot;FORTY-SIX&quot;, block.ToString());     }      } } namespace ChequeDisplay.Tests {   [TestClass]   public class CurrencyTextFixture   {     [TestMethod]     public void ToString_can_handle_cents()     {       var block = new CurrencyText(&quot;0.43&quot;);       Assert.AreEqual(&quot;FORTY-THREE CENTS&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_cent()     {       var block = new CurrencyText(&quot;0.01&quot;);       Assert.AreEqual(&quot;ONE CENT&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_whole_dollars()     {       var block = new CurrencyText(&quot;3&quot;);       Assert.AreEqual(&quot;THREE DOLLARS&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_dollar()     {       var block = new CurrencyText(&quot;1.00&quot;);       Assert.AreEqual(&quot;ONE DOLLAR&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_thousand()     {       var block = new CurrencyText(&quot;2,000.00&quot;);       Assert.AreEqual(&quot;TWO THOUSAND DOLLARS&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_hundred_thousand()     {       var block = new CurrencyText(&quot;100,000.00&quot;);       Assert.AreEqual(&quot;ONE HUNDRED THOUSAND DOLLARS&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_million_dollars()     {       var block = new CurrencyText(&quot;1,000,000.00&quot;);       Assert.AreEqual(&quot;ONE MILLION DOLLARS&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_billion_dollars()     {       var block = new CurrencyText(&quot;1,000,000,000.00&quot;);       Assert.AreEqual(&quot;ONE BILLION DOLLARS&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_one_billion_dollars_and_cents()     {       var block = new CurrencyText(&quot;1,000,000,000.22&quot;);       Assert.AreEqual(&quot;ONE BILLION DOLLARS AND TWENTY-TWO CENTS&quot;, block.ToString());     }   } } namespace ChequeDisplay.Tests {   [TestClass]   public class ThousandsBlockFixture   {     [TestMethod]     public void ToString_can_handle_zero_amount()     {       var block = new ThousandsBlock(&quot;0&quot;);       Assert.AreEqual(string.Empty, block.ToString());     }     [TestMethod]     public void ToString_can_handle_hundreds()     {       var block = new ThousandsBlock(&quot;100&quot;);       Assert.AreEqual(&quot;ONE HUNDRED&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_tens()     {       var block = new ThousandsBlock(&quot;20&quot;);       Assert.AreEqual(&quot;TWENTY&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_teens()     {       var block = new ThousandsBlock(&quot;15&quot;);       Assert.AreEqual(&quot;FIFTEEN&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_hundreds_and_tens()     {       var block = new ThousandsBlock(&quot;430&quot;);       Assert.AreEqual(&quot;FOUR HUNDRED AND THIRTY&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_hundrends_and_teens()     {       var block = new ThousandsBlock(&quot;817&quot;);       Assert.AreEqual(&quot;EIGHT HUNDRED AND SEVENTEEN&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_ones()     {       var block = new ThousandsBlock(&quot;3&quot;);       Assert.AreEqual(&quot;THREE&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_hundreds_and_ones()     {       var block = new ThousandsBlock(&quot;702&quot;);       Assert.AreEqual(&quot;SEVEN HUNDRED AND TWO&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_tens_and_ones()     {       var block = new ThousandsBlock(&quot;46&quot;);       Assert.AreEqual(&quot;FORTY-SIX&quot;, block.ToString());     }     [TestMethod]     public void ToString_can_handle_hundreds_tens_and_ones()     {       var block = new ThousandsBlock(&quot;561&quot;);       Assert.AreEqual(&quot;FIVE HUNDRED AND SIXTY-ONE&quot;, block.ToString());     }   } }  How would you do it?  TechnologyOne clearly stated it’s bad form and instant grounds for rejecting an applicant if they’re found to copy someone else’s solution from the interwebs, so I figure if you’ve found your way here to copy my solution, you fail.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/4/coding-fun-converting-monetary-value-to-words</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/4/coding-fun-converting-monetary-value-to-words</guid>
                    <pubDate>Fri, 20 April 2012 07:59:00 </pubDate>
                </item>
                <item>
                    <title>My first month as a developer in the public sector</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/3/my-first-month-as-a-developer-in-the-public-sector</comments>
                    <description>I’ve just finished my first four weeks as a public servant in one of my states’ largest departments. I think four weeks is a long enough period to get an initial impression of the differences. &#160; I’ve spent the first 13 years of my software developer career in the private sector working for a range of small companies. I started with a company that produced their own financial software which they then sold and supported, essentially producing a shrink-wrapped product. &#160; Next I worked for a company who resold and supported emergency services dispatch software as well as modified and extended it for the particular purchasing agency. &#160; The latest former role was at a company that is essentially a consulting company, producing media rich websites for a large telecoms enterprise, where the company owned no IP. &#160; Currently that leaves me where I am now, working in the public service at a department with over 8000 staff and an software projects department that numbers in the hundreds, including developers, business analysts, project managers, and software trainers. &#160; My role is managing the developers in one of the division – about 30-40 developers across, at the moment, 6 projects.  &#160;  The first and biggest difference, one that has to accompany any enterprise of significant size, regardless of it being in the public or private sector, is an unavoidable level of red tape. &#160; The public service is no different and I was immediately exposed to this. &#160; It was four days before I was added to the AD (Active Directory) groups necessary to do my job, leaving me essentially twiddling my thumbs (although I did read almost the entire main intranet in that time :p) for four days. &#160;   &#160;  Once I started work though everyone was generally friendly and I immediately felt one of the team, something that I hadn’t really experienced when starting other jobs, although this may be now due to my experience and confidence at my new role.  &#160;  The project methodology used for software development projects is Prince2 run SCRUM style. &#160; All teams have a physical whiteboard in the Pod (a collection of desks and performs a function like a  war room ). On the white board is the product backlog for the current sprint, along with the items in dev, test, and completed stages. &#160; We do lack a continuous integration server though, which probably doesn’t make a lot of difference given no one seems to write unit tests. &#160;   &#160;  The lack of unit tests in an interesting one and even in light of overwhelming evidence suggesting unit testing is a must, may not be a bad thing. &#160; Earlier in the month I debated the use of unit testing with a senior dev. &#160; His stance was that unit testing only slows the project down and we need to be focussed on delivering to deadlines. &#160; It is true that writing unit tests can slow down how much production code is written, but it also helps speed up development, in that it provides a safety net to code against and developers can more easily write new code or refactor, feeling safer that the CI server and unit tests will catch anything they missed (although that doesn’t mean developers don’t need to understand how the code under development impacts the system at large).  &#160;  As public servants, perhaps we have an obligation to deliver products as quickly as possible, wasting as little tax payer money as possible. &#160; Therein lays the irony. &#160; By getting products “out the door” faster, we may be compromising maintainability. &#160; Live software is maintained by a complete separate department and the original developers are not called on to touch software once it passes the final gate and is in production. &#160; The project managers know this and they too aren’t focussed on the maintainability aspect, only the time to deliver.  &#160;  Government software, hopefully like most any other software, is going to live a long time and therefore be maintained for a long time. &#160; To deliver best value for money we should be focussed on reducing maintenance costs, same as any software development team. &#160; Another problem is political. &#160; I get the early impression that the need for software is often politically driven. &#160; The “customers” (managers of various service deliver centric teams) need the software in production ASAP, so they can report to their director-general or minister. &#160; They don’t care about the long term costs of maintainability, and I daresay the politicians don’t either.  &#160;  With that in mind, I do wonder what the “best” approach to software in the public service is. &#160; I’m of the opinion that good software development practices are good practices whether in the private or public sector and I’d like to, over the next 6 months, try to move the teams on to writing unit testable software that obeys best practices such as S.O.L.I.D. and the SRP and more. &#160; In fact, one of the key official responsibilities of my position is to evangelise, encourage, (and hopefully implement) industry best practices in the team.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/3/my-first-month-as-a-developer-in-the-public-sector</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/3/my-first-month-as-a-developer-in-the-public-sector</guid>
                    <pubDate>Fri, 30 March 2012 18:22:00 </pubDate>
                </item>
                <item>
                    <title>Connecting to Codemasters F1 telemetry feed</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/3/connecting-to-codemasters-f1-telemetry-feed</comments>
                    <description>Codemasters F1 201x (lastest incarnation is F1 2012) does not come with utilities that allow you analyse your telemetry.&#160; This is disappointing to a many long time F1 game players.&#160; Even Geoff Crammands’ Grand Prix series came with the ability to analyse many different data points, including: wheelspin at each of the four corners of the car, speed, brake input, throttle input, gear, time, ground clearance, and more.&#160;&#160; I think Codemasters response when asked why this was not included was because it was a more advanced feature and wouldn’t be popular with the majority of the gamers (the game is available on PC, Xbox, and PS3).  Fortunately for those who are interested in really “Living the life”, Codemasters has exposed a telemetry data point via UDP.&#160; By creating a UDP ‘server’ (though it’s only a UDP endpoint) the game can connect to, we can receive the telemetry published by F1 2012.&#160; I recently spent a bit of time analysing this stream to help with building my realtime telemetry app for the F1 2012 game ( see here &#160;)  Before receiving the data we need to reconfigure F1 2012 to point to our end point.&#160; This is done by modifying the “ hardware_settings_config.xml ” file found in your My Documents folder subpath ./My Games/FormulaOne2012/hardwaresettings/. Open the file and change the line     to be     You can set ip and port to the correct values for wherever your UDP end point will be listening for a connection.  The packet F1 2011 puts out through this contains the following fields in order.&#160; All fields are float datatype (in C#)     Field   Description     Lap   The number of laps since the session began.  Restarting a Session in the game will set this back to 0.     Time   Time in seconds since this telemetry session began  Restarting a Session in the game will set this back to 0.     LapTime  Time in seconds since the lap began. F1 2012 has a capture/send rate of 60 telemetry packets per second. &#160;This is 0 for an &#39;out lap&#39;    LapDistance  The distance in meters since the lap began. &#160;This can be negative for &#39;out&#39; laps. &#160;If you cross the start finish line and then reverse back over it the LapDistance will shoot back up buytbut the Lap will stay the same    Distance   The laps and percent of laps covered since the session began.  The value is negative when you first enter a practice session. &#160;After you cross the start finish line for the first time it becomes positive (0). &#160;A value of 1.5 would indicate you&#39;re halfway through your second lap. &#160;1.995 would indicate you&#39;re almost finished your second lap.     X  The X position of the car in the world    Y  The Y position of the car in the world    Z  The Z position of the car in the world    Speed  The car speed at this point in time.&#160; To get km/hr, multiply this value by 3.6 (I’m not sure why codemasters uses this scale).    XV  &#160;    YV  &#160;    ZV  &#160;    XR  &#160;    YR  &#160;    ZR  &#160;    XD  &#160;    YD  &#160;    ZD  &#160;    SuspensionPositionRearLeft  The position of the rear left suspension    SuspensionPositionRearRight  The position of the rear right suspension    SuspensionPositionFrontLeft  The position of the front left suspension    SuspensionPositionFrontRight  The position of the front right suspension    SuspensionVelocityRearLeft  The speed of travel of the rear left suspension    SuspensionVelocityRearRight  The speed of travel of the rear right suspension    SuspensionVelocityFrontLeft  The speed of travel of the front left suspension    SuspensionVelocityFrontRight  The speed of travel of the front right suspension    WheelSpeedRearLeft  The speed the rear left wheel is travelling.&#160; I assume this value with Speed subtracted gives wheelspin.&#160; Although it’s a good 6 km/hr faster than the front, if that’s the case!    WheelSpeedRearRight  The speed the rear right wheel is travelling.    WheelSpeedFrontLeft  The speed the front left wheel is travelling    WheelSpeedFrontRight  The speed the front right wheel is travelling    Throttle  The percent of throttle applied.&#160; 1 = full throttle and 0 = no throttle    Steer  The amount of left/right (-/+) input into steering. 0 = straight ahead.    Brake  The percent of brake applied. 1 = full brake and 0 = no brake    Clutch  This value will always be 0    Gear  The current gear    GForceLatitudinal  The amount of gforces being generated due to turning    GForceLongitudinal  The amount of gforces being generated due to acceleration or braking    Lap  The current Lap.&#160; 0 = first lap across the start/finish line.    EngineRevs  The RPM of the engine. This value needs to be multiplied by 10 to get the real RPM, so it would seen.     Detecting new laps  From what I can tell, the following can be used to determine which lap the driver is on.     Action   Details    Sitting in the Pits  When the driver is sitting in the pits  LapTime  is 0,  Speed  is&#160;0.     Out Laps   Out laps, that is laps where the driver has exited the pits but not yet crossed the start/finish line on the race track have a LapTime value of 0 and a negative&#160; Distance value or a Distance value of less than 1. &#160; The value is the fraction of a lap you have left before you start your first timed lap. &#160;As you leave it&#39;ll be around -0.95 (95% to go) and as you&#39;re about to cross the start finish line it&#39;ll be -0.0001 (.01% to go) approx. &#160;If you&#39;ve crossed the start/finish line once, the value starts at 0 and gets larger as you progress through the lap, toward a value of 1 (which is the start of the next lap).  On your very first of the session the Lap value will be the same as the next lap so you cannot use only&#160;changes in the Lap value to detect new laps.     Return to Pits  When you return to the pits after being partway through a lap your Lap value will not change, so you need to use some logic to recognise a return to pits. &#160; LapTime = 0 is a good one.    Packet frequency  The first packet received from F1 2012 after the car has crossed the start/finish line will be within 1/60th of a second, which is the rate at which F1 2011 publishes data.    Reversing back across the start finish line  LapDistance will vary depending on how much you move around during your lap. &#160;If you cross the line it&#39;ll be set back to 0. &#160;If you then reverse back across the finish line it&#39;ll jump to a large number.     &#160;  The Source  To read the data from UDP point in C# you can use the following  // This method runs continously in the data collection thread. It // waits to receive UDP packets from the game, converts them and writes // them to the shared struct variable. private void FetchData() {   while (true)   {     // Get the data (this will block until we get a packet)     Byte[] receiveBytes = udpSocket.Receive(ref senderIP);     // Lock access to the shared struct     syncMutex.WaitOne();     // Convert the bytes received to the shared struct     latestData = PacketUtilities.ConvertToPacket(receiveBytes);     manager.AddPacket(latestData);         // Release the lock again     syncMutex.ReleaseMutex();   } } public static class PacketUtilities {   // Helper method to convert the bytes received from the UDP packet into the   // struct variable format.   public static TelemetryPacket ConvertToPacket(byte[] bytes)   {     // Marshal the byte array into the telemetryPacket structure     GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);     var stuff = (TelemetryPacket)Marshal.PtrToStructure(       handle.AddrOfPinnedObject(), typeof(TelemetryPacket));     handle.Free();     return stuff;   } }  My TelemetryPacket class is the following. For my particular application (F1Speed) I only need to store certain fields.  using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Xml.Serialization; namespace F1Speed.Core {   [Serializable]   public struct TelemetryPacket : ISerializable   {     public TelemetryPacket(SerializationInfo info, StreamingContext context)     {       Time = info.GetValue&amp;lt;float&amp;gt;(&quot;Time&quot;);       LapTime = info.GetValue&amp;lt;float&amp;gt;(&quot;LapTime&quot;);       LapDistance = info.GetValue&amp;lt;float&amp;gt;(&quot;LapDistance&quot;);       Distance = info.GetValue&amp;lt;float&amp;gt;(&quot;Distance&quot;);       Speed = info.GetValue&amp;lt;float&amp;gt;(&quot;Speed&quot;);       Lap = info.GetValue&amp;lt;float&amp;gt;(&quot;Lap&quot;);       X = 0;       Y = 0;       Z = 0;       XV = 0;       YV = 0;       ZV = 0;       XR = 0;       YR = 0;       ZR = 0;       XD = 0;       YD = 0;       ZD = 0;       SuspensionPositionRearLeft = 0;       SuspensionPositionRearRight = 0;       SuspensionPositionFrontLeft = 0;       SuspensionPositionFrontRight = 0;       SuspensionVelocitoyRearLeft = 0;       SuspensionVelocitoyRearRight = 0;       SuspensionVelocitoyFrontLeft = 0;       SuspensionVelocitoyFrontRight = 0;       WheelSpeedBackLeft = 0;       WheelSpeedBackRight = 0;       WheelSpeedFrontLeft = 0;       WheelSpeedFrontRight = 0;       Throttle = 0;       Steer = 0;       Brake = 0;       Clutch = 0;       Gear = 0;       GForceLatitudinal = 0;       GForceLongitudinal = 0;       EngineRevs = 0;     }     public float Time;     public float LapTime;     public float LapDistance;     public float Distance;     [XmlIgnore]     public float X;     [XmlIgnore]     public float Y;     [XmlIgnore]     public float Z;         public float Speed;     [XmlIgnore]     public float XV;     [XmlIgnore]     public float YV;     [XmlIgnore]     public float ZV;     [XmlIgnore]     public float XR;     [XmlIgnore]     public float YR;     [XmlIgnore]     public float ZR;     [XmlIgnore]     public float XD;     [XmlIgnore]     public float YD;     [XmlIgnore]     public float ZD;     [XmlIgnore]     public float SuspensionPositionRearLeft;     [XmlIgnore]     public float SuspensionPositionRearRight;     [XmlIgnore]     public float SuspensionPositionFrontLeft;     [XmlIgnore]     public float SuspensionPositionFrontRight;     [XmlIgnore]     public float SuspensionVelocitoyRearLeft;     [XmlIgnore]     public float SuspensionVelocitoyRearRight;     [XmlIgnore]     public float SuspensionVelocitoyFrontLeft;     [XmlIgnore]     public float SuspensionVelocitoyFrontRight;     [XmlIgnore]     public float WheelSpeedBackLeft;     [XmlIgnore]     public float WheelSpeedBackRight;     [XmlIgnore]         public float WheelSpeedFrontLeft;     [XmlIgnore]     public float WheelSpeedFrontRight;     [XmlIgnore]     public float Throttle;     [XmlIgnore]     public float Steer;     [XmlIgnore]     public float Brake;     [XmlIgnore]     public float Clutch;     [XmlIgnore]     public float Gear;     [XmlIgnore]     public float GForceLatitudinal;     [XmlIgnore]     public float GForceLongitudinal;         public float Lap;     [XmlIgnore]     public float EngineRevs;     [XmlIgnore]     public float SpeedInKmPerHour     {       get { return Speed*3.60f; }     }     public override string ToString()     {       return &quot;Lap: &quot; + Lap + &quot;, &quot; +          &quot;Time: &quot; + Time + &quot;, &quot; +          &quot;LapTime: &quot; + LapTime + &quot;, &quot; +          &quot;LapDistance: &quot; + LapDistance + &quot;, &quot; +          &quot;Distance: &quot; + Distance + &quot;, &quot; +                    &quot;Speed: &quot; + Speed;     }         public void GetObjectData(SerializationInfo info, StreamingContext context)     {       info.AddValue(&quot;Time&quot;, Time);       info.AddValue(&quot;LapTime&quot;, LapTime);       info.AddValue(&quot;LapDistance&quot;, LapDistance);       info.AddValue(&quot;Distance&quot;, Distance);       info.AddValue(&quot;Speed&quot;, Speed);       info.AddValue(&quot;Lap&quot;, Lap);         }       } }  You can see this in action in F1Speed available here  The current project source for F1Speed is available on github https://github.com/robgray/F1Speed If you want the latest source, always consult github.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/3/connecting-to-codemasters-f1-telemetry-feed</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/3/connecting-to-codemasters-f1-telemetry-feed</guid>
                    <pubDate>Sat, 24 March 2012 22:39:00 </pubDate>
                </item>
                <item>
                    <title>Principal Systems Developer Day 1</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/3/principal-systems-developer-day-1</comments>
                    <description>When it comes to gaining experience I&#39;ve always said it&#39;s not experience unless you&#39;re doing something new.&#160; Every time I take on a new position I want it to be for in something at least a little new.&#160; Last time I changed jobs it was to learn ASP.NET MVC websites and a work with a team more appreciative of agile practices.&#160; This time it was to experience working with a large enterprise, as well as on a technology front, Silverlight, WPF, and RIA Services.  My first day on the job was today and my manager certainly didn&#39;t try and sugar coat some of the challenges I&#39;d be up against, describing some of the difficulties he has in dealing with the 100+ strong infrastructure team.&#160; I definitely believe that it&#39;ll be a challenge, but I&#39;m looking forward to it.&#160; Any new experience is good experience.&#160; He also outlined his plan for me.&#160; Starting tomorrow I&#39;m moving up to work on Group #2, helping out with their main software, as well as working on a few of the smaller projects.&#160; In one or two months I&#39;m to take over managing the group and its teams currently working on 6 projects they have going, along with whatever else comes along.  My manager was also not shy in telling me about some of the challenges I may face,&#160; Our overall department is made of the merging of a number of departments&#39; IT teams into the super IT department: That&#39;s what Group #2 was, its own deparment of teams.&#160; Even each team and project with each former department was operating under it&#39;s own lifecycle and processes.&#160; Part of my role will be to contribute to the formalisation of a department wide ALM policy in an effort to standardise and hopefully improve the quality of all software development.  So far my single day has been pretty boring, waiting on various logons to be granted, sorting out access to various environments, and doing all the HR stuff that comes with a big company.&#160; I&#39;ve also had the time to start thinking about how I&#39;m going to approach this problem. And of course, to get excited :)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/3/principal-systems-developer-day-1</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/3/principal-systems-developer-day-1</guid>
                    <pubDate>Tue, 06 March 2012 04:58:00 </pubDate>
                </item>
                <item>
                    <title>Test multiple test cases with one test using TestCase and NUnit 2.5</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/2/test-multiple-test-cases-with-one-test-using-testcase-and-nunit-25</comments>
                    <description>TestCase  The [ TestCase ] attribute in NUnit 2.5 and above allows the execution of one test multiple times with different parameters.&#160; As a real life example, I have a PointsSystem class in my F1 stats website which is responsible for determinig the points to award a particular race entry result.&#160; I have a RaceEntry object which contains qualifying times, race times, and race finish positions.&#160; From this I want to determine the number of points finishing the race in a particular position will yield.  These test are a little contrived because I&#39;m testing data, not execution.&#160; Different points systems award different points for different position.&#160; Some award points down to 10th place, or 20th, while some only reward to 6th.&#160; The value of points awarded is so important to the stats site I decided that I&#39;d go ahead and ensure my calculator would return the desired results.  To do this I originally had a test case for every position that award points  [TestFixture] public class PointsSystem2010Fixture {   private IPointsSystem _pointsSystem;   [SetUp]   public void SetUp()   {           _pointsSystem = new PointsSystem2010();     CacheHelper.InvalidateAll();   }   [Test]   public void _01st_place_race_and_qualifying_gets_30_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 1);           // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(30, points);   }   [Test]   public void _02nd_place_gets_22_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 2);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(22, points);   }   [Test]   public void _03rd_place_gets_18_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 3);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(18, points);   }   [Test]   public void _04th_place_gets_14_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 4);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(14, points);   }   [Test]   public void _05th_place_gets_11_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 5);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(11, points);   }   [Test]   public void _06th_place_gets_8_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 6);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(8, points);   }   [Test]   public void _07th_place_gets_6_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 7);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(6, points);   }   [Test]   public void _08th_place_gets_4_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 8);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(4, points);   }   [Test]   public void _09th_place_gets_2_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 9);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(2, points);   }   [Test]   public void _10th_place_gets_1_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 10);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(1, points);   }   [Test]   public void Eleventh_place_gets_0_points()   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, 11);     // Act     var points = entry.Points;     // Assert     Assert.AreEqual(0, points);   } }  &#160;  As you can see that is a lot of redundant code.&#160; Using the [ TestCase ] attribute I can condense this down to just one test, with a TestCase for each value I want to test.&#160; To do this I create parameters on the test method and pass in the place and it’s expected points result.  [TestFixture] public class PointsSystem2010Fixture {   private IPointsSystem _pointsSystem;   [SetUp]   public void SetUp()   {           _pointsSystem = new PointsSystem2010();     CacheHelper.InvalidateAll();   }   [TestCase(1, 30)]   [TestCase(2, 22)]   [TestCase(3, 18)]   [TestCase(4, 14)]   [TestCase(5, 11)]   [TestCase(6, 8)]   [TestCase(7, 6)]   [TestCase(8, 4)]   [TestCase(9, 2)]   [TestCase(10, 1)]   [TestCase(11, 0)]       public void Points_are_correct_for_race_place(int place, decimal expectedPoints)   {     // Arrange     var entry = FakesFactory.RaceEntry(_pointsSystem, place);     // Act     var points = _pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(expectedPoints, points);   } }  Here are the executed tests in NUnits test runner.&#160; I can now remove my duplicated test code      I don&#39;t think testing the points for each place is really the same as testing data because each PointsSystem instance should and MUST always return a certain points value for a certain position, and if it doesn&#39;t, it&#39;s broken.  TestCaseSource  To complement the TestCase attribute, there&#39;s the TestCaseSource attribute which allows you to define a source for your inputs and removes the need to use a TestCaseAttribute for every test case.&#160; TestCaseSource takes a string representing the property/method name of the property or method returning an object array containing the test case data to pass into the test.  To continue my example, I have a number of PointsSystem&#39;s that need testing. To do this I added an extra parameter to my test method that accepts the type of the PointsSystem under test. I include this type in my test case source data.&#160; I end up with the following test case data definition:  static object[] Points2010Cases =   {     new object[] { typeof(PointsSystem2010), 1, 30M },     new object[] { typeof(PointsSystem2010), 2, 22M },     new object[] { typeof(PointsSystem2010), 3, 18M },     new object[] { typeof(PointsSystem2010), 4, 14M },     new object[] { typeof(PointsSystem2010), 5, 11M },     new object[] { typeof(PointsSystem2010), 6, 8M },     new object[] { typeof(PointsSystem2010), 7, 6M },     new object[] { typeof(PointsSystem2010), 8, 4M },     new object[] { typeof(PointsSystem2010), 9, 2M },     new object[] { typeof(PointsSystem2010), 10, 1M },     new object[] { typeof(PointsSystem2010), 11, 0M },   };   static object[] Points2011Cases =   {     new object[] { typeof(PointsSystem2011), 1, 25M },     new object[] { typeof(PointsSystem2011), 2, 18M },     new object[] { typeof(PointsSystem2011), 3, 15M },     new object[] { typeof(PointsSystem2011), 4, 12M },     new object[] { typeof(PointsSystem2011), 5, 10M },     new object[] { typeof(PointsSystem2011), 6, 8M },     new object[] { typeof(PointsSystem2011), 7, 6M },     new object[] { typeof(PointsSystem2011), 8, 4M },     new object[] { typeof(PointsSystem2011), 9, 2M },     new object[] { typeof(PointsSystem2011), 10, 1M },     new object[] { typeof(PointsSystem2011), 11, 0M },   };   static object[] Points1991Cases =   {     new object[] { typeof(PointsSystem1991), 1, 10M },     new object[] { typeof(PointsSystem1991), 2, 6M },     new object[] { typeof(PointsSystem1991), 3, 4M },     new object[] { typeof(PointsSystem1991), 4, 3M },     new object[] { typeof(PointsSystem1991), 5, 2M },     new object[] { typeof(PointsSystem1991), 6, 1M },     new object[] { typeof(PointsSystem1991), 7, 0M },     new object[] { typeof(PointsSystem1991), 8, 0M },     new object[] { typeof(PointsSystem1991), 9, 0M },     new object[] { typeof(PointsSystem1991), 10, 0M },     new object[] { typeof(PointsSystem1991), 11, 0M },   };   static object[] Points1990Cases =   {     new object[] { typeof(PointsSystem1990), 1, 10M },     new object[] { typeof(PointsSystem1990), 2, 6M },     new object[] { typeof(PointsSystem1990), 3, 4M },     new object[] { typeof(PointsSystem1990), 4, 3M },     new object[] { typeof(PointsSystem1990), 5, 2M },     new object[] { typeof(PointsSystem1990), 6, 1M },     new object[] { typeof(PointsSystem1990), 7, 0M },     new object[] { typeof(PointsSystem1990), 8, 0M },     new object[] { typeof(PointsSystem1990), 9, 0M },     new object[] { typeof(PointsSystem1990), 10, 0M },     new object[] { typeof(PointsSystem1990), 11, 0M },   };  Now I can define these sources on my modified test method:    [Test]   [TestCaseSource(&quot;Points2010Cases&quot;)]   [TestCaseSource(&quot;Points2011Cases&quot;)]   [TestCaseSource(&quot;Points1991Cases&quot;)]   [TestCaseSource(&quot;Points1990Cases&quot;)]   public void Points_are_correct_for_race_place(Type t, int place, decimal expectedPoints)   {     PointsSystem pointsSystem = (PointsSystem)Activator.CreateInstance(t);     // Arrange     var entry = FakesFactory.RaceEntry(pointsSystem, place);     // Act     var points = pointsSystem.CalculatePoints(entry);     // Assert     Assert.AreEqual(expectedPoints, points);   }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/2/test-multiple-test-cases-with-one-test-using-testcase-and-nunit-25</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/2/test-multiple-test-cases-with-one-test-using-testcase-and-nunit-25</guid>
                    <pubDate>Fri, 17 February 2012 20:59:00 </pubDate>
                </item>
                <item>
                    <title>Principal Systems Developer</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2012/2/principal-systems-developer</comments>
                    <description>Principal Systems Developer: That’s the position title of my new position, starting March 5 th 2012.  Back in December 2009, the reasons I left my employ at the time were fairly extensive, the main ones being:   Work on teams following agile processes  Learn more about building high volume websites, as I felt it was basically the last area I hadn’t worked in to date in my career.  Not have to juggle numerous tasks given by different directors, all having to be done ‘now’, as well as the normal responsibilities of Software Development Manager  Work with a team more experienced with OO principals and in general more nerdy and less 9-5. More like me.  Get more of the stuff I thought I loved: Hands on development, and less of management  Work less hours.   &#160;  I was immediately happy with my new employment and position. I had a good team of guys and following a good development process. Although I did for the first few weeks feel very much out of my comfort zone when it came to frameworks I had never used, such as ExtJs and even extensive Javascript. I had also not been exposed to the pain of cross browser support, having previously only developed web applications for Intranets on SOE’s that we controlled and that meant IE6.  What I did realise early on was that while I missed hands on development when I was managing, now I was developing with no other responsibilities and I missed the management, the architectural decision making, and working on overall department quality, engaging with customers more.  It wasn’t long before I was out of the team I thought I’d be working in, and working on or leading projects of my own, based on off-the-shelf CMS solutions including Umbraco, Kentico, and Sitecore. This was definitely something different, as prior to working here I’d heard the word CMS and knew it stood for Content Management System but that literally was as far as my knowledge went on the topic. I’d never seen a CMS back office before or knew how content documents were made or structured in a CMS, or of any of the capabilities a standard CMS should possess.  That made my first, and only, project lead challenging, as for a good while I really had no idea what I was doing let along felt I had the authority to make technical decisions for the team. The worst was that I lacked the confidence to voice the direction I thought we should go with particular implementations. In hindsight (it really is 20/20) my thoughts back then I believe were still correct, and now I’ve had a good 14 months with implementing systems in those three CMS’s I mentioned earlier, I’d not have changed my initial thoughts.  The Kentico CMS project was certainly rewarding insomuch that I was learning new things daily, even if it wasn’t what I thought I was getting in to. It was also challenging because the customer’s project manager and main stakeholder went on maternity leave the week implementation began, so our business expert was removed from our agile process and I was left to make decisions and assumptions instead. It became rather stressful and my days were back out to 10 hour days and my nights were spent worrying about work. A few of the reasons I’d left my previous job. In reality the position over the 2 years I worked it was pretty darn laid back and undemanding as a whole, so it balanced out. I knew that’s the nature of software development: Some times are flat out busy and others are quiet, too quiet.  The last 6 months of last year, while enjoyable as a work environment was not enjoyable on a professional level. I’ve spent my time doing basic HTML/CSS/Javascript work and little backend work. My only backend work, where I could get creative with patterns and software/class interactions was at home on my own projects.  Software Development is like any muscle: if you don’t work it regularly it atrophies. Thankfully, like a muscle, it only takes a short period of re-use to get back to the old performance level, provided the absence isn’t too great.  In December a workmate and friend resigned. Given that I was working in a team of one, my work life became rather solitary. It was rare to have more than a total of 5 minutes of conversation with everyone per day. I made the decision to actually look for more work in mid-December. The week before the Christmas break I submitted two applications, one for Software Development Manager and one for Principal Systems Developer.  For the Software Development Manager position I got through the first round of interviews , into the second round, which was to be a video conference with executives based in Canada, but after talking it over with my wife I withdrew my interest. The remuneration was only 20%-30% better than current while the workload was a 50%-60% more and I could expect to be overseas 3 months of the year. Still, it would have been an extremely challenging and reward role, and also extremely and probably too stressful.  The other job, of Principal Systems Developer, pays 3% less than now but the Super is 17% versus 9% and I get flex-time. I’m also leading teams again and working on business applications, rather than websites, which is where my expertise (and dare I say my heart) lay. On top of that it’s a permanent position within the government, so it is extremely safe. Now that my wife is pregnant with our third child, I need that stability. To top it off, my wife works the floor above me, so we can share transport to and from work: A good thing, particular as she grows in size.  I am sad to be leaving my current employer and I enjoyed the time I spent there. Overall I feel I’ve satisfied the reasons for starting in the first place, as well as made some new friends, and learnt a lot. I also think being around a bunch of other smart and passionate developers has changed me for the better. I do wish them all the best and great success.  What do I want from this new position?  I want to feel empowered again and I want to again feel passionate about software and about mentoring less senior developers in developing quality software. I want to engage with clients and I want to be responsible for great solutions to problems. This feels more like what I was doing and how I was feeling a few years ago. And I like it. I want to get more experience with large enterprise and with dealing with high levels of bureaucracy.  Update  Last night I had farewell drinks with the guys. I was surprised by the turnout, as I honestly didn&#39;t expect more than a few people. I&#39;m also humbled by the number of guys that stayed on drinking into the night and the kind words everyone had to say about me. Sometimes I had wondered just how I was performing in job. I assume, like most people, I like to do good work and I like to know both when I&#39;m not and when I am.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2012/2/principal-systems-developer</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2012/2/principal-systems-developer</guid>
                    <pubDate>Wed, 15 February 2012 07:03:00 </pubDate>
                </item>
                <item>
                    <title>Installing analytics for Sitecore</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/12/installing-analytics-for-sitecore</comments>
                    <description>Sitecore 6 has some awesome sexy cool analytics under the moniker of the Customer Engagement Platform (CEP), the Online Marketing Suite (OMS), and Engagement Analytics.&#160; My business development manager was keen to get our site hooked up with this goodness so I set about trying to install it from within Sitecore itself, thinking it may be a package.&#160; After a lot of head scratching I found analytics, while hyped as the best selling point of Sitecore, is not installed out of the box!&#160; Off to the SDN I went and grabbed the copy for my version of Sitecore from here .  Thinking about it, it’s not an insane idea to not install out of the box.&#160; A lot of customers may already be experienced with Google Analytics and prefer to use that, also Sitecore Analytics comes with a performance hit. That, and the Analytics data is stored in a separate database that can be used by multiple sites and Sitecore installations.  Grab the Sql Server installation for the DMS from the above link, unzip and attach to your Sql Server instance.  The next step is to enable analytics within your Sitecore site.&#160; This is done via config files.&#160; The documentation here talks about the web.config file, which lead me astray for a while before finding that there are separate analytics config files linked to the web config file. I probably should have paid more attention to the documentation, which clearly states this up at the top.  The Sitecore.Analytics.config and Sitecore.Analytics.ExcludeRobots.config are both included in the DMS download and should be copied into your Siteocre App_Config/Include folder for your site.  Once those are in place you need to open Sitecore.Analytics.config and find the Analytics.Enabled setting and set it to true (or at least verify it is set to true).     &amp;lt;!-- ANALYTICS ENABLED       Determines if analytics is enabled or not.       Default: true    --&amp;gt;    &amp;lt;setting name=&quot;Analytics.Enabled&quot; value=&quot;true&quot; /&amp;gt;  Once that’s done, touch your web.config and go back into Sitecore and to the Enagement Analytics module.&#160; Your site is now ready to start capturing site visits and analysing visitors activity on your site.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/12/installing-analytics-for-sitecore</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/12/installing-analytics-for-sitecore</guid>
                    <pubDate>Fri, 23 December 2011 22:50:00 </pubDate>
                </item>
                <item>
                    <title>I think F1 2011 has got to go</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/12/i-think-f1-2011-has-got-to-go</comments>
                    <description>I compete in a league for Codemasters F1 2011 and have done since F1 2010 came out.&#160; It&#39;s generally good fun and run with a bunch of internet mates. Sadly I think the time has come to pull the plug. For good this time.&#160; And I blame codemasters shoddy netcode.  It seems I get to around this time each season and spit the dummy, withdrawing for a few races.&#160; I did the same last season, missing the Hungarian GP and a few of my other favouries.&#160; This time, I was &quot;forced&quot; to miss it, so I could celebrate my wife&#39;s birthday with her (my preferred activity - not exactly forced).&#160; Now I&#39;ve missed a race I think I&#39;ll keep on missing them.&#160; Unfortuantely I also develop and maintain the website for the competition, so that means all the continuing competitors, of which there are an ever decreasing amount, will be missing out.  The problem with the competition is the platform. F1 2011 internet code leaves a lot to be desired.&#160; Patch 1.2 introduced a network performance meter and supposedly improved network code but it&#39;s still goddamn awful, especially when Windows Life For Games is used as the ingame speech mechanism.&#160; We found, since switching to F1 2011, that when the competitors on slower connections talk, everyones game suffers.&#160; I&#39;ve got a decent connection and when someone with a slow connection talks, my in game FPS drops to around 5. I&#39;m not the only one.  There&#39;s also a lot blinking cars.&#160; The cars for bad connections blink a lot and move unknown around the track. There&#39;s been a few times where I&#39;ve been punted into a wall by a car blinking into me, one second 10 metres away, the next it&#39;s right beside me, turning into me.&#160; That&#39;s very annoying when you practice for hours each week for a 90 minute quali/race event that happens once a week.&#160; Again, this happens to others too, not just me.  There seems to be a disconnect between the race events from different perspectives.&#160; Many of the competitors in our league record the races and on a number of occasions, the competitors on slower connections seem to have completely different races, racing in positions that no one else seems them in.&#160; They also effect other peoples races and when the recordings are used to adjudicate over ontrack issues, it&#39;s clear the slow-connected competitor is not to blame, yet on the effected persons replay, they clearly crash into them.  Because of these problems a number of competitors are dropping out and the league field is thinning.&#160; Back in early season 3 we had sometimes only 4 or 5 racers per race and it was dead boring.&#160; There was no field spread.&#160; If a driver made a small mistake the others would leave them behind and the rest of th race would be run in solitude.&#160; In Season 4 until now at least, with a 13+ car field, there&#39;s always a driver a few seconds down the road in either direction.&#160; We&#39;ve had 4 formal retirements so far, 2 top line drivers and 2 midfielders and I think it&#39;ll only get worse.&#160; There&#39;s already talk of running championships on different platforms.  My biggest contention point is iRacing.&#160; I pay a monthly subscription to iRacing and have invested a fair whack of money in tracks and cars, and frankly it&#39;s a much better racing experience.&#160; The physics of this simulator are much more accurate than the &quot;sim&quot; game that is F1 2011, and while there are a few blinking players every now and then, it&#39;s pretty darn rare.&#160; This last week I&#39;ve been back into iRacing and I&#39;m really enjoying it but I can&#39;t play both series because of time constraints and well, the physics of one don&#39;t translate smoothly to the other.&#160; It took me a good couple of days (a few hours of track time) to get back into iRacing.  It&#39;s looking likely that I&#39;ll be bidding goodbye to atomicf1 officially very soon.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/12/i-think-f1-2011-has-got-to-go</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/12/i-think-f1-2011-has-got-to-go</guid>
                    <pubDate>Wed, 21 December 2011 18:12:00 </pubDate>
                </item>
                <item>
                    <title>Visual Studio macro to attach to w3wp.exe</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/12/visual-studio-macro-to-attach-to-w3wpexe</comments>
                    <description>Last weekend I was working on a Media Browser plugin and I came across a post on the Media Browser dev forums showing a Visual Studio macro to ease the pain of debugging plugins, which normally require attaching to the Media Centre host.&#160; (That post is here ).  Today at work I had an &#39;ah ha&#39; moment and thought I should be doing the same for IIS/w3ws, as multiple times a day I&#39;m opening the debug processes, pressing &#39;w&#39;, going down to w3wp.exe and attaching to it do I can debug my ASP.NET applications.&#160; So here it is.  To do the same on your own machine:   in Visual Studio to go Tools -&amp;gt; Macros -&amp;gt; Macros IDE...  Now right-click on &#39;My Macros&#39; and select Add -&amp;gt; Add Module...  Rename the Module to whatever.  Add code from below.  Right click on &quot;MyMacros&quot; and select Build then close the Macro IDE  In the main VS IDE, go to Tools -&amp;gt; Options -&amp;gt; Keyboard and in &quot;Press shortcut keys:&quot; enter the shortcut keys combination you want to use to run this macro  Type &#39;MyMacros&#39; in to &#39;Show commands containing:&#39; and select AttachToW3WP and repeat for BuildAndAttachToW3WP   &#160;  I&#39;ve got two macros because I may not want to rebuild each time.  Public Module VSDebugger   Public Sub AttachToW3WP()     Dim attached As Boolean = False     Dim proc As EnvDTE.Process     For Each proc In DTE.Debugger.LocalProcesses       If (Right(proc.Name, 8) = &quot;w3wp.exe&quot;) Then         proc.Attach()         attached = True       End If     Next     If attached = False Then       MsgBox(&quot;Couldn&#39;t find w3wp.exe&quot;)     End If   End Sub   Public Sub BuildAndAttachToW3WP()     DTE.Solution.SolutionBuild.Build(True)     AttachToW3WP()   End Sub End Module   Update: You may experience trouble finding the Macro in Tools-&amp;gt;Options-&amp;gt;Keyboard. Make sure you open it from the main VS menu, not from within the Macro IDE. Also if you still can&#39;t find it, try adding the above code to an existing Module within Samples. A co-worker had this problem when adding the Macro and this fixed it.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/12/visual-studio-macro-to-attach-to-w3wpexe</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/12/visual-studio-macro-to-attach-to-w3wpexe</guid>
                    <pubDate>Wed, 14 December 2011 06:28:00 </pubDate>
                </item>
                <item>
                    <title>Localising Date Time display in ASP.NET and Javascript</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/12/localising-date-time-display-in-aspnet-and-javascript</comments>
                    <description>One of the tickets I worked on this week was to add a “date last updated” to some stock market data being displayed.&#160; I needed to display “As of [last update date] (30min delay)” underneath the market data table.    The data is retrieved from an xml feed that contains the last updated time in the format “HH:mm dd/MM/yyyy”.&#160; The time used is local to Sydney Australia, “AUS Eastern Standard Time”, 10 hours ahead of GMT and +11 in Summer.&#160; I wanted to ensure the last updated date was not in the future for people viewing from a different time zone; Brisbane for example, which is also +10 GMT but doesn’t have daylight savings.  The way I did this was to send the UTC time back to the client browser and use javascript to convert that to the local time zone of the client.&#160; I also wanted a fail safe and included the Sydney time as default.&#160; I created a server-side property on my ascx to convert the time I recieved in my stock data feed into GMT:  protected DateTime? LastUpdatedDateGmt {   get   {     if (!LastUpdatedDate.HasValue) return null;     // Time comes in as Sydney time.     TimeZoneInfo sydneyTimeZone = TimeZoneInfo.GetSystemTimeZones().First(p =&amp;gt; p.StandardName == &quot;AUS Eastern Standard Time&quot;);     return TimeZoneInfo.ConvertTimeToUtc(LastUpdatedDate.Value, sydneyTimeZone);               } }  The TimeZoneInfo class&#39;s GetSystemTimeZones provides an enumeration of all timezones and are Daylight Savings aware.&#160; I use the Sydney time zone of &quot;AUS Eastern Standard Time&quot; to convert from and into Utc (GMT 0) time.&#160; The html I sent back to the browser looks like the following:  &amp;lt;div class=&quot;refreshtime&quot;&amp;gt;         As of &amp;lt;span id=&#39;time&#39; class=&#39;time&#39; utctime=&#39;Thu Dec 01 2011 22:54:00&#39;&amp;gt;2011-12-02 9:54:00&amp;lt;/span&amp;gt; (30min delay) &amp;lt;/div&amp;gt;  &amp;lt;script type=”text/javascript”&amp;gt;MarketData.ConvertStockLastUpdatedToLocalTime();&amp;lt;/script&amp;gt;  &#160;  With the above, at a minimum a site visitor in my time zone will see last updated time 1 hour or so in the future.&#160; Not bad, but could be better.&#160; Next I use some javascript to find the span element containing the utctime attribute and extract the utc time of the last update.&#160; I can then convert this to the browsers local time zone.  var MarketData = {     ConvertStockLastUpdatedToLocalTime: function() {     var timeElements = getElementsByAttribute(document, &#39;*&#39;, &#39;utctime&#39;);     if (timeElements != &#39;undefined&#39; &amp;amp;&amp;amp; timeElements) {       var oAttribute;       var oCurrent;       for (var i = 0; i &amp;lt; timeElements.length; i++) {         oCurrent = timeElements[i];         oAttribute = oCurrent.getAttribute &amp;amp;&amp;amp; oCurrent.getAttribute(&#39;utctime&#39;);         var d = new Date();         var utcTime = new Date(oAttribute);         var localTime = new Date(utcTime + ‘ GMT’);         var localTimeString = localTime.getFullYear() + &#39;-&#39; + pad(localTime.getMonth() + 1, 2) + &#39;-&#39; + pad(localTime.getDate(), 2) + &#39; &#39; + pad(localTime.getHours(), 2) + &#39;:&#39; + pad(localTime.getMinutes(), 2) + &#39;:&#39; + pad(localTime.getSeconds(), 2);         timeElements[i].innerHTML = localTimeString;       }     }          } };  The above finds all the elements in the page with the utctime attribute and extracts that value for conversion.&#160; Once extracted I just need to assign it a timezone and let the Data function take care of the conversion to local time for me.&#160; I converted for the format “yyyy-MM-dd HH:mm:ss” format by using the custom Pad function.  function getElementsByAttribute(oElm, strTagName, strAttributeName, strAttributeValue) {   var arrElements = (strTagName == &quot;*&quot; &amp;amp;&amp;amp; oElm.all) ? oElm.all : oElm.getElementsByTagName(strTagName);   var arrReturnElements = new Array();   var oAttributeValue = (typeof strAttributeValue != &quot;undefined&quot;) ? new RegExp(&quot;(^|\\s)&quot; + strAttributeValue + &quot;(\\s|$)&quot;, &quot;i&quot;) : null;   var oCurrent;   var oAttribute;   for (var i = 0; i &amp;lt; arrElements.length; i++) {     oCurrent = arrElements[i];     oAttribute = oCurrent.getAttribute &amp;amp;&amp;amp; oCurrent.getAttribute(strAttributeName);     if (typeof oAttribute == &quot;string&quot; &amp;amp;&amp;amp; oAttribute.length &amp;gt; 0) {       if (typeof strAttributeValue == &quot;undefined&quot; || (oAttributeValue &amp;amp;&amp;amp; oAttributeValue.test(oAttribute))) {         arrReturnElements.push(oCurrent);       }     }   }   return arrReturnElements; } function pad(number, length) {   var str = &#39;&#39; + number;   while (str.length &amp;lt; length) {     str = &#39;0&#39; + str;   }   return str; }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/12/localising-date-time-display-in-aspnet-and-javascript</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/12/localising-date-time-display-in-aspnet-and-javascript</guid>
                    <pubDate>Fri, 02 December 2011 20:53:00 </pubDate>
                </item>
                <item>
                    <title>Encoding email links in Sitecore</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/11/encoding-email-links-in-sitecore</comments>
                    <description>Background  I&#39;ve been asked to encode/encrypt the email addresses on our work site, built on Sitecore&#39;s CMS, to reduce the likelihood of email harvesters scanning the site and picking up our emails, especially given we&#39;ve listed email address for a number of staff, as well as our main contact email addresses.  In email harvesting spiders, or bots, crawl sites and parse HTML, extracting email addresses and adding those addresses to lists for their own spamming or for sale to other organisations.&#160; There are a number of ways to protect against email harvesters, increasing in complexity.  The most common and simple is to &#39; Address Munge &#39;, replacing the @ and periods ( . ) with words and spaces, such as &quot;rob at robert gray net au&quot;.&#160; Such replacement is so trivial for a harvester to break that it&#39;s not worth the effort.  HTML Obfusication is another method that involves inserting some html elements that are hidden by CSS.&#160; This way any email address won&#39;t appear as valid when the source is viewed, but the web user will see what appears like a valid email address. This method has accessibility issues.  Some other methods including using images (accessbility problem), CAPTCHA (not user friendly).  I&#39;ve chosen Javascript Obfusication , where the contents of the email link and text are hidden by the source and converted by javascript after the page has loaded.&#160; Using this technique, email harvesters cannot find email address, and as long as the browser is javascript enabled, this technique will work .  &#160;  The Good Stuff  When I first tackled this problem, I wanted a solution that would catch all and was easy to implement. My first attempt, yesterday, was to create an Xsl Helper extension that would create the link and render the desired HTML.&#160; Sitecore uses Xsl Renderings to create components to display on the page, and my thinking was that developers and authors can use this extension method whenever they wished to encode an email address (which would be always).&#160; I got this method working, using the following code:  using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.XPath; using Sitecore.Data; using Sitecore.Xml; using Sitecore.Data.Fields; namespace MyVeryOwn.Xml.Xsl {     public class XslHelper : Sitecore.Xml.Xsl.XslHelper   {       /// &amp;lt;summary&amp;gt;     /// Encodes an email address to reduce the chances of Email Harvesting.     /// &amp;lt;/summary&amp;gt;     /// &amp;lt;param name=&quot;iterator&quot;&amp;gt;Xml node from within the sitecore xslt&amp;lt;/param&amp;gt;     /// &amp;lt;returns&amp;gt;an email link with character encoding&amp;lt;/returns&amp;gt;     /// &amp;lt;remarks&amp;gt;     /// Rather than return &amp;lt;a href=&quot;mailto:test@email.com&quot;&amp;gt;test@email.com&amp;lt;/a&amp;gt;,     /// the email addresses are character encoded and javascript is used client side to reverse the encoding and display the correct email when clicked on     /// If the Text of the link is an email address, the encrypted class is added ot the link. jQuery is used to call the decode function after the page has loaded     /// and display the correct text. The source still shows the encrypted html     /// eg: &amp;lt;a class=&quot;encrypted&quot; href=&quot;javascript:sendEmail(&#39;73616C6573406D616D6D6F74686D656469612E636F6D2E6175&#39;)&quot;&amp;gt;73616C6573406D616D6D6F74686D656469612E636F6D2E6175&amp;lt;/a&amp;gt;     /// which should be &amp;lt;a href=&quot;mailto:sales@mammothmedia.com.au&quot;&amp;gt;sales@mammothmedia.com.au&amp;lt;/a&amp;gt;     /// (the &#39;mailto:&#39; is added in the javascript function)     /// &amp;lt;/remarks&amp;gt;     public string GetEncryptedEmailLink(XPathNodeIterator iterator, string fieldname)     {       try       {         if (iterator == null) return &quot;&quot;;         iterator.MoveNext();         var currentItem = GetItem(iterator);         if (currentItem != null)         {           LinkField field = currentItem.Fields[fieldname];           var url = field.Url.Replace(&quot;mailto:&quot;, &quot;&quot;);           var convertedUrl = EncodeEmailAddress(url);           var linkText = field.Text;           if (IsLinkTextEmailAddress(linkText)) {             linkText = EncodeEmailAddress(linkText);             return string.Format(&quot;&amp;lt;a class=\&quot;encrypted\&quot; href=\&quot;javascript:sendEmail(&#39;{0}&#39;)\&quot;&amp;gt;{1}&amp;lt;/a&amp;gt;&quot;, convertedUrl, linkText);           }                     return string.Format(&quot;&amp;lt;a href=\&quot;javascript:sendEmail(&#39;{0}&#39;)\&quot;&amp;gt;{1}&amp;lt;/a&amp;gt;&quot;, convertedUrl, linkText);                     }         return &quot;&quot;;       }       catch (Exception ex) {         Sitecore.Diagnostics.Log.Error(&quot;Failed to generate encypted email address&quot;, ex, iterator);       }       return &quot;&quot;;     }     private static string EncodeEmailAddress(string email)     {       return BitConverter.ToString(ASCIIEncoding.ASCII.GetBytes(email)).Replace(&quot;-&quot;, &quot;&quot;);           }     private static bool IsLinkTextEmailAddress(string linkText)     {       // copied from /sitecore/system/Settings/Validation Rules/Field Rules/Common/Is Email       // to use the same pattern as the email rule within Sitecore.       const string emailValidPattern = @&quot;^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$&quot;;       var emailValidator = new Regex(emailValidPattern);       return emailValidator.IsMatch(linkText);     }   } }  My Contact List Rendering would call the above like so:  &amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt; &amp;lt;xsl:stylesheet version=&quot;1.0&quot;  xmlns:xsl=&quot;http://www.w3.org/1999/XSL/Transform&quot;  xmlns:sc=&quot;http://www.sitecore.net/sc&quot;  xmlns:dot=&quot;http://www.sitecore.net/dot&quot;  xmlns:mm=&quot;http://www.myveryown.com.au/mm&quot;  exclude-result-prefixes=&quot;dot sc mm&quot;&amp;gt; &amp;lt;xsl:template name=&quot;ContactThumbnail&quot;&amp;gt;  &amp;lt;xsl:param name=&quot;contact&quot; /&amp;gt;  &amp;lt;div class=&quot;ContactThumbnail&quot;&amp;gt;     &amp;lt;sc:image field=&quot;headshotimage&quot; select=&quot;$contact&quot; /&amp;gt;   &amp;lt;div class=&quot;Details&quot;&amp;gt;    &amp;lt;div class=&quot;Name&quot;&amp;gt;     &amp;lt;sc:text field=&quot;name&quot; select=&quot;$contact&quot; /&amp;gt;    &amp;lt;/div&amp;gt;    &amp;lt;div class=&quot;Position&quot;&amp;gt;         &amp;lt;sc:text field=&quot;position&quot; select=&quot;$contact&quot; /&amp;gt;    &amp;lt;/div&amp;gt;    &amp;lt;div class=&quot;Email&quot;&amp;gt;             &amp;lt;xsl:value-of select=&quot;mm:GetEncryptedEmailLink($contact, &#39;emailaddress&#39;)&quot; disable-output-escaping=&quot;yes&quot; /&amp;gt;    &amp;lt;/div&amp;gt;   &amp;lt;/div&amp;gt;  &amp;lt;/div&amp;gt; &amp;lt;/xsl:template&amp;gt; &amp;lt;/xsl:stylesheet&amp;gt;  The main stumbling block with this approach is that the rendering author would need to know to include this method. Also, it would not work when displaying an email address using HTML, such as in a layout or sub-layout.&#160; I needed a method that was more reliable.&#160; On another project (using Kentico CMS) I created a HttpModule that did a similar thing using response filters.&#160; Sitecore has a great extensibility story and it’s own pipelines so I went looking through the web.config file, where pipelines are configured, to see if I could find something more suitable.  After a few minutes of looking and I came to the renderField pipeline, which had a stage GetLinkFieldValue .&#160; Sounding like what I was after I broke out Reflector and took a look inside that class.&#160; Based on my findings I crafted a new class GetEncryptedLinkFieldValue that performed the same base functionality, with the additional logic when the LinkType was “mailto”, which is the link type for email addresses.  What I did find was it wasn’t as easy to just override the particular functionality I wanted, so I created some new classes. The only thing I needed from GetLinkFieldValue was I needed to add my own custom LinkRenderer, but the CreateRenderer method was protected and not virtual.&#160; To get around this I recreated the GetLinkFieldValue class as GetEncryptedLinkFieldValue.  public class GetEncrpytedLinkFieldValue {   protected LinkRenderer CreateRenderer(Item item)   {     return new EncryptedEmailLinkRenderer(item);   }      public void Process(RenderFieldArgs args)   {     switch (args.FieldTypeKey)     {       case &quot;link&quot;:       case &quot;general link&quot;:         {           SetWebEditParameters(args, new string[] { &quot;class&quot;, &quot;text&quot;, &quot;target&quot;, &quot;haschildren&quot; });           if (!string.IsNullOrEmpty(args.Parameters[&quot;text&quot;]))           {             args.WebEditParameters[&quot;text&quot;] = args.Parameters[&quot;text&quot;];           }           LinkRenderer renderer = this.CreateRenderer(args.Item);           renderer.FieldName = args.FieldName;           renderer.FieldValue = args.FieldValue;           renderer.Parameters = args.Parameters;           renderer.RawParameters = args.RawParameters;           args.DisableWebEditContentEditing = true;           RenderFieldResult result = renderer.Render();           args.Result.FirstPart = result.FirstPart;           args.Result.LastPart = result.LastPart;           break;         }     }   }   private static void SetWebEditParameters(RenderFieldArgs args, params string[] parameterNames)   {     Assert.ArgumentNotNull(args, &quot;args&quot;);     Assert.ArgumentNotNull(parameterNames, &quot;parameterNames&quot;);     foreach (string str in parameterNames)     {       if (!string.IsNullOrEmpty(args.Parameters[str]))       {         args.WebEditParameters[str] = args.Parameters[str];       }     }   } }  The only difference to the Sitecore GetLinkFieldValue is that mine returns my EncryptedEmailLinkRenderer .&#160; This adds the ability to encode/encrypt email addresses in links.&#160; Sitecores Link type can apply to a number of links, such as links to other documents in the content tree, links to external sites, and more, including email links    The LinkType property indicate which of the above options were used to create a link. when “Insert Email” was selected the LinkType is “ mailto ”.&#160; The important piece of code to change was in the Render method, which allows me to update the dictionary of attributes for the anchor (&amp;lt;a&amp;gt;) tag that will be output.  if (((str8 = this.LinkType) != null) &amp;amp;&amp;amp; (str8 == &quot;javascript&quot;)) {   dictionary[&quot;href&quot;] = &quot;#&quot;;   dictionary[&quot;onclick&quot;] = StringUtil.GetString(new string[] { dictionary[&quot;onclick&quot;], url }); } else if (this.LinkType == &quot;mailto&quot;) {   var encryptedEmailAddress = EncodeEmailAddress(url.Replace(&quot;mailto:&quot;, &quot;&quot;));   dictionary[&quot;href&quot;] = string.Format(&quot;javascript:sendEmail(&#39;{0}&#39;)&quot;, encryptedEmailAddress);   if (IsLinkTextEmailAddress(str)) {     dictionary[&quot;class&quot;] = &quot;encrypted&quot;;     str = EncodeEmailAddress(str);   }         } else {   dictionary[&quot;href&quot;] = HttpUtility.HtmlEncode(StringUtil.GetString(new string[] { dictionary[&quot;href&quot;], url })); }  &#160;  the IsLinkTextEmailAddress method checks to see if the inner text of the &amp;lt;a&amp;gt; element is an email address.&#160; If so, it also needs to be encoded.&#160; Also note that if the inner text is encoded I need a way to decode it on the client side.&#160; To accomplish this I add the “encoded” css class to all links needing to be decoded.&#160; The jquery for my page needs to contain the following logic to perform the decryption/decoding on page load  $(&#39;a.encrypted&#39;).each(function() {         var emailAddress = decodeEmail($(this).text());   $(this).text(emailAddress); });  &#160;  You’ll notice that the anchor tag contains a javascript function sendEmail .&#160; This function decodes the href (destination) and adds the mailto:  function sendEmail(encodedEmail) {   location.href = &quot;mailto:&quot; + decodeEmail(encodedEmail); } function decodeEmail(encodedEmail) {   var email = &quot;&quot;;   for (i = 0; i &amp;lt; encodedEmail.length; ) {     var letter = &quot;&quot;;     letter = encodedEmail.charAt(i) + encodedEmail.charAt(i + 1)     email += String.fromCharCode(parseInt(letter, 16));     i += 2;   }   return email; }  This javascript also needs to be added to the page (inline or preferrably in a .js file).  Below is the complete class, inherited from Sitecore.Xml.Xsl.LinkRenderer  public class EncryptedEmailLinkRenderer : LinkRenderer {   private readonly char[] _delimiter = new char[] { &#39;=&#39;, &#39;&amp;amp;&#39; };   public EncryptedEmailLinkRenderer(Item item) : base(item) { }     protected override string GetUrl(XmlField field)   {     if (field != null)     {       return new LinkUrl().GetUrl(field, this.Item.Database);     }     return LinkManager.GetItemUrl(this.Item, GetUrlOptions());   }   protected internal static UrlOptions GetUrlOptions()   {     UrlOptions defaultUrlOptions = LinkManager.GetDefaultUrlOptions();     defaultUrlOptions.SiteResolving = Settings.Rendering.SiteResolving;     return defaultUrlOptions;   }   public override RenderFieldResult Render()   {     string str8;     SafeDictionary&amp;lt;string&amp;gt; dictionary = new SafeDictionary&amp;lt;string&amp;gt;();     dictionary.AddRange(this.Parameters);     if (MainUtil.GetBool(dictionary[&quot;endlink&quot;], false))     {       return RenderFieldResult.EndLink;     }     Set&amp;lt;string&amp;gt; set = Set&amp;lt;string&amp;gt;.Create(new string[] { &quot;field&quot;, &quot;select&quot;, &quot;text&quot;, &quot;haschildren&quot;, &quot;before&quot;, &quot;after&quot;, &quot;enclosingtag&quot;, &quot;fieldname&quot; });     LinkField linkField = this.LinkField;     if (linkField != null)     {       dictionary[&quot;title&quot;] = StringUtil.GetString(new string[] { dictionary[&quot;title&quot;], linkField.Title });       dictionary[&quot;target&quot;] = StringUtil.GetString(new string[] { dictionary[&quot;target&quot;], linkField.Target });       dictionary[&quot;class&quot;] = StringUtil.GetString(new string[] { dictionary[&quot;class&quot;], linkField.Class });     }     string str = string.Empty;     string rawParameters = this.RawParameters;     if (!string.IsNullOrEmpty(rawParameters) &amp;amp;&amp;amp; (rawParameters.IndexOfAny(this._delimiter) &amp;lt; 0))     {       str = rawParameters;     }     if (string.IsNullOrEmpty(str))     {       Item targetItem = this.TargetItem;       string str3 = (targetItem != null) ? targetItem.DisplayName : string.Empty;       string str4 = (linkField != null) ? linkField.Text : string.Empty;       str = StringUtil.GetString(new string[] { str, dictionary[&quot;text&quot;], str4, str3 });     }     string url = this.GetUrl(linkField);     if (((str8 = this.LinkType) != null) &amp;amp;&amp;amp; (str8 == &quot;javascript&quot;))     {       dictionary[&quot;href&quot;] = &quot;#&quot;;       dictionary[&quot;onclick&quot;] = StringUtil.GetString(new string[] { dictionary[&quot;onclick&quot;], url });     }     else if (this.LinkType == &quot;mailto&quot;) {       var encryptedEmailAddress = EncodeEmailAddress(url.Replace(&quot;mailto:&quot;, &quot;&quot;));       dictionary[&quot;href&quot;] = string.Format(&quot;javascript:sendEmail(&#39;{0}&#39;)&quot;, encryptedEmailAddress);       if (IsLinkTextEmailAddress(str)) {         dictionary[&quot;class&quot;] = &quot;encrypted&quot;;         str = EncodeEmailAddress(str);       }             }     else     {       dictionary[&quot;href&quot;] = HttpUtility.HtmlEncode(StringUtil.GetString(new string[] { dictionary[&quot;href&quot;], url }));     }     StringBuilder tag = new StringBuilder(&quot;&amp;lt;a&quot;, 0x2f);     foreach (KeyValuePair&amp;lt;string, string&amp;gt; pair in dictionary)     {       string key = pair.Key;       string str7 = pair.Value;       if (!set.Contains(key.ToLowerInvariant()))       {         FieldRendererBase.AddAttribute(tag, key, str7);       }     }     tag.Append(&#39;&amp;gt;&#39;);     if (!MainUtil.GetBool(dictionary[&quot;haschildren&quot;], false))     {       if (string.IsNullOrEmpty(str))       {         return RenderFieldResult.Empty;       }       tag.Append(str);     }     RenderFieldResult result = new RenderFieldResult();     result.FirstPart = tag.ToString();     result.LastPart = &quot;&amp;lt;/a&amp;gt;&quot;;     return result;   }   private static string EncodeEmailAddress(string email)   {     return BitConverter.ToString(ASCIIEncoding.ASCII.GetBytes(email)).Replace(&quot;-&quot;, &quot;&quot;);   }   private static bool IsLinkTextEmailAddress(string linkText)   {     // copied from /sitecore/system/Settings/Validation Rules/Field Rules/Common/Is Email     // to use the same pattern as the email rule within Sitecore.     const string emailValidPattern = @&quot;^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$&quot;;     var emailValidator = new Regex(emailValidPattern);     return emailValidator.IsMatch(linkText);   } }  Finally make update the Sitecore pipeline in the web.config file  &amp;lt;renderField&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.SetParameters, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.ExpandLinks, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;!--&amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetLinkFieldValue, Sitecore.Kernel&quot; /&amp;gt;--&amp;gt;   &amp;lt;processor type=&quot;MyVeryOwn.Pipelines.RenderField.GetEncrpytedLinkFieldValue, sitecore&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.GetDocxFieldValue, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel&quot; /&amp;gt;   &amp;lt;processor type=&quot;Sitecore.Pipelines.RenderField.RenderWebEditing, Sitecore.Kernel&quot; /&amp;gt; &amp;lt;/renderField&amp;gt;</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/11/encoding-email-links-in-sitecore</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/11/encoding-email-links-in-sitecore</guid>
                    <pubDate>Tue, 29 November 2011 23:50:00 </pubDate>
                </item>
                <item>
                    <title>Parallelizing image requests in ASP.NET for Kentico CMS using Response Filters</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/11/parallelizing-image-requests-in-aspnet-for-kentico-cms-using-response-filters</comments>
                    <description>Background  Using multiple domains for static files and images is recommended method to dramatically decrease your page load time.&#160; Browsers have&#160; a limited number of concurrent connections.  RFC 2616 defines “ clients that use persistent connections should limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.”   The key point to take from the above is the recommendation is a browser should have a maximum of 2 connections per domain name.&#160; This means that if our pages downloading a lot of javascript, css, and image files, our pages will likely load slow, as the browser waits for connections to become available.  We can reduce this by for javascript and css by using script combiners that join all javascript files into one javascript file and all css files into one css file.&#160; But what about images?  The trick here is creating using multiple domain names for our images.&#160; We can use up to for domain names for this and effectively quadruple our page performance for images.&#160; The different domains or sub domains still all point to the same IP address.&#160; We also want each image to always go to the same domain name, so the browser can better cache the image.  &#160;  The Good Stuff  A website I recently developed has a many thumbnail images on the home page, as little as 20, but it could as high as the site editor wants to make it.&#160; Occasionally the site was timing out, with a timeout defined by operations as taking longer than 60 to completely load.&#160; Running the site through Firebug and then pingdom revealed some response for image requests were on occasion taking longer than 2 minutes to return, even for static images, where a 304 Not Modified was returned.&#160; The site does have iframes embedded, which are cached locally, but the cache expires and its possible the source of the iframe is slow to respond and in turn holding up other elements from loading.&#160; I wanted to make it obvious where bottlenecks lay.  The website is built with Kentico CMS so I had to be careful where to put the logic to change the url for images.&#160; Other projects, that used a CMS custom to those projects made it easy because the domain name could be selected when the entity is retrieved from the database.&#160; Kentico doesn’t afford me the same luxury, so my options were either update the src field when I created the HTML for the page/template ( eg in the ASCX or ASPX file) or opt for a catch-all method that scans every outgoing page and modifies it.&#160; I chose the later, using response filters .  &#160;   During one of the later stages in the HTTP Pipeline the rendered markup is handed off to a response filter which, if supplied, has an opportunity to inspect and modify the markup before it is returned to the requesting browser.    &#160;  Now what I needed to do is intercept the outgoing response, after it had been rendered, search for any img elements, extract the src attribute and modify it as necessary.  I store the possible domain names to use in the web.config file.  &amp;lt;add key=&quot;ImageHosts&quot; value=&quot;i1.myimageserver.com,i2.myimageserver.com,i3.myimageserver.com,i4.myimageserver.com&quot; /&amp;gt;  (You would use your own domain names. These are made up)  Because we always want the same image to resolve to the same server I use GetHashCode() to help select a domain name from the above list.&#160; Kentico CMS stores images as attachments as well as in the Media Library.&#160; To have images handled by Kentico not included in the parallelization we need to exclude urls starting with /getattachment/ and /getmedia/. I’ve done this in combination with a web.config setting.  &amp;lt;add key=&quot;ParallelizeKenticoImages&quot; value=&quot;true&quot; /&amp;gt; &amp;lt;add key=&quot;UseImageParallelization&quot; value=&quot;true&quot;  Code:  public class ImageHostsHelper {   private static string[] _imageHosts;       private readonly static string[] _dynamicImageUrls = new []                       {                         &quot;/getattachment/&quot;,                         &quot;/getmedia/&quot;                       };                                 private static string[] ImageHosts   {     get     {       if (_imageHosts == null)       {         var imageHostsString = string.IsNullOrEmpty(ConfigurationManager.AppSettings[&quot;ImageHosts&quot;])           ? &quot;www.mysite.com.au&quot;           : ConfigurationManager.AppSettings[&quot;ImageHosts&quot;];         _imageHosts = imageHostsString.Split(new[] { &quot;,&quot; }, StringSplitOptions.RemoveEmptyEntries);       }       return _imageHosts;     }   }   public static bool ParallelizeKenticImages   {     get     {               return bool.Parse(string.IsNullOrEmpty(ConfigurationManager.AppSettings[&quot;ParallelizeKenticoImages&quot;])         ? &quot;true&quot;         : ConfigurationManager.AppSettings[&quot;ParallelizeKenticoImages&quot;]);     }         }   public static bool ParallelizeKenticImages   {     get     {               return bool.Parse(string.IsNullOrEmpty(ConfigurationManager.AppSettings[&quot;UseImageParallelization&quot;])         ? &quot;false&quot;         : ConfigurationManager.AppSettings[&quot;UseImageParallelization&quot;]);     }         }   public string GetImageUrl(string imageUrl)   {               if (!string.IsNullOrEmpty(imageUrl) &amp;amp;&amp;amp; !imageUrl.StartsWith(&quot;http&quot;) &amp;amp;&amp;amp; !ContainsAnyOf(imageUrl, dynamicImageUrls)) {             var hostNumber = Math.Abs(imageUrl.GetHashCode())%ImageHosts.Length;       return string.Format(&quot;http://{0}{1}&quot;, ImageHosts[hostNumber], imageUrl);     }     return imageUrl;   }   private bool ContainsAnyOf(string content, IEnumerable&amp;lt;string&amp;gt; startStrings)   {     if (ParallelizeKenticImages)       return false;     foreach (var starter in startStrings) {       if (starter == null) continue;       if (string.IsNullOrEmpty(starter) &amp;amp;&amp;amp; (content.IndexOf(starter) &amp;gt; -1))         return true;     }     return false;   } }  With the logic to generate the domain name for each image done, now I need to create the response filter.&#160; This is done using a HttpModule to take the incoming request context and then hook the response filter to the response for the request.  The HttpModule is pretty basic, hooking the filter into the Response.&#160; The only noticeable part is that I had to filter on webresource.axd or the resources embedded inside the assembly would not load and the CMS back office would not function correctly.  public class ImageHostsModule : IHttpModule {   public void Dispose() { }     public void Init(HttpApplication context)   {     context.BeginRequest += new EventHandler(this.BeginRequestHandler);   }   void BeginRequestHandler(object sender, EventArgs e)   {     HttpApplication application = (HttpApplication)sender;    &#160; if (ImageRequestParallelizer.UseImageParallelization       &amp;amp;&amp;amp; application.Context.Request.Url.AbsoluatePath.ToLowerInvariant().IndexOf(&quot;webresource.axd&quot;) == -1)       application.Response.Filter = new ImageHostsFilter(application.Response.Filter);   } }  I pass in the Response.Filter to my custom filter so I have access to the outgoing response stream and can modify it.&#160; The filter itself looks for any img elements, and if found, extracts the src value, modifies it and puts it back, using ImageHostsHelper.GetImageUrl to get the new src value.  /// &amp;lt;summary&amp;gt; /// Scans outgoing HTML for img elements and updates the src path to use parallelization of images /// &amp;lt;/summary&amp;gt; public class ImageHostsFilter : MemoryStream {   private System.IO.Stream _filter;   private bool _filtered = false;   private static ImageHostsHelper _helper = new ImageHostsHelper();   public ImageHostsFilter(System.IO.Stream filter)   {     _filter = filter;   }   public override void Close()   {     const string sourceToken = &quot;src=&quot;;     const string imageToken = &quot;&amp;lt;img &quot;;     const string imageEndToken = &quot;/&amp;gt;&quot;;     if (_filtered) {       if (Length &amp;gt; 0) {         byte[] bytes;         string content = System.Text.Encoding.UTF8.GetString(this.ToArray());                 // find all images.         var occurrence = content.IndexOf(imageToken, 0);         while (occurrence &amp;gt;= 0) {           int occurrenceEnd = content.IndexOf(imageEndToken, occurrence + 5);           if (occurrenceEnd &amp;lt;= occurrence)             break;           int srcPos = content.IndexOf(sourceToken, occurrence + 5) + 4;                       string srcDelim = content.Substring(srcPos, 1);           int srcPosEnd = content.IndexOf(srcDelim, srcPos + 1);           string imageUrl = content.Substring(srcPos + 1, srcPosEnd - srcPos - 1);           string newImageUrl = _helper.GetImageUrl(imageUrl.Replace(HttpContext.Current.Request.GetBaseUrl(), &quot;&quot;));           if (!newImageUrl.Equals(imageUrl))             content = content.Replace(imageUrl, newImageUrl);           // should we handle that newImageUrl is different size to imageUrl and therefore           // occurenceEnd value as now changed?           occurrence = content.IndexOf(imageToken, occurrenceEnd + 2);         }         bytes = System.Text.Encoding.UTF8.GetBytes(content);         _filter.Write(bytes, 0, bytes.Length);       }       _filter.Close();     }           base.Close();   }   public override void Write(byte[] buffer, int offset, int count)   {     if ((System.Web.HttpContext.Current != null)       &amp;amp;&amp;amp; (&quot;text/html&quot; == System.Web.HttpContext.Current.Response.ContentType)) {               base.Write(buffer, offset, count);       _filtered = true;     } else {       _filter.Write(buffer, offset, count);       _filtered = false;     }         } } &#160;  Now all that’s left is to include the HttpModule in your web.config, which you know how to do, right? &amp;lt;</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/11/parallelizing-image-requests-in-aspnet-for-kentico-cms-using-response-filters</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/11/parallelizing-image-requests-in-aspnet-for-kentico-cms-using-response-filters</guid>
                    <pubDate>Thu, 03 November 2011 19:36:00 </pubDate>
                </item>
                <item>
                    <title>Using Google Chrome Frame with Sitecore CMS</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/10/using-google-chrome-frame-with-sitecore-cms</comments>
                    <description>Basics  Chrome Frame from Google is a plugin for IE that contains a mini version of the Chrome browser, allowing users of older IE browsers to see new HTML5/CSS sites in all their glory.&#160; With Chrome Frames enabled for your site, IE users will be prompted to install Chrome Frames.&#160; They can by default choose to not install Chrome Frames and continue viewing the site using the Trident rendering engine in IE.&#160; Using Chrome Frames will not only improve the look of your site in IE, it will probably also improve it&#39;s performance.  The quickest and easiest way to get Chrome Frames working with your site is to include the correct meta tag in your header  &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;chrome=1&quot; /&amp;gt;  will turn on Chrome Frames for all versions of IE.&#160; To enable for specific versions, change the chrome value to IE8 which will do all IE versions up to and including 8 or IE7 which will do all IE versions up to and including 7 etc.  You can then use either Javascript or server-side User-Agent sniffing.&#160; To use server-side, look for the string &#39;chromeframe’ in the user-agent header and then include the meta element above in the header.  The details of a basic implementation can be found at the project home  &#160;  Providing a friendly page consistent with site design  The basic implementation is all well and good but you’ll probably want to present a user friendly page to IE users explaining that your site requires a HTML5 compliant browser, and how to install Chrome Frame. In my case, the site I’m working on required that users that can’t support HTML5 be not allowed to use the site at all.&#160; This makes overall site development much easier, as I don’t have to code for HTML5 as well as older HTML4 that lacks the semantic mark-up in HTML5 as well as the CSS3 goodness we’ve all come love, rounded borders and shadows.  First step is to create the HTML page for the warning page (/browser-warning in my case).&#160; I took a copy of my site’s default Browser layout and modified HTML5 elements to use HTML4 elements, basically I replaced section, header, footer, and nav with div ’s.  I also included an IE stylesheet that included the styles for my divs that replaced the HTML5 elements.  Next, because the Chrome Frame will only appear if the browser is IE , I restricted inclusion of the styles and javascript to only IE.  &amp;lt;!--[if IE]&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;style&amp;gt;       .chromeFrameInstallDefaultStyle {     position:relative;                 left: 0;     top: 0;     margin: 20px 0 0 0;      height: 730px;     } &amp;lt;/style&amp;gt; &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;   $(document).ready(function() {           CFInstall.check({       mode: &quot;inline&quot;,       node: &quot;gcfprompt&quot;,       destination: &quot;/&quot;     });                 $(&quot;iframe&quot;).removeAttr(&quot;frameborder&quot;);             $(&quot;iframe&quot;).attr(&quot;frameBorder&quot;, 0);           $(&quot;iframe&quot;).attr(&quot;scrolling&quot;,&quot;no&quot;);         }); &amp;lt;/script&amp;gt;  &amp;lt;![endif]—&amp;gt;  I then configure the options for the Chrome Installer.  mode: &quot;inline&quot;, node: &quot;gcfprompt&quot;, destination: &quot;/&quot;  I want it to display inline, in the div with id “ gfcprompt ”.&#160; When the installation is complete I want the user to be directed back to the home page.&#160; I’ve also included some code to remove the frame borders from the Chrome Frame installation iframe.  My html body looks like the following  &amp;lt;body&amp;gt; &amp;lt;div class=&quot;container_24&quot;&amp;gt;   &amp;lt;div id=&quot;heading&quot;&amp;gt;      &amp;lt;div class=&quot;grid_6&quot;&amp;gt;       &amp;lt;a href=&quot;/&quot;&amp;gt;&amp;lt;img src=&quot;/images/logo.png&quot; /&amp;gt;&amp;lt;/a&amp;gt;     &amp;lt;/div&amp;gt;           &amp;lt;div class=&quot;clear&quot;&amp;gt;&amp;lt;/div&amp;gt;   &amp;lt;/div&amp;gt;   &amp;lt;sc:placeholder ID=&quot;headerPlaceHolder&quot; runat=&quot;server&quot; key=&quot;header&quot;&amp;gt;&amp;lt;/sc:placeholder&amp;gt;                       &amp;lt;form method=&quot;post&quot; runat=&quot;server&quot; id=&quot;mainform&quot;&amp;gt;             &amp;lt;div id=&quot;body&quot; class=&quot;grid_24&quot;&amp;gt;                                           &amp;lt;div class=&quot;nav&quot;&amp;gt;&amp;lt;sc:xslfile ID=&quot;Xslfile1&quot; runat=&quot;server&quot; renderingid=&quot;{EF44E8F3-FD7D-4F9C-B6B1-DA24353C74EA}&quot; path=&quot;/xsl/Breadcrumb.xslt&quot;&amp;gt;&amp;lt;/sc:xslfile&amp;gt;&amp;lt;/div&amp;gt;       &amp;lt;sc:placeholder ID=&quot;bodyPlaceHolder&quot; runat=&quot;server&quot; key=&quot;body&quot; /&amp;gt;               &amp;lt;!--[if IE]&amp;gt;       &amp;lt;div id=&quot;gcfprompt&quot;&amp;gt;&amp;lt;/div&amp;gt;       &amp;lt;![endif]--&amp;gt;             &amp;lt;/div&amp;gt;         &amp;lt;/form&amp;gt;   &amp;lt;div id=&quot;footer&quot; class=&quot;clearfix&quot;&amp;gt;           &amp;lt;sc:Sublayout ID=&quot;Sublayout2&quot; runat=&quot;server&quot; path=&quot;/layouts/footer details.ascx&quot; /&amp;gt;         &amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;/body&amp;gt;  You can see I’m providing the ability for site authors to modify the friendly message that gets displayed.  &#160;  Capturing all requests from an IE browser  Now the tricky part.&#160; I need to capture all requests from IE browsers.&#160; Sitecore CMS uses Devices to simulate different browsers, so one option is to create a new device for IE and assign that device to my warning page.&#160; The problem is I need to detect no matter what page, so it doesn’t quite fit.  What I need to do is use a HttpModule to intercept the request, inspect the User-Agent header or Browser and redirect to my warning page.&#160; In Sitecore this is done by hooking into the appropriate pipeline.&#160; In my case, I need to hook into the httpRequestBegin pipeline.  public class ChromeFrameProcessor : HttpRequestProcessor {   public override void Process(HttpRequestArgs args)   {     var warningItemPath = Settings.GetSetting(&quot;IEWarningUrl&quot;, &quot;/sitecore/content/home/browser warning&quot;);     // Need to allow access to /sitecore/ via IE.        if (Context.Database == null)       return;     if (Context.Database.Name != &quot;web&quot;)       return;     // We want to stop browsers that are non-HTML5 compliant and redirect to the IEWarning page.                     if (args.Context.Request.Browser.Browser == &quot;IE&quot;)     {       var warningItem = GetItemFromItemPath(warningItemPath);       var warningItemUrl = LinkManager.GetItemUrl(warningItem);       if (warningItemUrl != args.Context.Request.Url.AbsoluteUri)         WebUtil.Redirect(warningItemUrl);     }   }   public static Item GetItemFromItemPath(string itemPath)   {     Item temp = null;     using (new Sitecore.SecurityModel.SecurityDisabler()) {       temp = Context.Database.GetItem(itemPath);     }     return temp;   }   public static Item GetItemFromUrl2(string url)   {     string referring_item_path = null;     if (Uri.IsWellFormedUriString(url, UriKind.Relative))       url = Sitecore.Web.WebUtil.GetFullUrl(url);     if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) {       var uri = new Uri(url);       if (uri.AbsolutePath.StartsWith(&quot;/sitecore/&quot;))         referring_item_path = uri.AbsolutePath;       else         referring_item_path = Sitecore.Context.Site.StartPath + uri.AbsolutePath;     }     referring_item_path = Regex.Replace(referring_item_path, @&quot;(?:\.aspx|\.ashx)&quot;, &quot;/&quot;);     Item retVal = null;     using (new Sitecore.SecurityModel.SecurityDisabler()) {       retVal = Sitecore.Context.Database.GetItem(referring_item_path);     }     return retVal;         } }  The first step is to get the item to redirect to if IE is used.&#160; This is kept as a setting in the web.config file. Next up we need to inspect the request to determine if we need to show the warning page.  if (args.Context.Request.Browser.Browser == &quot;IE&quot;)  We only need to check for IE here.&#160; If Chrome Frame is installed, the user-agent and browser will be Apple / Webkit and not IE / MSIE.&#160; Once we know we need to do a redirect we can retrieve the CMS item for the warning page and extract it’s Url, using LinkManager. Then it’s just a matter of redirecting to that URL.  if (warningItemUrl != args.Context.Request.Url.AbsoluteUri)   WebUtil.Redirect(warningItemUrl);  If we don&#39;t put the check for Url in here we&#39;ll get an infinite loop as the redirect causes another browser check.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/10/using-google-chrome-frame-with-sitecore-cms</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/10/using-google-chrome-frame-with-sitecore-cms</guid>
                    <pubDate>Fri, 21 October 2011 20:35:00 </pubDate>
                </item>
                <item>
                    <title>The accident that changed me</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/10/the-accident-that-changed-me</comments>
                    <description>October 17 2011 marked 20 years since my head injury, an event that continues to influence my life daily.&#160; Twenty years on is probably as good a time as any to remember what happened on that day and the for the first few years after it.  Thursday October 17th 1991 I was a 15 year old grade 10 student at Maleny State High School .&#160; I&#39;d recently filled out my subjects for senior school and what I hoped would shape my future.&#160; I wanted to be an astrophysicist and had enrolled in Math I, Math II, Chemistry, Physics, and Biology, as well as the compulsory English.&#160; The 5 hardest subjects you can do at highschool, but also the 5 I worked out I needed most to become what I wanted to be.  I also remember only the day before, a Wednesday, I had told my best friend where was no way I was coming to school on Friday.&#160; That Friday my HPE (Health and Physical Education) class had kayaking at Baroon Pocket Dam and I hated taking my shirt off. Hated it.&#160; Water in general just didnt&#39; do it for me.&#160; Glasses get wet...&#160; Months later I would laugh with this with my friend. I certainly did get the day off that Friday. I got the days off for a long time after that.&#160; I never did finish grade 10.  From what I&#39;ve been able to fill in, my friends and I were sitting at &#39;B Block&#39;, the manual arts block we always sat.&#160; It was at the top of the school and the least populated block, which is good because it meant we could better make our pipe bombs and read our Terrorists Cookbooks and whatever it is bevan kids of the early 90&#39;s did.&#160; One of my mates younger brothers (a grade 8 kid) ran off with a mates hat and me being the biggest and fastest gave chase.  Maleny State High School is built on a hill, and comprises a number of&#160; buildings (Blocks) for each different department.&#160; Connecting each block are covered walkways, much the same as in other schools.&#160; A result of the school being on a hill is that most of the walkways include stairs at various parts, as was the case coming down from B block to the central hub of the school, the Undercovered Area (where the Tuckshop lived).&#160; The first set of stair was about 6-7 steps and but the second set wsa only 3 steps, so the drop from the top to bottom was not very great, maybe 50-60cm.&#160; At the bottom of each set of strairs, set 5cm in front of the bottom step was a frame used to support the roof, where the angled roof over the stairs met the rest of the covering.&#160; Welded in the center of the large beam was a metal plate that was used to bolt the wooden frame of the roof to the supporting beam.&#160; The end of this plate (approximately 8mm thick) stuck out from the top of the beam, such that there was a hard corner at the bottom of it...  I can only figure I chose to gain some ground on this kid I was chasing by jumping that set of stairs.  The top of my forehead hit the corner of the plate sticking out and because I was carrying speed, my head and body continued to move forward.&#160; My head twisted and my chin came up, so the right side of my face smashed into the steel beam.&#160; I pretty much stopped there, about 2.2m up in the air, and fell straight down onto the concrete below.    I woke up in the doctors surgery, not far from the school, as the doctor stitched my head together, vomitting into a bucket as he stitched.&#160; I think that was about 1:20pm (I&#39;m assuming the accident itself happened at lunch time).&#160; I was admitted to the Maleny hospital for observation.&#160; Friday came and I had a headache but I wanted to go home. I was bored lying around.&#160; Doctors being smarter than 15 year old kids said &#39;No&#39;.&#160; I needed to stay in for observation for a few days.  Saturday morning (I think? maybe friday night. It&#39;s all a blur but I do remember the pethidine shots in the arse. those hurt.) I started to complain of numbness in my left hand and leg.&#160; Mum, who had be sitting beside my bed since Thursday started massaging it, thinking the bloodflow may be restricted.&#160; The doctors suspected I had a broken neck and I was put on an ambulance for a bumpy 45 minute lights and sirens ride to Nambour General Hospital, where an x-ray could be performed.  I don&#39;t have memory of getting in the Ambulance, as I Saturday morning was when I fell into a coma.&#160; Nambour found I didn&#39;t have a broken neck.&#160; What I had was four accute subdural haematoma &#39;s, one in each quadrant of my brain, with the largest, about 35mm across, in the front right quadrant closest to where my head had been split open. My brain had been bleeding since Thursday afternoon and had now reached critical mass, so to speak.  Wikipedia says this about accute subdural haematoma&#39;s: Acute bleeds often develop after high speed acceleration or deceleration injuries and are increasingly severe with larger hematomas. They are most severe if associated with cerebral contusions . [3]  Though much faster than chronic subdural bleeds, acute subdural bleeding is usually venous and therefore slower than the usually arterial bleeding of an epidural hemorrhage .  Acute subdural bleeds have a high mortality rate, higher even than epidural hematomas and diffuse brain injuries , because the force (acceleration/deceleration) required to cause them causes other severe injuries as well. [4]  The mortality rate associated with acute subdural hematoma is around 60 to 80%. [5]    See also Traumtic Brain Injury  It was at this point the helicopter was called.&#160; I needed to be in Brisbane ASAP.&#160; The doctors told my parents there was a 20% chance I&#39;d live and if I did it was a foregone conclusion that I&#39;d be a &quot;vegetable&quot;. It was at this point my family called in our church.&#160; Practicing Mormons, attending the Nambour ward, at least I was near the Bishop and other that would come to administer a blessing .&#160; Apprently as soon as this blessing was administered my condition did not further deteriorate.  Once at the Royal Brisbane Hospital doctors examined me and determined an operation to remove the clots in my brain was too risky, as the largest was over my &quot;Personality and Learning&quot; area and would definitely leave me in bad shape in those areas.&#160; My best bet was to take steriods and hope the swelling went down and the blood clots broke up.  My injuries listed: 4 brain bleed (as mentioned above) causing complete left side hemiplasia (paralysis).&#160; The top and rear of my skull was fractured, as was my right cheekbone. My right eye was swollen shut along with the majority of the right side of my face being black and swollen and my left eye was paralysed but working and open.&#160; I also had Papilledema of the left eye and I remember something about 3 and 7 nerve damage?  I think I spent 10 days in the ICU, 7 of those in a coma.&#160; It might have been less, I&#39;m not sure. I do remember being conscious on a few occasions in the ICU.&#160; I remember pulling at my cathoder on several occasions, trying to remove it.&#160; I also remember trying to go number two lying down, with a bed ban under my bottom. Not very doable so I got to use a wheel chair with a toilet seat attached&#160; and a real toilet.&#160; I also remember waking in the middle of the night to what I swear was a male and female nurse &quot;going at it&quot; behind a curtain.&#160; Seems my male teenage brain was on the mend ;)  During my ICU stay my parents had two missionaries come and administer another blessing. This time they blessed me that I&#39;d eventually make a full recovery (sounds like a safe bet at this stage, the heathen in my says).  Once out of the ICU I went up to floor N (I think). Apparently I was too old for the childrens ward but too young to be put with the general adult male populate. This meant I got my own suite with own bathroom and TV and everything. Sweet! (Suite?! :))&#160; I had a view out the window over down to the exhibition grounds.  Over the next few weeks I had physiotherapy a few times day to try and get my left side going again.&#160; It&#39;s scary when your brain is telling your fingers to open and to move, but nothing is happening.&#160; Focussing so hard brought so little effort for a long time.&#160; For a while there I was very scared. I didn&#39;t know if I was going to fully recover.&#160; My head pounded all the time and couldn&#39;t walk more than a few metres before I got too tired to continue.&#160; I got a release for a few hours in November so I could go to my grandmothers house and watch the Adelaide Formula 1 Grand Prix (yes I was a big fan back then too :)) and walking up the 6 stairs leading in to her house was&#160; a real struggle.  During my stay at the RBH a few friends came and dropped in, brining dirt bike and car magazines for me to read.&#160; I spent a good portion of my waking hours dreaming of riding dirt bikes when I got out (at the time not knowing how risky and stupid that would be).  After 4 weeks I was transferred the Head Injury Rehabilitation unit at the Princess Alexandra Hospital, where I participated in Physiotherapy, Occupational Therapy, Hydra Therapy, Speech Therpay and bit of conselling.&#160; I still couldn&#39;t walk properly and my left side was very weak.&#160; I think there was even some discolouration from bruising, still evident on the right side of my face.&#160; I did meet some interesting people too.&#160; A 28 year old called Brian, who had been in an horific car accident that killed his partner and had been at the rehab unit for the last 5 months.&#160; An interesting guy to talk to.  Thankfully I powered through rehab and was fit leave in only 2 weeks definitely surprising the doctors with my progress.  I got out as the school year ended. I did go back to school the next year but I had no ability to concentrate for longer than a minute or two at best and my memory was rubbish.&#160; I had many days off.&#160;&#160; My report cars all read the same &quot;Motivation a cause for concern.&#160; Lack of attendance a problem. Next year will be a better year&quot;.&#160; All the teachers knew what happened to me and all were understanding.&#160; My best result that year as a C in english. Everything else was D- or E. E!  I wasn&#39;t a very popular guy at school for a number of months that year, 1992.&#160; Maleny rains a lot, and as a result of my accident building regulations were revisited and it was found that the beams that held up the roof at the bottom of all the staircases, were too close to the bottom stair. regulation said they needed to be at least 300mm away from the stair, not 50mm.&#160; Through 1992 all beams were moved back to the required distance. This meant the walkway covers had to be removed. When it rained, no one had could stay dry, and it rained a lot.  Working in paint factory in Darra in 1995 I realised I was getting better.&#160; I was doing caculations in my head faster than the guy I worked with (we worked in teams of two) could enter the numbers into the calculator.&#160; I could mentally attend to tasks for inceasingly longer periods of time and I realised I can do better than were I was.&#160; I went back to grade 11 in 1996, as a 19 year old, studying Math B, Math C, Physics, Chemistry and Information Processing and Technology. I got the Math B and IPT awards in both grade 11 and 12, and finished #2 in the year at the end of grade 12, with VHA&#39;s (A&#39;s) in Math B, Math&#160; C, Physics, IPT. (and HA&#39;s, B+&#39;s) in English and Chemistry.  One day a few years after my accident I was feeling particularly down with myself and the lasting effects of the accident and my brother got angry at me and told me to shut up because by all rights I should be dead.&#160; To this day, whenever I get down on myself or I&#39;m facing adversity, I recall my angry 15 year old brother and his true words.&#160; For me, things surely should have been worse than whatever situation I find myself in and I should be thankful for everything and anything I can get.  Over those two years from 1996-1997, and perhaps a bit of 1995, I came to know that people are capable of achieving great things if they&#39;re motivated enough and they keep at it.&#160; I&#39;ve definitely come out the other end a different person becuase of that experience and the realisation that we all, even teenagers are mortal.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/10/the-accident-that-changed-me</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/10/the-accident-that-changed-me</guid>
                    <pubDate>Tue, 18 October 2011 19:26:00 </pubDate>
                </item>
                <item>
                    <title>F1 2011 versus iRacing</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/9/f1-2011-versus-iracing</comments>
                    <description>Codemasters F1 2011 came out on Thursday just gone (September 22nd) to generally high anticipation.&#160; It’s the follow up to the BAFTA winning F1 2010 available on PC, XBox 360, and PS3.&#160; I’m a mad keen F1 fan so I should be really excited by this.&#160; The game reportedly offers much improved physics, new out of race action (post race), improved menus, as well as the goodies in the real F1 2011 season such as KERS (Kenetic Energy Recovery System) and DRS (Drag Reduction System).  I played for about 30 minutes on Thursday night and went round the track in on the Wettest Wet setting from the Quick Setups in about the same time I did in F1 2010 with my own setup, 1:24.6.  The changes to the physics engine is immediately noticeable.&#160; Driving with attention to the throttle and brake is now a must. In F1 2010 these two inputs were more off/on switches, balancing throttle through corners wasn’t necessary.&#160; Also, controlling the car once traction is lost has now been improved, with me able to perform controlled powerslides on a number of occasions.&#160; Codemasters have also removed the ability to tap the brake when you’ve spun and have the car immediately pull back on line. Tap the brake now, and you’ll slide down the grass/road; much better.  So far I’ve put in 5 hours into this game ( now more touted as a Sim ) and while it’s an improvement, I think I’m even more drawn to iRacing than F1 2010.&#160; This is disappointing, as I run the website for a competition based in this game, Atomic F1 and the members there are asking for articles on car setup etc for F1 2011, and I’m not motivated to play the game.&#160; The biggest problem is the way the car feels, more precisely, the steering.&#160; The force-feedback is good, better than it was in F1 2010, but to me it feels like my steering input has different effects at different car speed.&#160; I can’t quite put my finger on the malaise, but there’s something wrong there.  It’s also a personal thing about racing real life competitors versus racing AI.&#160; iRacing, for me, is a real sport.&#160; F1 2011 is just a game.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/9/f1-2011-versus-iracing</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/9/f1-2011-versus-iracing</guid>
                    <pubDate>Mon, 26 September 2011 21:09:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber New Hampshire with South Oval</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-new-hampshire-with-south-oval</comments>
                    <description>I do like New Hampshire.&#160; It&#39;s my first roval (Road course with parts on an Oval circuit) and it has some interesting corners.    Obviously turn 1 is different, on its banking.&#160;Turn 4 is fun and I seem to make up some ground under brakes, with my bias set at 64% front for this circuit.&#160; There&#39;s a lot of spinners on turn 6, as people get on the power too early.&#160; I&#39;ve also noticed a higher than normal number of people understeer off the track out at this corner.  Turn 9 is also tricky and I take it in second gear, gently coming off the brakes and trying to get the car align for going through turn 10.&#160; 10 is tricky because there is a bump on the inside and if you hit it there&#39;s a good chance you&#39;ll spin.  Turn 12 I&#39;ve named &#39;Andrew Kiss sucks at this corner&#39; corner because he just can&#39;t keep it on the track particular at this corner.&#160; For me, I lift off through 11 and start braking at 11 for turn 12.&#160; I hug the inside at the apex and then float wide to the bumpy edge of the track at track out.  A good lap for me at the moment is a 1:11.3.&#160; I&#39;ve seem some guys doing 1:10.3&#39;s.&#160; I find I&#39;m generally one of the fastest drivers in the race.  This week in my attempts at this track I&#39;ve had my wheel fail (see other posts here), a BSOD, and missing the start when I had to attend to my kids.&#160; I&#39;ve also been taken out a few times.&#160; The one time I&#39;ve had a clean run, or at least a run where everything that happened was my own fault, I&#39;ve come a close second.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-new-hampshire-with-south-oval</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-new-hampshire-with-south-oval</guid>
                    <pubDate>Mon, 19 September 2011 07:44:00 </pubDate>
                </item>
                <item>
                    <title>Logitech G27 wheel losing calibration and alignment</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/9/logitech-g27-wheel-losing-calibration-and-alignment</comments>
                    <description>My Logitech G27 turns 1 year old in two weeks and last night it started losing it’s calibration.&#160; I was playing iRacing and everything was correctly calibrated. After about 2 hours of play it started representing centre with the wheel turned about 10 degrees left.&#160; This shift left increased as I continued to play, making me turn increasing left to keep the car going straight ahead.  I tried recalibrating the wheel in the iRacing settings and that worked for about 2 minutes each time before it started to ‘slip’ again.&#160; I also tried it in the standard G27 calibration tool to make sure it wasn’t iRacing.&#160; I then tried unplugging my wheel, then turning the PC off at the wall then back on.&#160; All these various activities made no difference.  I resorted to the webs for my info.&#160; I found a thread on the logitech forums on this very problem with many angry posters.&#160; It seems this has happened to quite a few people and usually not long after purchase.&#160; At least I got 12 good months of frequent use from it.&#160; These other people took their wheels back for swapsies or just gave up altogether.  However, I wasn’t yet ready to part with another $400 to replace the wheel so I kept reading through the 20 or so pages. I found a post where a guy found that a screw on the motor that controls calibration can work itself loose and this will cause the problem. The remedy is as simple as tightening that screw, he said.&#160; I read through some more posts to see if others had tried this success, and they had.  Confidence up, I took to this thread and pulled my wheel apart, tightening the screw.&#160; All good now!  Update 16/09/2011: I got home tonight and played iRacing, doing a 20 lap race at Lime Rock.&#160; On lap 13 the problem returned.&#160; I noticed the instant it happened.&#160; I was in a moment of counter-steering and once it returned to straight ahead it was &#39;broken&#39;,&#160; This is consistent with the logitech thread reporting that the problem is most evident when fighting against the direction of the force feedback.&#160; I pulled the wheel apart and here&#39;s what I found:  Update 17/09/2011: I went and bought some superglue this morning.&#160; My thinking was to apply the glue to the cracks and hold with pliers until the glue set (a few minutes), then place the small wheel back on the FF calibration motor and reassemble, with testing.&#160; So that&#39;s what I did.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/9/logitech-g27-wheel-losing-calibration-and-alignment</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/9/logitech-g27-wheel-losing-calibration-and-alignment</guid>
                    <pubDate>Thu, 15 September 2011 19:11:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber Season 3 Week 5 – Virginia</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-season-3-week-5-–-virginia</comments>
                    <description>Man this track is difficult.&#160; Easily the most confusing track so far.    I found a number of these corners seriously challenging  NASCAR Bend : AKA Turn 3.&#160; This corner is seriously tricky.&#160; It looks pretty simple.&#160; You start braking once you clear the left kink that is turn 2.&#160; Problem I found was that I kept turning around (spinning) on the brakes. I spent a good hour or two just losing it nearly every time I went through that turn.&#160; Eventually I dialled in a LOT of front brake bias to cure the snap oversteer on entry.&#160; Also coasting after straight after turn 2 and before gently braking helped.&#160; Definitely no hard or jerky braking here.  Left Hook :&#160; Also moderately tricky, as it’s easy to understeer straight off the road at turn 5, after carrying too much speed into turn 4.  Up Hill :&#160; I actually got a hold of this one pretty early on and just hugged the inside in third gear.  Turn 8,9,10: It took a while for me to get the line through here but once I did it was all good.&#160; Mild throttle.  Roller Coaster : This was another of the corners that took me a while to find a good braking marker.&#160; I was generally entering too fast and shooting off down the grass.  Hog Pen :&#160; Damn this was tough.&#160; The trick is to not select 5th.&#160; Let the engine bounce off the limiter in 4th before braking just before you go over the hill and down into the turn.&#160; It’s imperative that this corner is taken well, so the car can carry maximum speed down the straight.  &#160;  I had a problem with my PC that meant I couldn’t do a race at this track.&#160; This is probably a good thing because I’m sure it would have ended up in tears.&#160; Much like the week 7 races at Lime Rock.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-season-3-week-5-–-virginia</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-season-3-week-5-–-virginia</guid>
                    <pubDate>Mon, 12 September 2011 07:12:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber Season 3 week 6 Lime Rock</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-season-3-week-6-lime-rock</comments>
                    <description>This should be easy, right? Everyone has raced at Lime Rock. It&#39;s one of only two circuits raced in the mandatory Rookie series    Well it should be easy but I found it anything but.&#160; Probably because I, like everyone else is initially super familiar with lapping this track in the similarly performing (in terms of lap times) Mazda MX-5. The braking points aren’t really different, but the inherent instability in the Skip Barber, coupled with the dynamics of the New Tyre Model make this a challenging track.&#160; My best lap around here is a 59.1 and I was shocked when the best I could manage was a mid 1:00.&#160; I thought I’d only ever driven the Skip Barber with the NTM, but that was definitely an OTM time because I’d previously only done a few laps around this circuit.  I did four races at Lime Rock and all four were abject failures.&#160; I didn’t post a qualifying time at any stage so I always started near the back end of the grid.&#160; At my last race I started 12th and last and was up to 4th by the midway point, before losing it into a wall.&#160; I’d had off’s in that race earlier, but that wall slamming event ruined my race chances, and the wall featured in all my other races too.&#160; The result, my SR dropped from C-3.39 to C 2.51, and my iRating is down from the mid 1700’s to 1548.&#160; I reckon I averaged 15 incidents a race.&#160; What a noob and easily my worst week since my first week of iRacing.  In my final practice session I got my lap times down to 59.6’s, which is with the other fast boys and placed me 4th in that session (from 21 runners).&#160; In the previous session I’d managed a 59.9 and placed 8th out of 31 runners.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-season-3-week-6-lime-rock</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/9/skip-barber-season-3-week-6-lime-rock</guid>
                    <pubDate>Mon, 12 September 2011 04:53:00 </pubDate>
                </item>
                <item>
                    <title>Supplements, they do work</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/9/supplements,-they-do-work</comments>
                    <description>Supplements.&#160; I&#39;ve been taking them since I first got serious about adding muscle mass back in the mid 90&#39;s when I read Arnold recommended a couple of basic animo acids should be part of every body builders diet.&#160; Then in 1998, I started out with a 4kg tub of cholocate flavoured &quot;Cheif&quot; protein powder, at about 70% protein.&#160; I got onto Creatine Monohydrate when it first become mainstream, back in probably 2000, though I found I didn&#39;t seem to get much benefit except from placebo, so I switched back to a higher caffiene intake to give me my energy boost.&#160; For nearly 5 years I operated on a &quot;normal diet&quot; of heaps of chicken breast for lunch and steaks for dinner, supplemented with protein shakes to get my 220g of protein a day.  By mid 2000&#39;s my mother&#39;s new husband, a former world powerlifting champion suggested I try HMB on top of the ZMA and L-Glutamine I&#39;d been on a year or two prior.  When I toned down in 2008 I stopped all supplements and heavy training.&#160; The extra calories in shakes meant it was too hard to stay under my 1500 calorie per day limit.&#160; Once I&#39;d got down to my goal weight by mid 2008 I hit the protein shakes again, this time with water, but no other supplements than that and caffiene.    In maybe 20 09/2010 The bloke at Ada St supplements at Taringa put me onto Urban Muscle&#39;s  Incel  product, which supposedly gave more energy boost that anything legally on the market.&#160; At least it worked better than creatine (and tasted good :)).&#160; I&#39;ve been using that since and it&#39;s quite good.  Last friday I went looking for a endurance product (as well as to replish my protein and incel stocks) and instead ended up walking out with Urban Muscle&#39;s new post workout formula,  Ressurect , which contains L-Glutamine, HMB, BCAA, L-Lucene, Taurine and other good stuff.&#160; All of which I&#39;ve taken individually over the years.&#160; Still, I was doubtful but I thought I&#39;d give it a try.&#160; I started taking the recommend amount that evening and have done every day since.  Monday afternoon was my usual Chest/Tri workout and I&#39;ve been slowly working back up to my best and on Monday night I equalled my previous best weight (with slightly less reps though) of 140kg for 8 reps unassisted and 2 reps assisted.&#160; It&#39;s been probably 7 or 8 years since I&#39;ve done that.&#160; Tuesday (today) I awoke barely sore and I went for my usual Tuesday bike ride at lunch time.&#160; Typically this ride is a killer, as my triceps and chest are so wasted I can&#39;t hold myself up after 15kms.&#160; Not today.&#160; I was at 33km/hr average by the halfway mark just before the Corso (a P.B) but it dropped to 30.5 km/hr when we slowed for a small period to see if a fellow rider had a bike problem.&#160; I ended the ride with an average of 29.5 km/hr, faster than I&#39;ve ever gone before.&#160; My previous best was 28.8 km/hr, set pretty much 12 months ago. I&#39;d got within a few tenths of a km/hr in the recent weeks but nothing like this difference.  While it&#39;s still early days, so far I&#39;m 2 from 2 workouts and it&#39;s late Tuesday night and my chest isn&#39;t hurting at all. Usually it&#39;s completely locked up by now.  Urban Muscle&#39;s Ressurect gets my vote!</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/9/supplements,-they-do-work</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/9/supplements,-they-do-work</guid>
                    <pubDate>Wed, 07 September 2011 08:01:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber 2000 at Mid Ohio</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-2000-at-mid-ohio</comments>
                    <description>I’m a bit late with my initial Mid Ohio thoughts.&#160; Being on holiday this week I grabbed the track on Tuesday, when it switched.&#160; I love learning new tracks.&#160; It’s a great feeling to take whole seconds of your previous time as you find braking points and sort the line out, then tenths as the setup is honed.  &#160;    &#160;  Mid Ohio is an undulating track with cambered corners, both off and on camber, making for some very interesting driving.&#160; It’s perhaps the most challenging for me of all the tracks I’ve raced so far because of this.&#160; Include the behaviour of the Skippy and it’s even more interesting.  After 2 hours of practice I’d got my time down to the low 1:40.3’s and when I returned later that night I dipped down to 1:39.7’s.&#160; For comparison, the fastest times I’ve seen are 1:37.7’s but that’s only by the aliens, most others that are “competitive” are scattered between mid 1:38’s and 1:39’s.&#160;&#160;&#160; My current qualifying time set on Wednesday night is 1:39.305, which usually puts me about 3rd or 4th on the grid.&#160; From there I can hold my own for a few laps but half the time I go backwards.&#160; If I can stay spin free I usually finish up anywhere from 3rd to 5th.  &#160;    Turn 1 doesn’t look like much but a lot of speed can be gained or lost here  &#160;    Turn 4: Getting the braking point right for this corner can make a lot of time  Turn 4 is one of my most hated corners.&#160; I approach it with fear (as I do most turn on Mid Ohio) every lap and I often find myself accelerating up to the apex, having braked too early.&#160; I get my braking started at about 175m, but I’ve seen others 50m later.&#160; Don’t know how…  &#160;    Turn 12 is probably the biggest time sink  Turn 12 is the worst for wasting time.&#160; I can easily lose half a second on incorrect entry. This turn is made extra difficult because it’s another turn with an unsighted apex when you turn in. There’s a few of these at Mid Ohio, but in my opinion this is the worst.&#160; Turn 12 is a blind right hander, hidden behind the crest of a hill (crest seen in the above image).&#160; Brake slightly too late and you understeer off the track as the corner tightens for a very late apex.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-2000-at-mid-ohio</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-2000-at-mid-ohio</guid>
                    <pubDate>Sat, 27 August 2011 01:13:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber at Suzuka</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-at-suzuka</comments>
                    <description>Race three of Season 3 in iRacing’s Skip Barber series is at the mighty Suzuka circuit in Japan, using the same track configuration as the Formula 1 cars.&#160; Awesome!&#160; I’m familiar with this track from my adventures in F1 2010 there, as little as two weeks ago, when I finished a comfortable second place.&#160; I love the esses at the circuit.&#160; It’s pure smooth driving and once you get the line, your absolutely fly through there.&#160; I took the same line in the SBF2000 and was like greased lightening through that sector.    &#160;  Suzuka Circuit Map  &#160;  I did a practice session with Flouncy from atomic (and fellow F1 2010 competitor), who had already done a few races at the circuit, so he served as a benchmark.&#160; I got my times down to a 2:32, while he was doing 2:31’s, and had said that most of the pack was doing 2:30’s.&#160; Taking his advice that racing was the best experience, and going against my trend to do lots of practice, I entered the 1:45pm race.&#160; He and I were in different races on account of my considerably lower iRating.&#160; I was ranked 6th and started 8th.&#160; I forget to put it in first gear on the line, so when the pack took off I just sat there revving my engine.&#160; I was 12th when I got away.  I had no expectations other than I expected to do poorly, so I just circulated at my own pace, watching cars in front of me spin and take themselves and others, out.&#160; I got up into 2nd and was catching 1st, at about 0.5s a lap average, and pulling away from 3rd.&#160; It wasn’t enough to get the win at the flag but it was a satisfying drive.    Esses: My favourite sequence of turns in iRacing so far.  &#160;  What impressed me more was how fun it was to drive a “real” track like Suzuka in a real simulator.&#160; Suzuka in iRacing looks so much better than F1 2010.&#160; Maybe it was because I was going slower.&#160; Maybe it was the triple monitors. I’m not sure.&#160; The Skippy felt magic too.&#160; I could slide it whenever I wanted and hold and correct the slide.&#160; It was so predictable.    Exiting Spoon for the straight before 130R  &#160;  After the race I jumped straight into another practice session and immediately started banging out laps in the mid 2:30’s, 2:30.542 to be exact. Nice and 4th fastest of that session.&#160; Not bad for only having been on the track less than 90 minutes.  I raced again later in the day and scored another second place, proving it wasn’t a fluke.&#160; The only difference being I had way more incidents (9 versus 0) that time. I think it was because I had expectations of being competitive, so I tried harder to be faster, and that led to mistakes.  Suzuka is easily my favourite track so far.&#160; The two tracks you get in the bas package (Lime Rock and Okayama) don’t come anywhere near it. Road Atlanta was fun, but it’s nothing like Suzuka.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-at-suzuka</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-at-suzuka</guid>
                    <pubDate>Mon, 22 August 2011 19:35:00 </pubDate>
                </item>
                <item>
                    <title>Weightlifters diary day #1233049</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/weightlifters-diary-day-1233049</comments>
                    <description>Ok, it’s probably not been that long, but after nearly 20 years, I reckon it’s not far off it.  I haven’t blogged about my weight training for a long while, but it’s still going on every 2nd day.&#160; My gym re-opened in June, after being caught in the floods in January, and I’ve been slowly returning to form, and more quickly putting the kilos on me (so say the scales).  I started out lifting about 50% of my usual weight, as measured by the typical big compound exercises: bench press, dead lifts, squats, bent over rows.&#160; Bench press is pretty much the yard-stick though, as it’s always the one people will ask about and most impressed with&#160;.  Many of the other gym goers found another gym in the interim and I’m sure many of them think I’m on steroids, given how quickly I went from reps of 90kg, and about equal with some of the other “big” guys in the gym to the last couple of weeks and especially today, when I had to realistically decide between my working sets (8 reps) of 137.5kg and 140kg.&#160; In the end I went with 137.5kg because my previous set of 10 reps I’d managed at 135kg without hurting or even felling the strain, but 140kg … I’ve never done that without a spotter (well, not sets of anyway).  I’m also happy with tri’s too, managed to increase my weights and reps over what I did last week.&#160; I didn’t ride today and I think that extra energy helped in the gym, that and with my anxiety disorder and general social discomfort, whenever the gym is busy, I’m extra stressed and tense, and I lift more (I swear it’s not because I want to show off.&#160; Well maybe that a little bit… )  &#160;    &#160;  Last year I was caught up in maintaining a weight of around 90kg and that meant less calories. Much less. Now I’m focussed on just smashing the weights and growing, I’m taking my protein shakes with milk, not water, and rather than have a salad for lunch, I’m having pasta bake.&#160; So about 1000 calories more a day.&#160; The result is I’m up nearly 8 kgs on my what I start at the end of May, early June, yet I’m still on the same hole in my belt.&#160; I think I’d be even heavier if I was still working legs.&#160; My legs respond the best, so I positive I’d be over the 100kg benchmark again.  And to think I was considering trying to shed down to a lean 85kg.  My weights / workout this afternoon was, in order I did them,     1. Flat Bench Press    20 reps @ 60kg  15 reps @ 100kg  12 reps @120kg  10 reps @ 135kg  8 reps @ 137.5kg (x 2 sets)      2. Incline Bench Press    10 reps @ 100kg (x 4 sets)      3. Incline flies    12 reps @ 30kg dumbbells  10 reps @ 32.5kg dumbbells (x 3 sets)      4. Seated Tri-cep extensions    12 reps @ 45kg dumbbells (x4 sets)      5. Tricep pulldowns    10 reps @ 50kg (x3 sets – all the weights on the pin weight machine)      6. Dips (body weight)    20 reps @ 3 sets</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/weightlifters-diary-day-1233049</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/weightlifters-diary-day-1233049</guid>
                    <pubDate>Tue, 16 August 2011 05:07:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber Season 3 2011 Road Atlanta Race</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-season-3-2011-road-atlanta-race</comments>
                    <description>I’ve just finished my one race for the week on Road Atlanta and it was both a disappointment and a success.&#160; Firstly, I started 10th on the grid, in last place.&#160; I didn’t set a qualifying time for this race and my rank was 9th; rank 10 started the last of the qualifiers.  By first corner I was into 8th and I gained another spot before the halfway point of the first lap.&#160; By the end of lap two I was into fourth after two cars made contact, and I’d passed a few more.&#160; I was holding 4th well, ever so slightly catching 3rd and pulling away from 5th when I thought I’d ‘drop the hammer’, to borrow a phrase from Col Trickett in Days of Thunder.&#160; What a mistake.&#160; I dropped it alright, right off the next corner and into into a wall.  I’d been so careful up to this point at lap 6.&#160; I felt I was driving well within myself, braking early and getting on the accelerator later, trying to minimise the risk.&#160; I’d also had 0 incidents.&#160; I lost 2 laps in the tow back to the pits and the repair.&#160; I came back out in second last, in front of the car ranked #7.&#160; I was also just in front of the car I was chasing for 3rd and the car that was behind me.&#160; I let them both through, as racing etiquette dictates but I didn’t let them out of my sight.  I followed the 5th placer closely for the rest of the race, breaking early to avoid him, as I was easily faster.  The lesson I learnt was don’t push hard.&#160; It’s not worth the crashing.&#160; At least my iRating has dropped, so I&#39;ll be in a slower group next time.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-season-3-2011-road-atlanta-race</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-season-3-2011-road-atlanta-race</guid>
                    <pubDate>Mon, 15 August 2011 06:35:00 </pubDate>
                </item>
                <item>
                    <title>Saturday Skip Barber Practice at Road Atlanta</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/saturday-skip-barber-practice-at-road-atlanta</comments>
                    <description>It’s Saturday and that means practice day and hopefully only a few refinements on car setup.&#160; I ended last night practice with a 1:41.1 best in practice, but I felt there was more to come.&#160; Over the day I did about 2 to 3 hours of practice and managed to braking points for all the corners that I’m happy with.&#160; I’d mostly struggled to get a line and a braking point for turns 1, 2, and 3, losing a number of tenths of a second through turn one and the same through turn 3, when I managed to stay on the track.    Turn 1 at Road Atlanta  &#160;  This time I got down to 1:39.742, but more tellingly was my average lap time, 1:40.287, easily the fastest average lap time and over the most laps.&#160; I think there’s a few more tenths in my too, just on lines and breaking points alone.&#160; Considering my optimal lap time is now 1:39.2s</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/saturday-skip-barber-practice-at-road-atlanta</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/saturday-skip-barber-practice-at-road-atlanta</guid>
                    <pubDate>Sun, 14 August 2011 09:54:00 </pubDate>
                </item>
                <item>
                    <title>Skip Barber Season 3 week 2 Road Atlanta</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-season-3-week-2-road-atlanta</comments>
                    <description>After the disappointment of week 1 in the Skip Barber series, where in both races I competed I was taken out through events no fault of my own, I was looking forward to week 2.  Week 2 of the Skip Barber series moves to Road Atlanta.&#160; I’ve not previously seen this circuit; not on television or in any other game.&#160; Like all other circuits I’ve raced on in iRacing to date, I’m going in blind.&#160; Whilst it’s probably not the wisest move, I immediately joined a formal practice session with other cars because I wanted to see what time I was aiming for.&#160; I had between 60 and 90 minutes to practice in and boy did it go fast.&#160; The fastest time was a 1:38.5, so I decided I would be satisfied if I could get down to 1:50 before my time was up.  Initially I struggled to put a lap together, coming off at most of the corners and breaking the car, a case of pushing too hard too early.&#160; The result was that I got ok at the first few corners out from the pits but that was it.&#160; Actually, I found turn 5 a real pain.&#160; It looks like it can be taken pretty fast but I was consistently coming unstuck and flying through the gravel trap.&#160; The tricky part is the braking for it.&#160; Tap the brakes aggressively and the tail comes round and you’re spinning like a top.&#160; So far the only way I could stay on the track and make the turn is to have the gentlest of touches on the brakes and roll through the turn, driving completely along the raised ripple strip at track out.    Turn 5: A tough ask for a noob  The exit of turn 5 leads to a pretty flat longish straight followed by a favourably cambered right hander.&#160; After a few laps I was able to get round in third with a few tyre screeches.&#160; This sets the car up for turn 7, which I’m currently taking in 2nd gear.  T7 precedes the longest straight at the circuit and it’s fun to blast down the hill into the breaking zone for turn 8, riding the ripple strip.&#160; Coming down the straight between T7 and T8 I find I&#39;m taking a different line to some of the faster guys.&#160; I can see the straight has a numbre of small ups and downs as you go along it.&#160; Surely the fastest line would be minimise the rises?&#160; I move out to the right hand side pretty soon after T7 and going by the constant gap timer thingo (press &#39;Tab&#39;) it appears to be the fasest line.    Turn 8 entry.&#160; Brake just after the 100m  There following corners are just as challenging, balancing throttle and entry speeds.  By the end of my first 90 minute session I had got my time down to 1:43.6, only 2.5s off what I at the moment consider to be fast enough to race competitively.&#160; My time put me 10th out of 19, and most the drivers above me were in the 1:40’s and 1:41’ss.   Update : I just did a 45 minute session (11/08/2011) and managed 1:41.141 . &#160;I was up even more than that on two occassions. &#160;One I was fast approaching a slightly slower guy (bean) and my haste to not get slowed up I tapped him on entry to turn 7. &#160;I didn&#39;t outbrake myself, he was just going slower than me. &#160;Then a few laps later I was approaching turn 8 and unusually stuff it, losing 0.4s through there. &#160;I think I&#39;m good for mid to high 1:40&#39;s with my current setup.  To get down to 1:41.1 I did change my SPO from +2 to 0.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-season-3-week-2-road-atlanta</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/skip-barber-season-3-week-2-road-atlanta</guid>
                    <pubDate>Thu, 11 August 2011 23:16:00 </pubDate>
                </item>
                <item>
                    <title>My social media pruning rules</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/my-social-media-pruning-rules</comments>
                    <description>I&#39;m a fan of privacy.&#160; Ok, you got me; I&#39;m not a total fan or I wouldn&#39;t be using social media at all .&#160; However, I do have a large family and a few good friends who live outside a distance that allows us to meet regularly.&#160; Facebook and Google+ are great for letting us keep in touch, or at least feel we&#39;re a part of each others lives.&#160; I&#39;m also open to new friends.&#160; What I&#39;m not a fan off is vanity friends. You know, the profiles that have a hundreds of friends.&#160; While I&#39;m pretty anti-social (or maybe I&#39;m just too busy to maintain many meaningful friendships) I don&#39;t see how a person can realistically and honestly have 600 friends.  Facebook has privacy controls, but I&#39;m still cautious about allowing people access to my information.&#160; Not just my profile information like my education, age, hometime etc, but information about me, as I am now; what I&#39;m doing, what I&#39;m feeling, if I&#39;m at home or on holiday.&#160; By hving facebook friends I that aren&#39;t real friends, I open myself up to problems associated with having an open profile.  Another thing is I just don&#39;t want to have to read through a bunch of random peoples live.&#160; I prefer meaningful connections only. So to keep it meaningful I&#39;ve got rules that are uesd to maintain my friends and contacts that I use for Facebook, Skype, MSN, Google+ (though it&#39;s circles changes things a bit :)), whatever else  1) If you ask, I&#39;ll add you as a friend / contact providing I&#39;ve previously communicated with you.&#160; E.g. we met IRL once, or I know you online from some of the communities I&#39;m a member of (eg atomic, atomicf1, iRacing)  2) If you don&#39;t talk to me or comment on my posts or articles etc.&#160; for 6 months, you get pruned.&#160; The only exception being is if you&#39;re a close relative: mother, wife, child, father, aunt, uncle, cousin, sister, brother, grandparent, or an &#39;in-law&#39; variant of those.&#160; There&#39;s also exceptions for exceptional circumstances, like you&#39;re in afganistan fighting terrorists, or exploring the antarctic.&#160; You&#39;re not expected to be accessing social networking tools.&#160; Though I&#39;ll delete you if I didn&#39;t know this :)  It doesn&#39;t matter if we were best buds for 10 years.&#160; If you don&#39;t communicate with me, and me with you, you get pruned. I don&#39;t need 600 friends.&#160; I need a few good ones.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/my-social-media-pruning-rules</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/my-social-media-pruning-rules</guid>
                    <pubDate>Thu, 11 August 2011 16:06:00 </pubDate>
                </item>
                <item>
                    <title>My first Skip Barber 2000 race day</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/my-first-skip-barber-2000-race-day</comments>
                    <description>What a disappointment.&#160; I got my times down to 1:47.5&#39;s.&#160; That&#39;s an ok time. Still nearly 2 seconds a lap off the fastest times I&#39;ve seen, but in the pack with the majority of the drivers.&#160; Enough to debut, I thought.  I started my first race (9:45pm start time) in last (out of 12th).&#160; As I started the warmup, I hit a few keys trying to get the gear indicator box to appear (it&#39;s &#39;P&#39;, BTW) and my pc decided to hibernate.&#160; Of course, no Windows based PC ever comes out of hibernation, so I had to hit the reset button and wait for the reload into Windows and then the game.&#160; I got back just in time for the race start (missed warmup) and took off and got the first corner only to discover my brake pedals was now mapped completely wrong, so whenever I touched the brakes the car would lock up.&#160; I managed to adapted ok but was 4seconds behind 2nd last by the time I&#39;d got used to it.&#160; Then my oldest daughter (4 years old) decided the gastro she&#39;d been threatening with would rear it&#39;s vomittous head all over herself and her bed.&#160; I had to stop where I was and clean it up.&#160; Totally killed my iRating and my Safety Rating.&#160; I was up to 1748 iRating and D 3.92 with my SR.  I had another crack at 11:45pm and qualified this time, in 9th position, with an 1:47.5.&#160; I made a good start and by the fourth corner I was up to&#160; 7th and closing.&#160; Unfortunately on the run into turn 5 (hairpin) some dude thought he would try and take me on the inside.&#160; I stayed left and gave him room but he completely messed it up and flipped my car over.&#160; He did appologise but it completely screwed my race :(.&#160; I had to get a tow back to the pits, which cost me over two and half a minutes and then the repair.&#160; By the time I got going again I was 3 laps down.&#160; At least I wasn&#39;t the only one to lose it, and I finished the race where I started.  In two races I&#39;d lost all the safety rating I worked to build up over the last week and most of the iRating I built up since Friday night, where I had had some good results in the MX5 car.  I&#39;ll admit I still need more practice time in the SBF2000 though.&#160; At least I&#39;ve recorded a result for this round...</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/my-first-skip-barber-2000-race-day</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/my-first-skip-barber-2000-race-day</guid>
                    <pubDate>Sun, 07 August 2011 19:56:00 </pubDate>
                </item>
                <item>
                    <title>Day 1 of practice for Season 3 in the Skip Barber 2000</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/8/day-1-of-practice-for-season-3-in-the-skip-barber-2000</comments>
                    <description>iRacing 2.0 debuted last night (my time), for the start of 2011 Season 3. Hopefully this will be my first full season of racing, having previously performed the odd race here and there and gone a few weeks between races on some occasions.&#160; I also recently graduate to a D class license (ok, maybe a month ago, but I’ve been focussing on F1 2010 until last week).&#160; As a D class license holder I can enter the Skip Barber series, of which iRacing says this: “One of the most basic, but purest racing cars in existence, the SBF2000 offers drivers the chance to both learn the dynamics of an uncompromised racing car and how to compete with other drivers in identical equipment”  It’s certainly a different animal to the road going based MX-5 Roadster I’ve raced in the rookie season and the only car I’ve raced to this point.&#160; The SBF2000, along with the new tyre model (NTM) introduced in iRacing 2.0, means braking points have to be spot on, the amount of brakes applied have to be exact, the amount of throttle mid corner has to be managed, and more. It’s not longer a case of just pointing the car at the apex and going for it.&#160; For me, there’s no controlled sliding of the car through the turns.&#160; The only way to get a fast lap time is to be slow into the corners and consistent on the throttle.  One thing is astoundingly clear, and I was warned: Getting good at iRacing requires wheel time.&#160; Sounds pretty obvious.&#160; I thought I’d just rock up and be moderately competitive.&#160; Wrong.&#160; These guys are all racing fans and all put in long hours behind the wheel in either real life motorsport or sims.&#160; I’ve been out of the sim game for years now and only recently returned with the release of F1 2010.  I expect to have more after hours time on my hands and I’ve committed to putting in more hours into iRacing.  Here’s the results from my very first official practice session for the Skip Barber series Season 3    My very first full lap was a 1:55 something.&#160; That took about 4 laps before I could get one full circuit without spinning.&#160; I joined the above session at the half way point, at about 4:15pm, with 45 mins on the clock.  My next session didn’t fair much better.    There were 31 recorded times in this session and we also had a Pro in our midst, setting 1:45’s!&#160;&#160; For those that don’t know the license classes go: Rookie –&amp;gt; D –&amp;gt; C –&amp;gt; B –&amp;gt; A –&amp;gt; Pro/WC .&#160; The number portion represents the “Safety Rating” (SR).&#160; License jumps are based purely on SR.&#160; Hitting 4.0 in Rookie results in automatic promotion to Class D. It’s not indicative of pure speed.&#160; For that, in a practice session, you need to compare Divisions.&#160; iRacing has 11 divisions, 1 being the top, and Rookie being the lowest (1 down to 10, and then Rookie).&#160; I’m sitting in Division 4, so that’s not too bad and up from Division 6 on Sunday.  I jumped on again after Dinner for about 30 mins pounding out the laps. I managed a whopping improvement of 0.08 seconds.&#160;Seems I’ve hit my “natural” level after about 2 hours of practice.&#160; Now I need to find at least another second to be as competitive as I want to be for the time being.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/8/day-1-of-practice-for-season-3-in-the-skip-barber-2000</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/8/day-1-of-practice-for-season-3-in-the-skip-barber-2000</guid>
                    <pubDate>Wed, 03 August 2011 18:02:00 </pubDate>
                </item>
                <item>
                    <title>Dynamics CRM 2011 Server Installation–The instance name must be the same as computer name</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/7/dynamics-crm-2011-server-installation–the-instance-name-must-be-the-same-as-computer-name</comments>
                    <description>I’ve spent the last couple of days installing Sitecore and Microsoft Dynamics CRM onto a VPS (Windows Server 2008 R2) and I came across a rather annoying problem.&#160; During the system checks part of the installation of Dynamics CRM 2011 I was getting the error message ‘The instance name must be the same as computer name’ .    I googled and came up with the following to rename the instance.  sp_dropserver &#39;oldservername&#39; go sp_addserver &#39;newservername&#39; go  &#160;  However, that didn’t resolve the issue.&#160; Eventually I run SQL Profile to see what was going on.    Running SELECT @@servername I could see the result was (NULL).&#160; A bit more googling and it turns out I need the following:  sp_addserver &#39;newservername&#39;, local  &#160;  And then a restart of the sql server service and all was good.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/7/dynamics-crm-2011-server-installation–the-instance-name-must-be-the-same-as-computer-name</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/7/dynamics-crm-2011-server-installation–the-instance-name-must-be-the-same-as-computer-name</guid>
                    <pubDate>Fri, 29 July 2011 19:05:00 </pubDate>
                </item>
                <item>
                    <title>Why not just do data driven status changes?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/7/why-not-just-do-data-driven-status-changes</comments>
                    <description>A good programmer friend of mine posed this question to me after reading my last two posts on this topic.&#160; I’m not sure what he means exactly (I don’t want to second guess what he means), so while I wait for his explanation I thought I’d show a version of how status changes are typically managed by people not understanding patterns, usually junior devs, or those coming from a procedural background.  Typically there would be what we call anaemic domain entities , where the domain entity is essentially a DTO (Data Transfer Object), all properties and no behaviour.&#160; Updating all properties are done from either the UI or the database and procedural code, often in an event handler such as a button click event will update the DTO before it’s persisted.  Here is what such an ‘entity’ would look like.  namespace WorkflowTest {   public class Document   {         public string PropertyOne { get; set; }     public string PropertyTwo { get; set; }     public string PropertyThree { get; set; }             public string PropertyFour { get; set; }         public int Status { get; set; }   } }  Hopefully it is immediately apparent that while an entity should always know it’s status, this entity relies entirely on calling code to understand when it’s state changes.&#160;&#160; This means that anywhere this entity is updated we need to duplicate the state change check code prior to assigning to the value to the entity. Even if we assume data binding we still need to repeat this check in every place.&#160; We could refactor to a method, like GetStatus and pass in the Document instance had have it return or set the Status in there.&#160; The GetStatus method would essentially be the same implementation as the private CheckStatus method on my first WorkflowDocument class, this method:  private void CheckStatus() {   // Rules for each state change are contained here.   // This is messy and should be refactored into Status objects.   if (Status == 0 &amp;amp;&amp;amp; !string.IsNullOrEmpty(PropertyOne)) {     Status = 1;   }      if (Status == 1 &amp;amp;&amp;amp; (!string.IsNullOrEmpty(PropertyTwo) &amp;amp;&amp;amp; !string.IsNullOrEmpty(PropertyThree)))   {     Status = 2;   }      if (Status == 2 &amp;amp;&amp;amp; (!string.IsNullOrEmpty(PropertyFour)))   {     Status = 3;   }   if (Status == 3 &amp;amp;&amp;amp; string.IsNullOrEmpty(PropertyFour)) {     Status = 2;   } }  &#160;  And we’re back where we started, except for having a method floating around in what will probably be a static helper class.&#160; Bad.&#160; If you remember back to the first post in this series, one of the benefits of the state pattern was reducing the complexity and readability problems of having ALL of your state change logic in this one method.&#160; While the above is fairly simple, where state transitions are more complex, such as can go from one state to many others in a variety of conditions, or when there are many many states, this method would become unwieldy and large. Larger than a single method should be, and really, because the each transition is dependant only on a particular state, not on the entire object in any state, the SRP (Single Responsibilty Pattern) says we should move the&#160; logic for each state transition to separate and individual classes, as in the State Pattern.  We would more than likely still need to store state in the database, particularly for reporting purposes, where&#160; something like Crystal Report may talk directly to the database.&#160; That though is a problem for the persistence strategy.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/7/why-not-just-do-data-driven-status-changes</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/7/why-not-just-do-data-driven-status-changes</guid>
                    <pubDate>Sat, 23 July 2011 05:54:00 </pubDate>
                </item>
                <item>
                    <title>Implementing document workflow with the state pattern</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/7/implementing-document-workflow-with-the-state-pattern</comments>
                    <description>Yesterday I blogged about managing basic document state/status&#160; as it moves through the concept of a workflow.&#160; In the first version of the workflow there were no workflow related classes.&#160; The workflow alluded to be the existence of a Status property on the object and that status object determined the step of the workflow and the status of the document, or Contract as in the case I was looking at.  Here I’ve continued my exploration by implementing&#160; a proper version of the State Pattern . My only deviation is that I’ve made the State internal to my document, rather than expose a public setter and getter.&#160; I think the only way it should be possible to change the state of the document is by calling the appropriate method.  I’ve altered my test case internal slightly to deal with the new API.&#160; Now, rather than have an objects State update automatically, the client calls a method to invoke the state change, once all properties have been set.&#160; If the object can validly transition to the desired state, it does so.&#160; If not, an exception is thrown.  using System; using NUnit.Framework; namespace WorkflowTest {   [TestFixture]   public class WorkflowDocumentFixture   {     [Test]     public void Initial_status_is_zero()     {       var document = new WorkflowDocument();       Assert.AreEqual(0, document.Status);     }         [Test]     public void Can_change_to_status_one()     {       var document = new WorkflowDocument() {PropertyOne = &quot;Value&quot;};       document.StatusOne();       Assert.AreEqual(1, document.Status);           }     [Test]     public void Can_change_to_status_two()     {       var document = GetStatusOneDocument();       document.PropertyTwo = &quot;Value&quot;;       document.PropertyThree = &quot;Value&quot;;       document.StatusTwo();       Assert.AreEqual(2, document.Status);     }     [Test]     public void Does_not_change_to_status_two_if_only_property_two_set()     {       var document = GetStatusOneDocument();       document.PropertyTwo = &quot;Value&quot;;       var ex = Assert.Throws&amp;lt;Exception&amp;gt;(() =&amp;gt; document.StatusTwo());       Assert.That(ex.Message, Is.EqualTo(&quot;Cannot change to Status Two.&quot;));     }     [Test]     public void Does_not_change_to_status_two_if_only_property_three_set()     {       var document = GetStatusOneDocument();       document.PropertyThree = &quot;Value&quot;;       var ex = Assert.Throws&amp;lt;Exception&amp;gt;(() =&amp;gt; document.StatusTwo());       Assert.That(ex.Message, Is.EqualTo(&quot;Cannot change to Status Two.&quot;));           }     [Test]     public void Can_change_to_status_three_from_two()     {       var document = GetStatusTwoDocument();       document.PropertyFour = &quot;Value&quot;;       Assert.AreEqual(2, document.Status);       document.StatusThree();       Assert.AreEqual(3, document.Status);     }     [Test]     public void Can_change_back_to_status_two_from_status_three()     {       var document = GetStatusThreeDocument();             Assert.AreEqual(3, document.Status);       document.PropertyFour = null;       document.StatusTwo();       Assert.AreEqual(2, document.Status);     }     [Test]     public void Cannot_change_back_to_status_one_from_status_two()     {       var document = GetStatusTwoDocument();       document.PropertyTwo = null;       var ex = Assert.Throws&amp;lt;Exception&amp;gt;(() =&amp;gt; document.StatusOne());       Assert.That(ex.Message, Is.EqualTo(&quot;Cannot change to Status One.&quot;));     }       private static WorkflowDocument GetStatusOneDocument()     {       return new StatusOneDocument();     }     private static WorkflowDocument GetStatusTwoDocument()     {       return new StatusTwoDocument();     }     private static WorkflowDocument GetStatusThreeDocument()     {       return new StatusThreeDocument();     }     private class StatusOneDocument : WorkflowDocument     {       public StatusOneDocument()       {         _state = new WorkflowDocumentStatusOne(this);         _propertyOne = &quot;Value&quot;;       }     }     private class StatusTwoDocument : StatusOneDocument     {       public StatusTwoDocument()       {         _state = new WorkflowDocumentStatusTwo(this);         _propertyTwo = &quot;Value&quot;;         _propertyThree = &quot;Value&quot;;       }     }     private class StatusThreeDocument : StatusTwoDocument     {       public StatusThreeDocument()       {         _state = new WorkflowDocumentStatusThree(this);         _propertyFour = &quot;Value&quot;;       }     }   } }  I have declared an interface IWorkFlowDocumentState which represents the state transitions.&#160; In a real world example these would be verbs such as Activate, BeginProcessing, and so on, which when successful would place the object in a status/state of “Active” or “Pending”, using my example from the previous blog entry.  Similar to my last blog entry, where the class for each state implemented the abstract base class, each different state class implements this interface.&#160; The methods representing the transition from that state to the state each method represents.&#160; In some case the state transition is invalid and it is in these cases exceptions are raised when the method is called.  Below are my concrete state classes.&#160; If you compare them to the yesterdays version, you’ll see they’re pretty similar.  using System; namespace WorkflowTest {   // OK this one is abstract :)   public abstract class WorkflowDocumentState : IWorkflowDocumentState   {     protected IWorkflowDocument _document;         protected WorkflowDocumentState(IWorkflowDocument document)     {       _document = document;     }     public abstract IWorkflowDocumentState StatusOne();     public abstract IWorkflowDocumentState StatusTwo();     public abstract IWorkflowDocumentState StatusThree();          }   public class WorkflowDocumentStatusZero : WorkflowDocumentState   {     public WorkflowDocumentStatusZero(IWorkflowDocument document) : base(document) { }     public override IWorkflowDocumentState StatusOne()     {       if (!string.IsNullOrEmpty(_document.PropertyOne)) {         return new WorkflowDocumentStatusOne(_document);       }       return this;     }     public override IWorkflowDocumentState StatusTwo()     {       throw new Exception(&quot;Cannot change to Status Two&quot;);     }     public override IWorkflowDocumentState StatusThree()     {       throw new Exception(&quot;Cannot change to Status Three&quot;);     }   }   public class WorkflowDocumentStatusOne : WorkflowDocumentState   {     public WorkflowDocumentStatusOne(IWorkflowDocument document) : base(document) { }     public override IWorkflowDocumentState StatusOne()     {             return this;     }     public override IWorkflowDocumentState StatusTwo()     {       if (!string.IsNullOrEmpty(_document.PropertyTwo) &amp;amp;&amp;amp; !string.IsNullOrEmpty(_document.PropertyThree)) {         return new WorkflowDocumentStatusTwo(_document);       }       throw new Exception(&quot;Cannot change to Status Two.&quot;);     }     public override IWorkflowDocumentState StatusThree()     {       throw new Exception(&quot;Cannot change to Status Three.&quot;);     }   }   public class WorkflowDocumentStatusTwo : WorkflowDocumentState   {     public WorkflowDocumentStatusTwo(IWorkflowDocument document) : base(document) { }     public override IWorkflowDocumentState StatusOne()     {       throw new Exception(&quot;Cannot change to Status One.&quot;);     }     public override IWorkflowDocumentState StatusTwo()     {             return this;     }     public override IWorkflowDocumentState StatusThree()     {       if (!string.IsNullOrEmpty(_document.PropertyFour))         return new WorkflowDocumentStatusThree(_document);       throw new Exception(&quot;Cannot change to Status Three.&quot;);     }   }   public class WorkflowDocumentStatusThree : WorkflowDocumentState   {     public WorkflowDocumentStatusThree(IWorkflowDocument document) : base(document) { }     public override IWorkflowDocumentState StatusOne()     {       throw new Exception(&quot;Cannot change to Status One.&quot;);     }     public override IWorkflowDocumentState StatusTwo()     {       if (string.IsNullOrEmpty(_document.PropertyFour)) {         return new WorkflowDocumentStatusTwo(_document);       }       throw new Exception(&quot;Cannot change to Status Two.&quot;);     }     public override IWorkflowDocumentState StatusThree()     {       return this;     }   } }  Now my revised Document class,  namespace WorkflowTest {   public interface IWorkflowDocument   {     int Status { get; }     string PropertyOne { get; set; }     string PropertyTwo { get; set; }     string PropertyThree { get; set; }     string PropertyFour { get; set; }     void StatusOne();     void StatusTwo();     void StatusThree();   }   public class WorkflowDocument : IWorkflowDocument   {     protected IWorkflowDocumentState _state;     public WorkflowDocument()     {       _state = new WorkflowDocumentStatusZero(this);     }     protected string _propertyOne;     public string PropertyOne     {       get { return _propertyOne; }       set       {         _propertyOne = value;               }     }     protected string _propertyTwo;     public string PropertyTwo     {       get { return _propertyTwo; }       set       {         _propertyTwo = value;               }     }     protected string _propertyThree;     public string PropertyThree     {       get { return _propertyThree; }       set       {         _propertyThree = value;               }     }     protected string _propertyFour;     public string PropertyFour     {       get { return _propertyFour; }       set       {         _propertyFour = value;               }     }     public int Status     {       get       {         if (_state is WorkflowDocumentStatusZero)           return 0;         if (_state is WorkflowDocumentStatusOne)           return 1;         if (_state is WorkflowDocumentStatusTwo)           return 2;                 return 3;               }     }     public void StatusOne()     {       _state = _state.StatusOne();     }     public void StatusTwo()     {       _state = _state.StatusTwo();           }     public void StatusThree()     {       _state = _state.StatusThree();     }   } }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/7/implementing-document-workflow-with-the-state-pattern</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/7/implementing-document-workflow-with-the-state-pattern</guid>
                    <pubDate>Sat, 23 July 2011 00:10:00 </pubDate>
                </item>
                <item>
                    <title>Modelling and programming entities that have state and participate in workflow</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/7/modelling-and-programming-entities-that-have-state-and-participate-in-workflow</comments>
                    <description>It’s a common enough problem:&#160; How do you programme your domain model to handle entities that participate in workflow and have a state (status).&#160; I’ve been mentally debating this one for a little bit now.&#160; My first thought was, “Easy, the State Pattern ”.&#160; I’ve not had to implement this pattern but I’ve read enough pattern books to know it exists and it sounded like a good fit.&#160; The entity in this case is a Contract for signing up for mobile phone airtime.  In the business world the Contract goes through the following workflow:   New: Data is being entered but nothing has been submitted or saved yet.&#160; Essentially the operator has selected the “New Contract” button.  Submitted: the Contract has been submitted for approval and activation but has not yet been looked at by an “approver”  Pending: The Contract has been opened by an approver and they are currently working on approving the Contract and hooking up airtime.  Active: The Contract has been approved and is connected to the network. A phone number is assigned and calls can be placed.  Suspended: The phone has been suspended with the network and can not be used on the network.&#160; Phone can revert to Active after suspension.  Closed: The Contract has been completed or terminated and is no longer connected to the network. Final state.   A Contract usually transitions through the states from 1 to 6.&#160; It can transition between 4 and 5 at any time and can transition to 6 from 2, 3, 4, or 5.  I was thinking of having methods for each status on the Contract, so I’d call Submit(), View(), Activate(), Suspend(), Close() to put the contract in various status.&#160; Perhaps I’d need to supply the various methods with all the data necessary to make the valid state change.&#160; E.G., the Activate method might take the phone number and other information that is mandatory for an active phone.&#160; But then I need the ability to change the properties of the phone number once it’s active (data binding) , so didn’t want to do that.  An entity status is defined by the combined state of all it’s properties, so to me it made sense to some how have the status change whenever all the appropriate properties had the correct values.&#160; This means checking the object status each time any property changes.  Now that I’ve mentioned Contracts, lets forget them, because they contain far too many properties and rules to easily (without confusing the reader) demonstrate my point.&#160; Instead of gone with WorkflowDocument, which is a generic document, such as a Contract document, that is used in any workflow.&#160; I’ve created a few basic properties, enough to illustrate the state change possibilities I’ve mentioned above, in my Contract case.  &#160;  public class WorkflowDocument : IWorkflowDocument {   protected string _propertyOne;   public string PropertyOne   {     get { return _propertyOne; }     set     {       _propertyOne = value;       CheckStatus();     }   }   protected string _propertyTwo;   public string PropertyTwo   {     get { return _propertyTwo; }     set     {       _propertyTwo = value;       CheckStatus();     }   }   protected string _propertyThree;   public string PropertyThree   {     get { return _propertyThree; }     set     {       _propertyThree = value;       CheckStatus();     }   }   protected string _propertyFour;   public string PropertyFour   {     get { return _propertyFour; }     set     {       _propertyFour = value;       CheckStatus();             }   }   public int Status { get; protected set; }   private void CheckStatus()   {     // Rules for each state change are contained here.     // This is messy and should be refactored into Status objects.     if (Status == 0 &amp;amp;&amp;amp; !string.IsNullOrEmpty(PropertyOne)) {       Status = 1;     }         if (Status == 1 &amp;amp;&amp;amp; (!string.IsNullOrEmpty(PropertyTwo) &amp;amp;&amp;amp; !string.IsNullOrEmpty(PropertyThree)))     {       Status = 2;     }         if (Status == 2 &amp;amp;&amp;amp; (!string.IsNullOrEmpty(PropertyFour)))     {       Status = 3;     }     if (Status == 3 &amp;amp;&amp;amp; string.IsNullOrEmpty(PropertyFour)) {       Status = 2;     }   } }  &#160;  In the above I’m calling CheckStatus whenever a value changes (I could be a little more intelligent and check the value has actually change not just been assigned).&#160; That’s how I would have implemented the behaviour without using the state pattern.&#160; in the above I wanted the Status to change as soon as the actual state of the object changed.  Here are the unit tests I’m using, to give an idea of the functionality and behaviour.  [TestFixture] public class WorkflowDocumentFixture {   [Test]   public void Initial_status_is_zero()   {     var document = new WorkflowDocument();     Assert.AreEqual(0, document.Status);   }     [Test]   public void Can_change_to_status_one()   {     var document = new WorkflowDocument() {PropertyOne = &quot;Value&quot;};     Assert.AreEqual(1, document.Status);         }   [Test]   public void Can_change_to_status_two()   {     var statusOneDocument = GetStatusOneDocument();     statusOneDocument.PropertyTwo = &quot;Value&quot;;     statusOneDocument.PropertyThree = &quot;Value&quot;;     Assert.AreEqual(2, statusOneDocument.Status);   }   [Test]   public void Does_not_change_to_status_two_if_only_property_two_set()   {     var statusOneDocument = GetStatusOneDocument();     statusOneDocument.PropertyTwo = &quot;Value&quot;;     Assert.AreEqual(1, statusOneDocument.Status);   }   [Test]   public void Does_not_change_to_status_two_if_only_property_three_set()   {     var statusOneDocument = GetStatusOneDocument();     statusOneDocument.PropertyThree = &quot;Value&quot;;     Assert.AreEqual(1, statusOneDocument.Status);   }   [Test]   public void Can_change_to_status_three()   {     var statusTwoDocument = GetStatusTwoDocument();     statusTwoDocument.PropertyFour = &quot;Value&quot;;     Assert.AreEqual(3, statusTwoDocument.Status);              }   [Test]   public void Can_change_back_to_status_two_from_status_three()   {     var document = GetStatusThreeDocument();         Assert.AreEqual(3, document.Status);     document.PropertyFour = null;     Assert.AreEqual(2, document.Status);   }   [Test]   public void Cannot_change_back_to_status_one_from_status_two()   {     var document = GetStatusTwoDocument();     document.PropertyTwo = null;     Assert.AreEqual(2, document.Status);   } }  &#160;  The state pattern goes a little further and isolates the state change logic into separate classes.&#160; In the next snippet I haven’t gone to the extreme of the state pattern proper, as I didn’t want to call entity.Activate() just yet. I wanted to have the state change when the property changed. That meant a few modifications to my WorkflowDocument class above.  public class WorkflowDocumentWithState : IWorkflowDocument {   protected WorkflowDocumentState _state;   public WorkflowDocumentWithState()   {     _state = new WorkflowDocumentStatusZero(this);   }   protected string _propertyOne;   public string PropertyOne   {     get { return _propertyOne; }     set     {       _propertyOne = value;       CheckStatus();     }   }   protected string _propertyTwo;   public string PropertyTwo   {     get { return _propertyTwo; }     set     {       _propertyTwo = value;       CheckStatus();     }   }   protected string _propertyThree;   public string PropertyThree   {     get { return _propertyThree; }     set     {       _propertyThree = value;       CheckStatus();     }   }   protected string _propertyFour;   public string PropertyFour   {     get { return _propertyFour; }     set     {       _propertyFour = value;       CheckStatus();     }   }   public int Status { get { return _state.Value; }   }   private void CheckStatus()   {     _state = _state.Validate();         } }  The two main changes are the addition and use of the WorkflowDocumentState class, which is an abstract based class for the various states the WorkflowDocument can be in.  public abstract class WorkflowDocumentState {   protected IWorkflowDocument _document;   protected int _value;    public int Value { get { return _value; } }     protected WorkflowDocumentState(IWorkflowDocument document)   {     _document = document;   }   public abstract WorkflowDocumentState Push(); }  &#160;  The Push method will be called to push the document through the workflow to the next the state.&#160; I’ve implemented concrete WorkflowDocumentState classes that contain the logic that was previous in my original CheckStatus method in WorkflowDocument.  public class WorkflowDocumentStatusZero : WorkflowDocumentState {   public WorkflowDocumentStatusZero(IWorkflowDocument document) : base(document)   {     _value = 0;   }   public override WorkflowDocumentState Push()   {         if (!string.IsNullOrEmpty(_document.PropertyOne)) {       return new WorkflowDocumentStatusOne(_document).Push();     }     return this;   } } public class WorkflowDocumentStatusOne : WorkflowDocumentState {   public WorkflowDocumentStatusOne(IWorkflowDocument document) : base(document)   {     _value = 1;   }   public override WorkflowDocumentState Push()   {     if (!string.IsNullOrEmpty(_document.PropertyTwo) &amp;amp;&amp;amp; !string.IsNullOrEmpty(_document.PropertyThree)) {       return new WorkflowDocumentStatusTwo(_document).Push();     }     return this;   } } public class WorkflowDocumentStatusTwo : WorkflowDocumentState {   public WorkflowDocumentStatusTwo(IWorkflowDocument document) : base(document)   {     _value = 2;   }   public override WorkflowDocumentState Push()   {     if (!string.IsNullOrEmpty(_document.PropertyFour)) {       return new WorkflowDocumentStatusThree(_document).Push();     }     return this;   } } public class WorkflowDocumentStatusThree : WorkflowDocumentState {   public WorkflowDocumentStatusThree(IWorkflowDocument document) : base(document)   {     _value = 3;   }   public override WorkflowDocumentState Push()   {     if (string.IsNullOrEmpty(_document.PropertyFour)) {       return new WorkflowDocumentStatusTwo(_document).Push();     }     return this;   } }  The pattern I’ve followed in each of my concrete Push methods is to check the properties of the entity to see it meets the conditions of a state change.&#160; Generally calling Push would check if the entity is ready to move along the workflow.&#160; If an entity meets the conditions of a state change, the new state is created and Push is called on it.&#160; this makes it possible to create a New object and fill in enough properties to have it seemlessly progress through to active, as would happen when an Activator in my Contract scenario is the person signing up new contracts.  One thing I should have included was the ability for a WorkflowDocumentState to store the state of the document when it first transitioned into that state. None of my basic tests required it though, so I did not.  I’m now going to investigate using a fully fledged state pattern, where I call entity.Activate() when I’m ready to active a contract.&#160; But for now, this is a step in the right direction.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/7/modelling-and-programming-entities-that-have-state-and-participate-in-workflow</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/7/modelling-and-programming-entities-that-have-state-and-participate-in-workflow</guid>
                    <pubDate>Fri, 22 July 2011 00:25:00 </pubDate>
                </item>
                <item>
                    <title>Outputting 5.1 sound from mkv’s via optical to an A/V receiver</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/7/outputting-51-sound-from-mkv’s-via-optical-to-an-av-receiver</comments>
                    <description>It seems every time I install shark007 codecs, clean or upgrade, for my windows 7 x64 media centre, I run into problems where my A/V receiver does not receive 5.1 sound from my PC.&#160; Sometimes I get this problem seemingly randomly.  My setup is I’m outputting via&#160; X-Fi PCI-e card’s optical output into my Phillips A/V Receiver.&#160;&#160; I spent $1600 on my mid level deck so I figure it’s going to have a better processor than my $50 sound card bought only to get optical out.  My latest problem came when some mkv’s played extremely slowly so I downloaded and installed the latest shark007 codecs (2.9.2 at the time).&#160; Then began the usual hours spent getting my receiver to show it was was receiving 5.1 sound from every appropriate source within Media Centre, my player of choice thanks to the fabulous Media Browser .  My latest problem was that 5.1 sound would come through correctly in Windows Media Player but not when playing mkv’s in Media Centre. (One would expect that both being Microsoft products, both would work!) I could not resolve my latest issue using the usual techniques.  After some playing around I got my sound to work with the following settings:  1. Select use pass-through (Digital Audio) in FFDshow S/PDIF pass-through    &#160;  2. Use FFDShow’s codec for all three options on the left side of the SWAP tab  3. Disable Microsoft’s Audio Decoder.    &#160;  &#160;  Pretty straight forward.&#160; I usually also pipe my sound through AC3Filter and set its audio to pass-through too. My results vary and it seemed I’d have different settings on each different configuration run to get the same results.&#160; Getting MKV to work is still a black art to me, but at least now I’ve recorded a working example, rather than relying on my memory each time I need to configure my sound.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/7/outputting-51-sound-from-mkv’s-via-optical-to-an-av-receiver</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/7/outputting-51-sound-from-mkv’s-via-optical-to-an-av-receiver</guid>
                    <pubDate>Wed, 20 July 2011 17:50:00 </pubDate>
                </item>
                <item>
                    <title>It&#39;s clear Red Bull favours Vettel</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/7/its-clear-red-bull-favours-vettel</comments>
                    <description>At the recent British GP Mark Webber started from pole but it was Sebastial Vettel who took the lead at turn 1.&#160; Both drove a solid race and in the closing stages it was clear Webber was considerably faster than Vettel, closing right up on him in the last few laps.&#160; At that point team boss Christian Horner issued a now legal team order for Webber to &quot;maintain the gap&quot; behind Vettel, which he ignored and closed right up to Vettel&#39;s gearbox.  A row erupted over both Webber ignoring his team order and the issuing of the team order itself.&#160; Christian Horner has defended his issuing of orders under the auspices of wanting to prevent the two cars coming together and taking each other out, as in Turkey last year.&#160; Since winning the constructors championship last year they (Red Bull) have found there is significant monetary reward for every place further up the order they finish, and have taken the approach that the Team is greater than the Driver.&#160; I can understand his logic, and to a large extent I agree with it.&#160; I also have no problem with team orders.&#160; F1 is a team sport and team orders are legal.  What I don&#39;t understand is Christian Horners reasoning, given the stated goal.&#160; Why didn&#39;t Horner order Vettel to move aside and let Webber through?&#160; Webber had the pole.&#160; Webber was clearly faster in the closing stages.&#160; If Vettel moved aside for Webber we would have the following:   Tthe Red Bulls still would have collected 33 points and finished 2-3.  Vettel&#39;s lead in the championship would barely be dented.  Red Bull and Horner would not be being lamblasted in the media, as they currently are.  Webber would not be dispirited thinking he now has no chance in the title.   &#160;  The only possible negative is the young german Vettel might be a little unhappy.&#160; Its&#39; pretty clear now (not like it wasn&#39;t) that Red Bull are doing a Ferrari and are focussed on Vettel.&#160; I get that. I do.&#160; But it&#39;s hypocritical from Horner and Red Bull, after their spraying of Ferrari over team orders last year.  See here for more</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/7/its-clear-red-bull-favours-vettel</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/7/its-clear-red-bull-favours-vettel</guid>
                    <pubDate>Tue, 12 July 2011 18:27:00 </pubDate>
                </item>
                <item>
                    <title>Using events in your domain model</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/6/using-events-in-your-domain-model</comments>
                    <description>Disclaimer : This isn&#39;t something I&#39;ve thought up. It&#39;s taken directly from Udi Dahan&#39;s Domain Events .&#160; This post is about me understanding what Udi is describing.  In the system I’m refactoring to a richer domain model (from a more “dumb” domain, where most logic is in the service layer) I want to raise domain events when something important happens in the domain.&#160; Prior to actually implementing this change in my codebase I needed to understand how the framework will work.&#160; To that end I&#39;ve made the following dummy application with one event that is raised and below I’ll attempt to explain the how and why it works.    Business objects wanting to raise domain events call the Raise method on the DomainEventDispatcher to notify the system that an event of interest has occurred.&#160; The business object passes a instance of IDomainEvent in the Raise method.  The DomainEventDispatcher uses the Castle Windsor IoC container to get all registered handlers of the event being raised and calls the Handle method on each.  To wire up events you implement the IHandles interface for each different action you want to occur when the the business object raises the event.&#160; Registering each IHandles implementation with IoC allows the event to be subscribed to.&#160; DomainEventDispatcher will query IoC to get all handlers for the raised event and call their Handle method.  In this simple exmaple I have an invoice that raises an event when the invoice is paid.&#160; In the real world this could be used to send out a thankyou email, updating other accouting software, or do whatever other action needs to be performed in this case.  public class Invoice {   public string InvoiceNumber { get; set; }   public decimal AmountOwing { get; set;}       public void MakePayment(decimal amount)   {     AmountOwing += amount;     DomainEventDispatcher.Raise(new InvoiceWasPaid() { InvoiceNumber = InvoiceNumber, PaymentAmount = amount});   } }  IDomainEvent doesn’t have any actions and is used to identify and restrict class to only those that are domain events.  public interface IDomainEvent { }  &#160;  InvoiceWasPaid is the event that occurs.&#160; It’s good form to name event as verbs past tense. This also indicates when the event took place.&#160; In thise case the invoice has already been paid.  &#160;  public class InvoiceWasPaid : IDomainEvent {   public string InvoiceNumber { get; set; }   public decimal PaymentAmount { get; set; } }  We now need to implement IHandles to perform the action that should occur once the Invoice has been paid.  public class InvoiceWasPaidHandler : IHandles&amp;lt;InvoiceWasPaid&amp;gt; {   public void Handle(InvoiceWasPaid args)   {     Console.WriteLine(&quot;{0:c} was paid on invoice &#39;{1}&#39;&quot;, args.PaymentAmount, args.InvoiceNumber);   } }  Finally we need to hook it all up with the DomainEventDispatcher class  public static class DomainEventDispatcher {           private static IWindsorContainer Container;   // Call this in your App startup, such as main() or in the Application_Start event of your webapp,   // after you have configured your IoC container   public static void SetContainer(IWindsorContainer container)   {     Container = container;   }   public static void Raise&amp;lt;T&amp;gt;(T args) where T : IDomainEvent   {     foreach(var handler in Container.GetAllInstances&amp;lt;IHandles&amp;lt;T&amp;gt;&amp;gt;()) {       handler.Handle(args);     }        } }  Last but not least in this sample, we have the stub to test it all.  class Program {   static void Main(string[] args)   {     // configure IoC container     IWindsorContainer container = new Container();     container.Register(Component.For&amp;lt;IHandles&amp;lt;InvoiceWasPaid&amp;gt;&amp;gt;().ImplementedBy&amp;lt;InvoiceWasPaidHandler&amp;gt;());     DomainEventDispatcher.SetContainer(container);      var invoice = new Invoice() {InvoiceNumber = &quot;70034&quot;, AmountOwing = 100M};     Console.WriteLine(&quot;Invoice &#39;{0}&#39; with amount owing of {1:c}&quot;, invoice.InvoiceNumber, invoice.AmountOwing);     Console.WriteLine(&quot;About to make payment of $50.00 on invoice &#39;{0}&#39;&quot;, invoice.InvoiceNumber);     invoice.MakePayment(50M);         Console.ReadLine();   } }  &#160;  When executed, this code will output the following:    You can see the in last line above that the event handler InvoiceWasPaidHandler.Handle was raised and executed when the call invoice.MakePayment(50M) method was called.  We can&#39;t test the above because DomainEventDispatcher is a static class.&#160; To get around this The DomainEventDisaptcher allows registering for callbacks.&#160; The callbacks are fired just after the event. This allows us to create a delegate in our test method.&#160; The updated version of DomainEventDispatcher looks like:  public static class DomainEventDispatcher {   [ThreadStatic]   private static List&amp;lt;Delegate&amp;gt; actions;   private static IWindsorContainer Container { get; set; }   public static void SetContainer(IWindsorContainer container)   {    Container = container;   }   public static void Register&amp;lt;T&amp;gt;(Action&amp;lt;T&amp;gt; callback) where T : IDomainEvent   {     if (actions == null) {       actions = new List&amp;lt;Delegate&amp;gt;();     }     actions.Add(callback);   }   public static void ClearCallbacks()   {     actions = null;   }   public static void Raise&amp;lt;T&amp;gt;(T args) where T : IDomainEvent   {     if (Container != null) {       foreach (var handler in Container.ResolveAll&amp;lt;IHandles&amp;lt;T&amp;gt;&amp;gt;()) {         handler.Handle(args);       }     }     if (actions != null) {       foreach (var action in actions)         if (action is Action&amp;lt;T&amp;gt;)           ((Action&amp;lt;T&amp;gt;) action)(args);             }   } }  Testing  Now when an event is raised any attached callbacks are also called. This allows for a unit test that looks like the following:  [TestFixture] public class InvoiceFixture {   [SetUp]   public void SetUp()   {     IWindsorContainer container = new WindsorContainer();     // Add event handlers to the container     container.Register(Component.For&amp;lt;IHandles&amp;lt;InvoiceWasPaid&amp;gt;&amp;gt;().ImplementedBy&amp;lt;InvoiceWasPaidHandler&amp;gt;());     DomainEventDispatcher.SetContainer(container);     DomainEventDispatcher.ClearCallbacks();   }   [Test]   public void Invoice_payment_raises_event()   {     var invoice = new Invoice() {AmountOwing = 50, InvoiceNumber = &quot;70043&quot;};     string invoiceNumber = &quot;&quot;;     decimal amount = 0;     DomainEventDispatcher.Register&amp;lt;InvoiceWasPaid&amp;gt;(x =&amp;gt;     {       invoiceNumber = x.InvoiceNumber;       amount = x.PaymentAmount;     });     invoice.MakePayment(50M);     Assert.AreEqual(50M, amount);     Assert.AreEqual(&quot;70043&quot;, invoiceNumber);   } }  I&#39;m not convinced its fantastic to have test only code in there, as I&#39;m not sure when we&#39;d use the callback in production. Regardless, my test passes, showing the event is being raised from the MakePayment() call.  &#160;  A little more on IoC  My container of choise is Castle Windsor. &#160;I prefer to use an installer to register my event handlers with the container, meaning I don&#39;t have explicity register each handler and I won&#39;t run the risk of forgetting to register one.  &#160;  public class DomainEventsInstaller : IWindsorInstaller {   public void Install(IWindsorContainer container, IConfigurationStore store)   {     container.Register(Classes.FromThisAssembly()                  .BasedOn(typeof(IHandles&amp;lt;&amp;gt;))                  .WithService.Base()                  .LifestyleTransient());         } }  I also like to keep my domain event handlers in a separate assembly to my domain model and my assembly for domain services. My model assembly contains the infrastructure to raise the domain event as well as the events themselves. But the subscribers to the event, the model doesn&#39;t really care about who else cares about the event, and to make the event handlers easiest to find, I keep then in their own assembly (usually something like Domain.Events)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/6/using-events-in-your-domain-model</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/6/using-events-in-your-domain-model</guid>
                    <pubDate>Thu, 30 June 2011 00:07:00 </pubDate>
                </item>
                <item>
                    <title>OCZ vertex 3 vs OCZ vertex 2 vs WD Black Sata 3</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/6/ocz-vertex-3-vs-ocz-vertex-2-vs-wd-black-sata-3</comments>
                    <description>I’ve just upgraded my system and this gives a good opportunity to compare the performance of these drives.&#160; My Vertex 2 is 10 months old and has been faultless, while the Vertex 3 and Black are brand new, bought on Friday.  I’ve installed these drives my new PC   Intel i7 2600K (stock speeds ATM)  16GB Corsair Vengence RAM (@1333)  Gigabyte GA-Z68-UD4-B3  Corsair HX850W PSU  *X-Fi Xtreme Gamer  *ATI HD 5850  *nVidia 8800 GTS (for extra monitor support)  *Pioneer DVD-R/W 212 (not important)  * Antec P182   * Indicates existing components.  I grabbed a copy of the trial version of HD Tune 4.61 for these test and change no default settings.  OCZ Vertex3 120GB (SATA3 interface)    I believe the Vertex3 is advertised with a 550MB/s maximum read, so 511 is OK and I’m happy with an average of 477.4 MB/s.  Purchase Price: AU$325  &#160;  OCZ Vertex2 120GB (SATA2 interface)    Again, this is around the results marketed.  Purchase Price AU$395 (I bought it as soon as it dipped below $400.&#160; About a month later it was &amp;lt; $300… )  &#160;  Western Digital Black 2TB (SATA3 Interface)    I didn’t believe the drop off at first, so I reran the test and got eh same minimum and maximum results (with a 1MB/s variation in burst rate).&#160; We expect that HDD’s are slower than SSD’s but the burst rate of the Western Digital completely shames both SSD’s.&#160; I’m not sure why this is, because the HD Tune help says The burst rate is the highest speed (in megabytes per second) at which data can be transferred from the drive interface (IDE or SCSI for example) to the operating system. Based on that, I’d expect the burst rate to always be higher than Maximum rate, which is what we see in the Western Digital.  For lols I ran the test on my HTPC, which is an old Pentium D 3.2GHZ (soon to be a Q6600  4GB DDR2 800) with 2GB DDR2 667 ram.&#160; That PC houses vanilla Wester digital 640GB 7200 rpm and two few month old Western Digital 2TB Green drives.  &#160;  Western Digital 640GB (SATA) 7200rpm    Interesting, so the burst rate is nearly half as much as the Black, and the average transfer rate is nearly half as fast.&#160;&#160; Cleary the Black is a better HDD than this 4 year old 640GB drive.&#160; No surprise.  Next up is the 2TB Green drive, a Sata2 drive plugged into a SATA interface (AFAIK that old Dell mobo doesn’t have Sata2 ports).&#160; The Green is the current generation as the new Black above, and only about 2-3 months old.&#160; It’s at complete opposite ends of the WD (marketing) spectrum though and is sold as an “Eco” drive.  &#160;  Western Digital Green 2TB (SATA interface)    Again we can see the drive is definitely slower than the Black, although it’s faster than the old 640GB drive.&#160; I think there’s also more cache in the Green than the 640GB.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/6/ocz-vertex-3-vs-ocz-vertex-2-vs-wd-black-sata-3</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/6/ocz-vertex-3-vs-ocz-vertex-2-vs-wd-black-sata-3</guid>
                    <pubDate>Sun, 26 June 2011 20:22:00 </pubDate>
                </item>
                <item>
                    <title>Why iracing is better than any game</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/6/why-iracing-is-better-than-any-game</comments>
                    <description>iRacing sells itself as a simulation and not a game.&#160; Before I subscribed to iRacing I thought that was just another marketing gimmick.&#160; Now I know that&#39;s not the case.  Last night at about 11:35pm I got my D class license and advanced out of Rookie, opening up a range of other cars and tracks, such as the Skip Barber 2000 car, on open wheeler.&#160; I did a few laps on that before I set about configure SofTH and triple screens and now that I&#39;ve got them runnng I cannot go back to a single monitor car game.    Anyway, I digress - but triple screens are waaaaayyy better :)  Last night I raced against Chaz Mostert a number of times and the dude was extremely fast, qualifying in 58.9s and my best is 59.6s.&#160; Every race he and I were in he led from start to finish and completely dominated, with about half the time no incidents.&#160; I average around 2-3 a race, which is pretty good, but to do that I don&#39;t push that hard.&#160; The last time I was up against that sort of speed it was Jake Fourace, who upon investigation is a young upcoming Formula Ford/Vee racer.&#160; I checked out Chaz&#39;s profile and found nothing so just thought maybe he was some exceptionally talented computer gamer who practiced heaps before joining any races.&#160; He&#39;s done less races than me...  A quick google and it turns out Chaz Mostert is a quickly rising start in the Konica V8&#39;s, the feeder series to Australia&#39;s main category, the V8 supercars.&#160; I feel better about being smashed now.  That&#39;s the beauty of iRacing.&#160; The racing is for the most part very clean and courtoeous and half the races I&#39;ve been in this year have been with proper professional race car drivers (or soon to be pro, if they&#39;re still in school lol).&#160; Especially now my irating is decent and my SR is high I don&#39;t get stuck with noobs who haven&#39;t yet learnt you don&#39;t win it on the first corner.  Makes it easy to walk away from the F1 championship I was &quot;competing&quot; in at www.atomicf1.com So much more rewarding now I&#39;m out of rookie.&#160; Hah that is until I find I&#39;m not fast enough to get any further up the ladder and find something eaiser :p</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/6/why-iracing-is-better-than-any-game</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/6/why-iracing-is-better-than-any-game</guid>
                    <pubDate>Mon, 20 June 2011 02:39:00 </pubDate>
                </item>
                <item>
                    <title>Auditing with Nhibernate</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/6/auditing-with-nhibernate</comments>
                    <description>I recently began the undertaking of converting the linq 2 sql data access layer of an application of mine to nhibernate (prior to linq 2 sql it was hand crafted sql through ado.net).&#160; I’m using fluentnhibernate to define my mappings.&#160;&#160; Part of the conversion is converting the auditing.  &#160;  My existing auditing table looks looks like the following:  CREATE TABLE [dbo].[Audit](   [AuditId] [int] IDENTITY(1,1) NOT NULL,   [AuditDate] [datetime] NOT NULL,   [User] [nvarchar](30) NOT NULL,   [TableName] [nvarchar](60) NOT NULL,   [FieldName] [nvarchar](60) NOT NULL,   [KeyId] [int] NOT NULL,   [OldValue] [nvarchar](80) NULL,   [NewValue] [nvarchar](80) NULL )  &#160;  plus the normal indexes and constraints.&#160; This code is in production so I don’t want to change the schema.&#160; The basics of it is I log every field change, logging the date and time the field changed and the old and new values.  To achieve this in linq 2 sql I was subscribing to the SubmitChanges event on the DataContext and I registered each linq 2 sql entity of interest to auditing.  &#160;  public override void SubmitChanges(ConflictMode failureMode) {   try   {     this.Audit&amp;lt;Contract&amp;gt;(c =&amp;gt; c.ContractId);     this.Audit&amp;lt;Account&amp;gt;(a =&amp;gt; a.AccountId);     this.Audit&amp;lt;Company&amp;gt;(c =&amp;gt; c.CompanyId);     this.Audit&amp;lt;Contact&amp;gt;(c =&amp;gt; c.ContactId);     this.Audit&amp;lt;Network&amp;gt;(n =&amp;gt; n.NetworkId);     this.Audit&amp;lt;NetworkTariff&amp;gt;(nt =&amp;gt; nt.NetworkTariffId);     this.Audit&amp;lt;Plan&amp;gt;(p =&amp;gt; p.PlanId);     this.Audit&amp;lt;Tariff&amp;gt;(t =&amp;gt; t.TariffId);     this.Audit&amp;lt;Agent&amp;gt;(a =&amp;gt; a.AgentId);     base.SubmitChanges(failureMode);   }   catch (Exception ex)   {     LoggingUtility.LogException(ex);     throw;   }       }  I also had my own AuditLogger class, where the Audit&amp;lt;TEntity&amp;gt;() method did the heavy lifting, finding the properties that were updated, getting their old and new values and persisting to the audit table (you can still see some of the old ado.net code in there!)  &#160;  public static class AuditLoggingUtility {   static readonly SqlConnection cn = new SqlConnection(ConfigItems.ConnectionString);   static readonly SqlCommand cmd;   static AuditLoggingUtility()   {     cmd = cn.CreateCommand();     cmd.CommandText = &quot;INSERT INTO Audit (AuditDate, [User], TableName, FieldName, KeyId, OldValue, NewValue) VALUES (@AuditDate, @User, @TableName, @FieldName, @KeyId, @OldValue, @NewValue)&quot;;           cmd.Parameters.Add(new SqlParameter(&quot;@AuditDate&quot;, SqlDbType.DateTime));     cmd.Parameters.Add(new SqlParameter(&quot;@User&quot;, SqlDbType.NVarChar, 30));     cmd.Parameters.Add(new SqlParameter(&quot;@TableName&quot;, SqlDbType.NVarChar, 60));     cmd.Parameters.Add(new SqlParameter(&quot;@FieldName&quot;, SqlDbType.NVarChar, 60));     cmd.Parameters.Add(new SqlParameter(&quot;@KeyId&quot;, SqlDbType.Int));     cmd.Parameters.Add(new SqlParameter(&quot;@OldValue&quot;, SqlDbType.NVarChar, 80));     cmd.Parameters.Add(new SqlParameter(&quot;@NewValue&quot;, SqlDbType.NVarChar, 80));     cmd.CommandType = CommandType.Text;   }   public static bool WriteAuditLog(string user, string tableName, string fieldName, int keyId, string oldValue, string newValue)   {     cmd.Parameters[&quot;@AuditDate&quot;].Value = DateTime.Now;     cmd.Parameters[&quot;@User&quot;].Value = user;     cmd.Parameters[&quot;@TableName&quot;].Value = tableName;     cmd.Parameters[&quot;@FieldName&quot;].Value = fieldName;     cmd.Parameters[&quot;@KeyId&quot;].Value = keyId;     cmd.Parameters[&quot;@OldValue&quot;].Value = oldValue;     cmd.Parameters[&quot;@NewValue&quot;].Value = newValue;     var rows = 0;     try     {       cn.Open();       rows = cmd.ExecuteNonQuery();     }     catch (Exception ex)     {       System.Diagnostics.Trace.WriteLine(ex.Message);     }     finally     {       cn.Close();     }     return rows == 1;   } } public static class AuditExtensions {   public static bool HasAttribute(this Type t, Type attrType)   {     return t.GetCustomAttributes(attrType, true) != null;   }   public static bool HasAttribute(this PropertyInfo pi, Type attrType)   {     return pi.GetCustomAttributes(attrType, false) != null;   }   private static string GetPropertyValue(PropertyInfo pi, object input)   {     var tmp = pi.GetValue(input, null);     return (tmp == null) ? string.Empty : tmp.ToString();   }   private static string GetPropertyValue(object input)   {     return (input == null) ? &quot;&amp;lt;null&amp;gt;&quot; : input.ToString();   }   public static void Audit&amp;lt;TEntity&amp;gt;(this DataContext dc, Func&amp;lt;TEntity, int&amp;gt; tableKeySelector) where TEntity : class   {           var table = dc.GetTable&amp;lt;TEntity&amp;gt;();     string tableName = dc.Mapping.GetTable(typeof(TEntity)).TableName;         // Remove the &#39;dbo.&#39; prefix     if (tableName.IndexOf(&quot;dbo.&quot;) == 0)     {       tableName = tableName.Substring(4);     }     // Audit the items that have been modified.     var updates = dc.GetChangeSet().Updates.OfType&amp;lt;TEntity&amp;gt;();     foreach (var item in updates)     {               var key = tableKeySelector.Invoke(item);       var mmi = table.GetModifiedMembers(item);       foreach (var mi in mmi)       {         var user = System.Threading.Thread.CurrentPrincipal.Identity.Name;         AuditLoggingUtility.WriteAuditLog(user, tableName, mi.Member.Name, key, GetPropertyValue(mi.OriginalValue), GetPropertyValue(mi.CurrentValue));       }     }   } }    I figured Nhibernate should have an equivalent, and after a little reading, it turns out I was right. Nhibernate 2.0 has events, and the IPreUpdateEventListener is the interface of interest.&#160; Nhforge explains audit logging where the auditing is happening in the same table.&#160; Not quite what I want.&#160; Check out the helpful article on that site or the background information on this event and how it works.  &#160;  My class is based on what I read in that article:  public class AuditEventListener : IPreUpdateEventListener {   public bool OnPreUpdate(PreUpdateEvent @event)   {        var entity = @event.Entity as EntityBase;     if (entity == null) return false;         for (var index = 0; index &amp;lt; @event.State.Length; index++) {       if (@event.State[index] != @event.OldState[index]) {         var audit = new Audit(Thread.CurrentPrincipal.Identity.Name, entity.GetEntityName(),                    @event.Persister.PropertyNames[index], entity.Id,                    @event.OldState[index].ToString(), @event.State[index].ToString());         @event.Session.Save(audit);       }             }         return false;   }     }  &#160;  What I am doing is iterating through the State and OldState properties and writting to the Audit table where they’re different.&#160;&#160; It’s nice how the two different ORM’s are similar.&#160; Though there’s not a lot that should be different.  The other main step is letting the Nhibernate configuration know about the event.&#160; For that we need to call SetListener on the Configuration object.&#160; Note I am using FluentNhibernate and my configuration below is for my unit test assembly which uses SQLite  private static ISessionFactory CreateSessionFactory() {   return Fluently.Configure()     .Database(SQLiteConfiguration.Standard.InMemory().ShowSql())     .Mappings(m =&amp;gt; m.FluentMappings.AddFromAssemblyOf&amp;lt;CompanyMap&amp;gt;())     .ExposeConfiguration(cfg =&amp;gt; { cfg.SetListener(ListenerType.PreUpdate, new AuditEventListener()); _configuration = cfg; })     .BuildSessionFactory(); }  There is a difference between the two strategies though. Where Linq 2 Sql logs changes to the database tables, Nhibernate logs changes to the entities. Take the follow entity as example  public class Company : EntityBase {   public Company()   {     Address = Address.Empty;     PostalAddress = Address.Empty;   }   public virtual string CompanyName { get; set; }   public virtual string ABN { get; set; }   public virtual string ACN { get; set; }   public virtual Address Address { get; set; }   public virtual Address PostalAddress { get; set; }         public virtual MasterAccount CreateAccount()   {     return new MasterAccount(this);   }   public override System.Collections.Generic.IEnumerable GetRuleViolations()   {     if (string.IsNullOrEmpty(CompanyName))       yield return new RuleViolation(&quot;Company Name required&quot;, &quot;CompanyName&quot;);     // TODO check for valid ABN and ACN.     yield break;   } }  The linq 2 sql method will save the individual fields, whereas the Nhibernate method will save the entire component value. Basically I think the nhibernate method needs more work to handle many to many relationships and components. etc.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/6/auditing-with-nhibernate</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/6/auditing-with-nhibernate</guid>
                    <pubDate>Sat, 18 June 2011 07:51:00 </pubDate>
                </item>
                <item>
                    <title>How are the 2011 Formula 1 rules going</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/6/how-are-the-2011-formula-1-rules-going</comments>
                    <description>The formula one season is now seven races done and what a season it has been so far.&#160; 2011 saw the introduction and re-introduction of a few major, possibly game changing components.   DRS  KERS  Pirelli tyres   &#160;  DRS, or Drag Reduction System is designed to temporarily reduce the downforce of the cars by reducing the angle of and &#39;opening&#39; the rear wing of the cars at particular locations on track.&#160; DRS only becomes an option when the car is within 1 second of the car in front and it is only available to the following car.&#160; This is intended to allow overtaking in the braking zone and spice up the show.&#160; DRS activiation is also limited to within the specified DRS zones, two laps after the race has started.  DRS has had mixed reviews amongst the fans.&#160; Yes it is a great tool for passing but the long time and hard core fans, as well as the drivers themselves, complain that it takes skill out of the mix, that these are just passes, not hard fought overtakes.&#160; DRS is pretty much exactly push to pass.&#160; If you&#39;re less than a second behind a driver, you push the button and it&#39;s highly likely you&#39;ll be past him before the next braking zone.&#160; It&#39;s playstation racing at it&#39;s best.  KERS, or Kenetic Energy Recovery System is designed to store energy generated by the braking forces of&#160;a car and return that energy to the car in the form of a 6.8 second per lap 80HP power boost.&#160; KERS can be activated at any time, including off the line.  The start of this year saw the return of Pirelli, replacing Bridgestone as tyre supplier to the series.&#160; They entered with the brief to supply tyres that would offer excellent performance but degrade very quickly.&#160; Each grade of tyre, super soft, soft, medium, hard, must offer markedly different performance characteristics.&#160; At each race Pirelli nominate which two types of tyres will be used, with the provision that they must be two grades appart (eg super soft and medium) and each car must run each dry race with both availble, prime and option tyres.  As it&#39;s shaped up so far, the tyres are the most dominate factor and have provided the most variability in races.&#160; Drivers have struggled with how to best manage their tyres and when to come in.&#160; Such is the race tyre strategy game that it has to a significant extent invalidated qualifying, as evidenced by Mark Webbers drive from near the back of the grid through to third place and only a few seconds from winning the Chinese grand prix this year.&#160; The number of pit stops have increased, but not by the amount many were expecting before the season began.&#160; The usual is now 3 or 4 stops, rather than the old 2 and occasional 3 stop race.  Based on the number of wins so far this season it&#39;s hard to fault Vettels tyre management, except maybe at the Chinese GP where he pitted early and tried to run on 2 stops, nearly being beaten by a four stopping hard charging Webber.&#160; The other obvious candidate is the silky smooth Jensen Button, winner of one the most exciting grand prix I&#39;ve seen in years.&#160; It was second only to Webbers hard charge through the field in China this year, but that race was brilliant for me because I was backing my fellow country man for the win.&#160; Buttons win in Canada was so deserved I was cheering for him rather than a hard charging Webber once it became apparent what he might do.&#160; That and I&#39;d like Vettel not to completely run away with it.  How are the drivers standing up at this point in the season?  Sebastian Vettel - So many poles. So many wins.&#160; How can he not be at the top?  Fernando Alonso - Performing very well in an ordinary car  Lewis Hamilton - Should be above Alonso but he&#39;s losing it a bit in his frustration to keep pace with Vettel, and Button is on occasion making him look silly.  Jensen Button - So hard to put Jensen behind Mark, especially after Montreal and his pace relative to his teammate is solid.&#160; He seems really happy at Mclaren and it works well for him. As a Mclaren fan I like Jensen at Mclaren more than Lewis.  Mark Webber - Should be going faster he his.&#160; His qualifying performances have let me down and I would have expected him to win at least one race so far.  Nico Roseberg - The forgotten man.&#160; Has made Schumacher look silly at all but Montreal.&#160; Still he needs a race winning car to really show us what he can do.&#160; And it would be mega.&#160; He has to be careful though because time is running out.  Fillipe Massa - Started off the season in nowhere. The last couple of races have seen a marked improvement. It might just be enough for him to keep his ride next year.  Michael Schumacher - Consistently slower than Roseberg but getting better at each race.&#160;&#160; Needs to work on his qualifying pace and as the most pole positions record holder, he doesn&#39;t have excuses for qualifying behind his teammate.  Vitaly Petrov - I&#39;m surprised. I thoroughy expected Nick Hiedfeld to dominate Vitaly but it hasn&#39;t looked like happening this season. Not even a little. The big question is &quot;What would Kubica have done?&quot; :p  Nick Hiedfeld - Needs more pace.&#160; That&#39;s about it.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/6/how-are-the-2011-formula-1-rules-going</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/6/how-are-the-2011-formula-1-rules-going</guid>
                    <pubDate>Fri, 17 June 2011 16:08:00 </pubDate>
                </item>
                <item>
                    <title>FluentNhibernate Reveal.Member method</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/6/fluentnhibernate-revealmember-method</comments>
                    <description>I&#39;m converting the persistence layer fo existing application that uses Linq2Sql to popluate POCO business objects (seems like overkill but that&#39;s another story) to Nhibernate, using Fluent Nhibernate to create the mappings from POCO to DB.&#160; Everything has been going swimmingly, until it came to setting readonly properties that were classes, where I was getting the error  &quot;NHibernate.MappingException : An association from the table Call refers to an unmapped class: System.Object&quot; .  I tried commenting code out an narrowed it down to the the Contract property of my call class.  &#160;  public class CallMap : ClassMap&amp;lt;Call&amp;gt; {   public CallMap()   {     Table(&quot;Call&quot;);     Id(x =&amp;gt; x.Id).Column(&quot;CallId&quot;).GeneratedBy.Identity();     Map(x =&amp;gt; x.CallTime).Not.Nullable();     Map(x =&amp;gt; x.CallType).Nullable().Not.Nullable();     Map(x =&amp;gt; x.CalledFrom).Column(&quot;Location&quot;).Length(50);     Map(x =&amp;gt; x.ImportedCallType).Length(50);     Map(x =&amp;gt; x.PhoneNumber).Length(20);     Map(x =&amp;gt; x.NumberCalled).Length(20);     Map(x =&amp;gt; x.UnitsOfTime);     Map(x =&amp;gt; x.ActualVolume).Column(&quot;Volume&quot;).Not.Nullable();     Map(Reveal.Member&amp;lt;Call&amp;gt;(&quot;_dateInvoiced&quot;)).Column(&quot;DateInvoiced&quot;);     References(Reveal.Member&amp;lt;Call&amp;gt;(&quot;_contract&quot;)).Column(&quot;ContractId&quot;).Not.Nullable();     References(x =&amp;gt; x.Tariff).Column(&quot;TariffId&quot;).Not.Nullable();   } }  My business classes is:  public class Call : EntityBase {   private string _displayVolume;   private string _numberCalled;   private Contract _contract;   private DateTime? _dateInvoiced;   protected Call() { }   public Call(Contract contract)   {     _contract = contract;   }   public virtual Contract Contract { get { return _contract; } }   public virtual PlanTariff Tariff { get; set; }   public virtual DateTime CallTime { get; set; }     public virtual string PhoneNumber { get; set; }     public virtual string NumberCalled   {     get     {       if (CallType == CallVolumeUnit.Data)         return &quot;Data&quot;;       if (CallType == CallVolumeUnit.Discrete)         return &quot;SMS&quot;;       return _numberCalled;      }     set     {       _numberCalled = value;     }   }     public virtual string CalledFrom { get; set; }   /// &amp;lt;summary&amp;gt;   /// AKA Duration property. Should reflect the Duration   /// formula from the Invoice. This gets filled by the DAL   /// But will be calcualted, in the NHibernate version!   /// &amp;lt;/summary&amp;gt;   public virtual decimal Volume { get; set; }   public virtual string DisplayVolume   {     get     {       if (CallType == CallVolumeUnit.Time) {         // format         var hours = Math.Floor(Volume);         var minutes = Math.Round((Volume - hours)*60);         return string.Format(&quot;{0}:{1:00}&quot;, hours, minutes);                 }       if (CallType == CallVolumeUnit.Data) {         return string.Format(&quot;{0:0.00} MB&quot;, Volume);       }                return string.Format(&quot;{0:0}&quot;, Volume);             }     set { _displayVolume = value; }   }   /// &amp;lt;summary&amp;gt;   /// The Volume as it was imported from the Call Data   /// &amp;lt;/summary&amp;gt;   public virtual decimal ActualVolume { get; set; }     public virtual decimal UnitsOfTime { get; set; }   public virtual decimal UnitCost { get; set; }   public virtual decimal Cost { get; set; }     public virtual string ImportedCallType { get; set; }   public virtual CallVolumeUnit CallType { get; set; }      public virtual bool IsInvoiced   {     get { return _dateInvoiced.HasValue; }   } }  &#160;  The problem was setting _contract, as seen on the line  References(Reveal.Member&amp;lt;Call&amp;gt;(&quot;_contract&quot;)).Column(&quot;ContractId&quot;).Not.Nullable();  &#160;  Looking at the intellisense I noticed the Member call takes a return type as second argument, so I set that and everything worked.  References(Reveal.Member&amp;lt;Call, Contract&amp;gt;(&quot;_contract&quot;)).Column(&quot;ContractId&quot;).Not.Nullable();  It seems that when setting reference types / complex types you need to specify the type, or you’ll get the System.Object error.&#160; Makes sense now I know the problem.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/6/fluentnhibernate-revealmember-method</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/6/fluentnhibernate-revealmember-method</guid>
                    <pubDate>Mon, 13 June 2011 22:46:00 </pubDate>
                </item>
                <item>
                    <title>Creating custom multi-level trees in Umbraco</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/5/creating-custom-multi-level-trees-in-umbraco</comments>
                    <description>This is the third post in a series.&#160; If you haven&#39;t already, read the previous post on creating a custom content tree and section in umbraco  In my last post I created a custom section and basic tree for my atomicf1.com website.&#160; In this blog I’ll create a more complex nested tree in the “Results” section.&#160; This tree is allows site editors/administrators to maintain race results and register drivers for competition in a season.&#160; Only drivers registered for the particular season will be able to have results entered against races.&#160; The races for a given season are defined in the Data section created in the previous post and the Results section will use that information to build the basics of the tree.&#160; I realise all this could be done the main content tree, but the exercise is more about me learning about custom sections and trees in Umbraco using a subject (Formula 1) that motivates me.  What we want is a content tree that looks like the following, where we have all Seasons defined in the Data section and a place to register entrants and enter race results for each of the races defined for that season.    How to build a simple tree is covered in detailed in the previous post which I’ll assume has been read prior to this.&#160; To recap, we need to implement to do the follow:   Define the new section in the umbracoApp table  Create a single entry for the root “Results” in the umbracoAppTree table.  Add a new section to the desired language files  Derive a class from umbraco.cms.presentation.Trees.BaseTree to populate the tree  Implement classes from the umbraco.interfaces.ITaskReturnUrl interface to create nodes for the tree where custom controls are not used  Create a Page to maintain each item  Create custom User Controls for new items where required  Define the nods in the UI.xml file for the custom user controls   &#160;  The first difference to note in this implementation is that we only require one entry in the umbracoAppTree table, that for the root node “Results”.&#160; Because the other nodes in the tree are generated from data entered in the Seasons subtree in the Data section we don’t need to create and remove entries from the umbracoAppTable for each of those.  My implementation of umbraco.cms.presentation.Trees.BaseTree looks like the following  namespace atomicf1.cms.presentation.Trees {   public class loadResults : BaseTree   {     private ISeasonRepository _seasonRepository;         public loadResults(string application) : base(application)     {       _seasonRepository = new SeasonRepository();     }     protected override void CreateRootNode(ref XmlTreeNode rootNode)     {       rootNode.Icon = FolderIcon;       rootNode.OpenIcon = FolderIconOpen;       rootNode.NodeType = TreeAlias;       rootNode.NodeID = &quot;-1&quot;;       rootNode.Menu.Clear();       rootNode.Menu.Add(ActionRefresh.Instance);     }     public override void Render(ref XmlTree tree)     {       TreeService treeService;       if (this.NodeKey == string.Empty) {                 PopulateSeasons(ref tree);       }       else       {         string keyType = this.NodeKey.Split(new string[] { &quot;-&quot; }, StringSplitOptions.RemoveEmptyEntries)[0];         int keyId = int.Parse(this.NodeKey.Split(new string[] { &quot;-&quot; }, StringSplitOptions.RemoveEmptyEntries)[1]);                 var factory = new LoadResultsTreeCommandFactory(this);         var command = factory.GetLoader(keyType);         command.Populate(ref tree, keyId);       }           }     protected void PopulateSeasons(ref XmlTree tree)     {       var seasons = _seasonRepository.GetAll();       foreach (var season in seasons)       {         var node = XmlTreeNode.Create(this);         node.NodeID = season.Id.ToString();         node.Text = season.Name;         node.Icon = &quot;folder.gif&quot;;         node.OpenIcon = &quot;folder_o.gif&quot;;         //node.Action = &quot;javascript:alert(&#39;boo&#39;)&quot;;                   var treeService = new TreeService(-1, TreeAlias, ShowContextMenu, IsDialog, DialogMode, app, string.Format(&quot;Season-{0}&quot;, season.Id));         node.Source = treeService.GetServiceUrl();         node.Menu.Clear();         node.Menu.Add(ActionRefresh.Instance);                 tree.Add(node);       }     }            public override void RenderJS(ref System.Text.StringBuilder Javascript)     {       Javascript.Append(        @&quot;           function openSeasonEntry(id)           {             parent.right.document.location.href = &#39;plugins/atomicf1/editSeasonEntry.aspx?id=&#39; + id;           }           function openRaceEntry(id, raceid)           {             parent.right.document.location.href = &#39;plugins/atomicf1/editRaceEntry.aspx?id=&#39; + id + &#39;&amp;amp;raceid=&#39; + raceid;           }                   &quot;);     }   } }  &#160;  If you think it looks a little sparse to be creating all the nodes in a tree that’s 6 levels deep, you’d be right, but more on that later.&#160; The main “trick” to point out is that you need to call TreeService.GetServiceUrl to have your tree know how to create it’s child node.&#160; Within the TreeService constructor most arguments can be left as they are above, with the last being the one that you need to set for each for each parent node.&#160; This value is how your code will determine what sub-tree needs to be created in the Render method and what Id to use to populate it.&#160; In my case I’m saying create a Season substree for the season with Id season.Id.&#160; You will also need to extract the sub-tree type and id inside the Render method  string keyType = this.NodeKey.Split(new string[] { &quot;-&quot; }, StringSplitOptions.RemoveEmptyEntries)[0]; int keyId = int.Parse(this.NodeKey.Split(new string[] { &quot;-&quot; }, StringSplitOptions.RemoveEmptyEntries)[1]);  Using the above two pieces of key information (pardon the pun) I can determine how my sub-tree is going to be built.&#160; I’ve used a builder/strategy pattern rather than include all the code I need to generate al levels of my tree inside the one Render method and class.  &#160;  namespace atomicf1.cms.presentation.Trees {   public class LoadResultsTreeCommandFactory   {     private BaseTree _baseTree;     public LoadResultsTreeCommandFactory(BaseTree baseTree)     {       _baseTree = baseTree;     }     public ILoadResultsTreeCommand GetLoader(string subtreeKey)     {       switch (subtreeKey) {         case &quot;Season&quot;:           return new loadResultsSeason(_baseTree);                   case &quot;Entries&quot;:           return new loadResultsEntries(_baseTree);         case &quot;Races&quot;:           return new loadResultsRaces(_baseTree);         case &quot;RaceEntry&quot;:           return new loadResultsRaceEntries(_baseTree);               }       return null;     }   } }  The above class determines which tree Loader to use.&#160; Nothing particularly Umbraco here…  I’ve created a BaseLoadResultsTreeCommand to handle all the common stuff.  namespace atomicf1.cms.presentation.Trees {   public abstract class BaseLoadResultsTreeCommand : ILoadResultsTreeCommand   {     protected BaseTree _baseTree;     protected ISeasonRepository _seasonRepository;     public BaseLoadResultsTreeCommand(BaseTree tree)     {       _baseTree = tree;       _seasonRepository = new SeasonRepository();     }     public abstract void Populate(ref XmlTree tree, int keyId);     public TreeService GetTreeService(int keyId, string nodeKey)     {       return new TreeService(keyId, _baseTree.TreeAlias, _baseTree.ShowContextMenu, _baseTree.IsDialog, _baseTree.DialogMode, _baseTree.app, nodeKey);     }   } }  namespace atomicf1.cms.presentation.Trees {   public interface ILoadResultsTreeCommand   {     void Populate(ref XmlTree tree, int keyId);   } }  Here’s how my loader for each Season subtree looks  namespace atomicf1.cms.presentation.Trees {   public class loadResultsSeason : BaseLoadResultsTreeCommand, ILoadResultsTreeCommand   {     public loadResultsSeason(BaseTree tree) : base(tree) { }         #region ILoadResultsTreeCommand Members     public override void Populate(ref XmlTree tree, int keyId)     {       Season season = _seasonRepository.GetById(keyId);       if (season != null)       {         XmlTreeNode entries = XmlTreeNode.Create(_baseTree);         entries.NodeID = season.Id.ToString();         entries.Icon = &quot;folder.gif&quot;;         entries.Text = &quot;Entrants&quot;;         entries.NodeType = &quot;seasonEntry&quot;;         var treeService = GetTreeService(keyId, string.Format(&quot;Entries-{0}&quot;, season.Id));         entries.Source = season.Entrants.Count() &amp;gt; 0 ? treeService.GetServiceUrl() : &quot;&quot;;         entries.Menu.Clear();         entries.Menu.AddRange(new List&amp;lt;IAction&amp;gt; { ActionNew.Instance, ContextMenuSeperator.Instance, ActionRefresh.Instance });         tree.Add(entries);         XmlTreeNode races = XmlTreeNode.Create(_baseTree);         races.NodeID = season.Id.ToString();         races.Icon = &quot;folder.gif&quot;;         races.Text = &quot;Races&quot;;         races.NodeType = &quot;seasonRaceFolder&quot;;         treeService = GetTreeService(keyId, string.Format(&quot;Races-{0}&quot;, season.Id));         races.Source = season.Races.Count() &amp;gt; 0 ? treeService.GetServiceUrl() : &quot;&quot;;         races.Menu.Clear();         races.Menu.AddRange(new List&amp;lt;IAction&amp;gt; { ActionRefresh.Instance });         tree.Add(races);       }     }     #endregion   } }  I take the supplied database key for the season and retrieve my season from the repository and then create the Entrants and Races nodes.&#160; As mentioned earlier, it’s important to provide the keyType and keyId for the for the next node down the tree.&#160; Remember that the original Render method, for the loadResults class registered in the umbracoAppTree will be the one that’s called each time and it will determine which class to load to render the appropriate node and it’s children.  &#160;  That’s it!&#160; There’s not a lot of difference between what I did in the previous post and this one.&#160; Just the small tweaking to link up the relationships between parent and child trees.&#160; Once complete, my final tree looks like</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/5/creating-custom-multi-level-trees-in-umbraco</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/5/creating-custom-multi-level-trees-in-umbraco</guid>
                    <pubDate>Fri, 27 May 2011 18:09:00 </pubDate>
                </item>
                <item>
                    <title>Creating a custom content tree in umbraco</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/5/creating-a-custom-content-tree-in-umbraco</comments>
                    <description>If you’re new to this series, checkout the introductory post .  See the source code on Github   I’m going to create two new sections for manage my statistical data.&#160; The first section “Data” will contain the base data for the site: Drivers, Circuits, Teams, and Seasons (made up of races held at circuits at a specific time).&#160; The other section “Results” will be used to register driver/team combinations for a season and to enter the results of each driver/team at each race.&#160; The creation of each custom content tree follows the basic steps:   Define the section in the umbracoApp table  Add new section to your language file  Define tree nodes in the umbracoAppTree table  Derive a class from umbraco.cms.presentation.Trees.BaseTree to populate your custom 1st level sub trees.  Implement a class from the umbraco.interfaces.ITaskReturnUrl interface for nodes of the tree  Create a Page for each item to maintain  Create Custom User Controls for new items where required.  Define the nodes in the UI.xml file   &#160;  Define the section in the umbracoApp table  To let Umbraco know about the new section I need to create an entry in the umbracoApp table.&#160; This defines the following fields     sortOrder  This defines the order of the section in the sections displayed    appAlias  The appAlias is used to identify your section to umbraco    appIcon  Defines a css class in /umbraco/css/umbracoGui.css that determines the icon to display in the section.&#160; There are other tutorials online on how to do this.&#160; Some recommend just using your custom background image, while others recommend adding your image to the existing tray icons sprite.&#160; I’m going with the later method.    appName  This is your section name.    appInitWithTreeAlias  This can safely be left as NULL. I’m not sure exactly what purpose it serves, but this is the advice I read elsewhere.     &#160;  After I defined my new section I had to add it to my language file, at /umbraco/config/lang/en.xml, in the sections area. (&amp;lt;area alias=”sections”&amp;gt;)&#160; This gives the section an English title. Following the configuration I will end up with a section container like the following:    &#160;  Define a tree nodes in the umbracoAppTree table  The next step is to define some tree nodes for my new content tree.&#160; This is done in the umbracoAppTree table.&#160; I’ll only define nodes for subtrees I don’t generate in code. These are the Driver, Team, Circuit, Seasons nodes in the Data section and the Results node in the Results section.&#160; The Results content tree is a tricky one because all but the root node depend on data created in the Data section.&#160; We’ll get to that in the next post.     treeSilent  leave this value as 0    treeInitialize  set this value to 1    treeSortOrder  This is the position of your node within the tree    appAlias  This is the alias of the app, as entered in the umbracoApp table.&#160; Mine will be dataentry and results    treeAlias  This is the alias of the tree node    treeTitle  This is the title of the tree node.    treeIconClosed  This is the icon to use when the node is not expanded    treeIconOpen  This is the icon to use when the node is expanded.&#160; I’ve found this doesn’t make a difference.    treeHandlerAssembly  This is the name of the assembly that contains the code umbraco will call to perform actions on your tree.&#160; Don’t include the dll extension.    treeHandlerType  This is the Type that handles the loading of your tree contents.&#160; Contained in the above assembly    action  leave this as null     &#160;  Derive a class from umbraco.cms.presentation.Trees.BaseTree  Now I need to create a class (Type) that umbraco will use to populate my tree contents, as declared in the treeHandlerType field in the umbracoAppTree table.&#160; I need to fill out the contructor, override the CreateRootNode method which will create a node in the tree, following what’s in the table above, override the Render method which will populate my tree, and finally override the RenderJS method with code needed to open the edit page for my content.  &#160;  using atomicf1.domain.Repositories; using atomicf1.persistence; using umbraco.cms.presentation.Trees; namespace atomicf1.cms.presentation.Trees {   public class loadDrivers : BaseTree   {     private IDriverRepository _driverRepository;     public loadDrivers(string application) : base(application)     {       _driverRepository = new DriverRepository();     }     protected override void CreateRootNode(ref XmlTreeNode rootNode)     {       rootNode.Icon = FolderIcon;       rootNode.OpenIcon = FolderIconOpen;       rootNode.NodeType = TreeAlias;       rootNode.NodeID = &quot;init&quot;;     }     public override void Render(ref XmlTree tree)     {            var drivers = _driverRepository.GetAll();       foreach(var driver in drivers) {         var dNode = XmlTreeNode.Create(this);         dNode.NodeID = driver.Id.ToString();         dNode.Text = driver.Name;         dNode.Icon = &quot;user.png&quot;;         dNode.Action = &quot;javascript:openDrivers(&quot; + driver.Id + &quot;)&quot;;         tree.Add(dNode);       }           }     public override void RenderJS(ref System.Text.StringBuilder Javascript)     {       Javascript.Append(         @&quot;           function openDrivers(id)           {             parent.right.document.location.href = &#39;plugins/atomicf1/editDriver.aspx?id=&#39; + id;           }         &quot;);     }   } }  Following this I should end up with (mine’s been populated) a tree that looks like the following    &#160;  Implement a class from the umbraco.interfaces.ITaskReturnUrl interface  I need to implement a class that will allow me to create the initial entities in my tree.&#160; To do this I must implement the umbraco.interfaces.ITaskReturnUrl interface and stub out some key methods.&#160; I’ll create a base class to handle the common stuff…  &#160;  using umbraco.interfaces; namespace atomicf1.cms.presentation {   public abstract class BaseTasks : ITaskReturnUrl   {     protected const string BasePageDirectory = &quot;plugins/atomicf1/&quot;;     protected string _returnUrl;     protected string _alias;     protected int _typeId;     protected int _parentId;     protected int _userId;     public string ReturnUrl     {       get { return _returnUrl; }     }     public string Alias     {       get       {         return _alias;       }       set       {         _alias = value;       }     }     public abstract bool Delete();         public int ParentID     {       get       {         return _parentId;       }       set       {         _parentId = value;       }     }     public abstract bool Save();          public int TypeID     {       get       {         return _typeId;       }       set       {         _typeId = value;       }     }     public int UserId     {       set { _userId = value; }     }         } }  Then I’ll need a class to do the Saving and Deleting of my entities.&#160; This isn’t needed when I use a User Control (ascx) to create entities.&#160; I will do this later on, when I need to save more than just the name with each entity (mandatory fields).&#160; My code will utilise my repositories to do the persistence.  &#160;  &#160;  using atomicf1.domain; using atomicf1.domain.Repositories; using atomicf1.persistence; namespace atomicf1.cms.presentation {   public class driverEntryTasks : BaseTasks   {     private readonly IDriverRepository _repository;     public driverEntryTasks()     {       _repository = new DriverRepository();     }     public override bool Delete()     {       var driver = _repository.GetById(ParentID);       _repository.Delete(driver);       ;       return true;     }     public override bool Save()     {       var driver = new Driver { Name = Alias };       _repository.Save(driver);       _returnUrl = BasePageDirectory + &quot;editDriver.aspx?id=&quot; + driver.Id;       return true;     }   } }  &#160;  Create a Page for each item to maintain  Next I’ll need to create a page to maintain each entity.&#160; This page is opened by the javascript I added to my RenderJS method and node.Action property a few steps ago.&#160; Umbraco has numerous inbuilt controls to facilitate creating a consistent admin UI so I’ve gone and utilised those.&#160; The following is just standard ASP.NET webforms stuff.  &amp;lt;%@ Page Language=&quot;C#&quot; MasterPageFile=&quot;../../masterpages/umbracoPage.Master&quot; AutoEventWireup=&quot;true&quot; CodeBehind=&quot;editDriver.aspx.cs&quot; Inherits=&quot;atomicf1.cms.presentation.pages.editDriver&quot; %&amp;gt; &amp;lt;%@ Register Namespace=&quot;umbraco.uicontrols&quot; Assembly=&quot;controls&quot; TagPrefix=&quot;umb&quot; %&amp;gt; &amp;lt;asp:Content ID=&quot;Content&quot; ContentPlaceHolderID=&quot;body&quot; runat=&quot;server&quot;&amp;gt;     &amp;lt;umb:UmbracoPanel ID=&quot;Panel1&quot; runat=&quot;server&quot; hasMenu=&quot;true&quot; Text=&quot;Edit Circuit&quot;&amp;gt;         &amp;lt;umb:Pane ID=&quot;Pane1&quot; runat=&quot;server&quot;&amp;gt;             &amp;lt;umb:PropertyPanel ID=&quot;PPanel3&quot; runat=&quot;server&quot; Text=&quot;Atomic Username&quot;&amp;gt;         &amp;lt;asp:TextBox ID=&quot;AtomicName&quot; runat=&quot;server&quot; MaxLength=&quot;50&quot;         CssClass=&quot;guiInputText guiInputStandardSize&quot;&amp;gt;&amp;lt;/asp:TextBox&amp;gt;       &amp;lt;/umb:PropertyPanel&amp;gt;       &amp;lt;umb:PropertyPanel ID=&quot;PPanel1&quot; runat=&quot;server&quot; Text=&quot;Driver Name&quot;&amp;gt;         &amp;lt;asp:TextBox ID=&quot;DriverNameTextBox&quot; runat=&quot;server&quot; MaxLength=&quot;50&quot;         CssClass=&quot;guiInputText guiInputStandardSize&quot;&amp;gt;&amp;lt;/asp:TextBox&amp;gt;       &amp;lt;/umb:PropertyPanel&amp;gt;             &amp;lt;umb:PropertyPanel ID=&quot;PPanel2&quot; runat=&quot;server&quot; Text=&quot;Nationality&quot;&amp;gt;                 &amp;lt;asp:TextBox ID=&quot;NationalityTextBox&quot; runat=&quot;server&quot; MaxLength=&quot;50&quot;         CssClass=&quot;guiInputText guiInputStandardSize&quot;&amp;gt;&amp;lt;/asp:TextBox&amp;gt;       &amp;lt;/umb:PropertyPanel&amp;gt;             &amp;lt;umb:PropertyPanel ID=&quot;PPanel4&quot; runat=&quot;server&quot; Text=&quot;Atomic User ID&quot;&amp;gt;         &amp;lt;asp:TextBox ID=&quot;AtomicUserId&quot; runat=&quot;server&quot; MaxLength=&quot;50&quot;         CssClass=&quot;guiInputText guiInputStandardSize&quot;&amp;gt;&amp;lt;/asp:TextBox&amp;gt;       &amp;lt;/umb:PropertyPanel&amp;gt;           &amp;lt;/umb:Pane&amp;gt;       &amp;lt;umb:Pane ID=&quot;Pane2&quot; runat=&quot;server&quot;&amp;gt;       &amp;lt;umb:PropertyPanel ID=&quot;PropertyPanel1&quot; runat=&quot;server&quot; Text=&quot;Url&quot;&amp;gt;         &amp;lt;asp:TextBox ID=&quot;UrlTextBox&quot; runat=&quot;server&quot; MaxLength=&quot;70&quot;         CssClass=&quot;guiInputText guiInputStandardSize&quot;&amp;gt;&amp;lt;/asp:TextBox&amp;gt;       &amp;lt;/umb:PropertyPanel&amp;gt;     &amp;lt;/umb:Pane&amp;gt;     &amp;lt;/umb:UmbracoPanel&amp;gt; &amp;lt;/asp:Content&amp;gt;  In the code behind I&#39;ll add the details on how to populate and save changes to my entity.  using System; using System.Web.UI; using System.Web.UI.WebControls; using atomicf1.domain.Repositories; using atomicf1.persistence; using umbraco.BasePages; namespace atomicf1.cms.presentation.pages {   public partial class editDriver : BasePage   {     private IDriverRepository _repository;     protected void Page_Load(object sender, EventArgs e)     {       if (!IsPostBack)       {         int id = int.Parse(Request[&quot;id&quot;]);         LoadRecord(id);       }     }     protected void LoadRecord(int id)     {       var driver = _repository.GetById(id);       if (driver != null) {         DriverNameTextBox.Text = driver.Name;         NationalityTextBox.Text = driver.Nationality;         AtomicName.Text = driver.AtomicName;         AtomicUserId.Text = driver.AtomicUserId.ToString();         UrlTextBox.Text = driver.Url;       }     }     protected override void OnInit(EventArgs e)     {       base.OnInit(e);       _repository = new DriverRepository();       ImageButton save = Panel1.Menu.NewImageButton();       save.ImageUrl = umbraco.GlobalSettings.Path + &quot;/images/editor/save.gif&quot;;       save.AlternateText = &quot;Save&quot;;       save.Click += new ImageClickEventHandler(SaveRecord);     }     void SaveRecord(object sender, ImageClickEventArgs e)     {       if (Page.IsValid) {         var driver = _repository.GetById(int.Parse(Request[&quot;id&quot;]));         if (driver != null) {           driver.Name = DriverNameTextBox.Text;           driver.Nationality = NationalityTextBox.Text;           driver.AtomicName = AtomicName.Text;           driver.AtomicUserId = int.Parse(AtomicUserId.Text);           driver.Url = UrlTextBox.Text;           _repository.Save(driver);         }         BasePage.Current.ClientTools.ShowSpeechBubble(speechBubbleIcon.save, &quot;Saved&quot;,                                &quot;Driver details have been saved.&quot;);       }     }   } }  &#160;  Create Custom User Controls for new items where required  There are some cases where I’ll need to save more than just the entity name, such as when I create registrations for a particular season.&#160; Rather than use the default simple.ascx control, I can make my own.&#160; To register a driver and team for a season I’ll need to select the team and driver, from the list of entrants for a particular season.&#160; I’ll know which season I’m registering for based on the node selected in the tree.  First the html for the page  &amp;lt;%@ Control Language=&quot;C#&quot; AutoEventWireup=&quot;true&quot; CodeBehind=&quot;createSeasonEntry.ascx.cs&quot; Inherits=&quot;atomicf1.cms.presentation.controls.createSeasonEntry&quot; %&amp;gt; &amp;lt;input type=&quot;hidden&quot; name=&quot;nodeType&quot;&amp;gt; &amp;lt;div style=&quot;MARGIN-TOP: 20px&quot;&amp;gt;     Driver:&amp;lt;asp:RequiredFieldValidator id=&quot;RequiredFieldValidator1&quot; ErrorMessage=&quot;*&quot; ControlToValidate=&quot;DriverList&quot; runat=&quot;server&quot;&amp;gt;*&amp;lt;/asp:RequiredFieldValidator&amp;gt;&amp;lt;br /&amp;gt;   &amp;lt;asp:DropDownList ID=&quot;DriverList&quot; runat=&quot;server&quot; CssClass=&quot;bigInput&quot; Width=&quot;300px&quot; /&amp;gt;   &amp;lt;!-- added to support missing postback on enter in IE --&amp;gt;   &amp;lt;asp:TextBox runat=&quot;server&quot; style=&quot;visibility:hidden;display:none;&quot; ID=&quot;Textbox1&quot;/&amp;gt;   &amp;lt;br /&amp;gt;   Team:&amp;lt;asp:RequiredFieldValidator id=&quot;RequiredFieldValidator2&quot; ErrorMessage=&quot;*&quot; ControlToValidate=&quot;TeamList&quot; runat=&quot;server&quot;&amp;gt;*&amp;lt;/asp:RequiredFieldValidator&amp;gt;&amp;lt;br /&amp;gt;   &amp;lt;asp:DropDownList ID=&quot;TeamList&quot; runat=&quot;server&quot; CssClass=&quot;bigInput&quot; Width=&quot;300px&quot; /&amp;gt;   &amp;lt;/div&amp;gt; &amp;lt;div style=&quot;padding-top: 25px;&quot;&amp;gt;   &amp;lt;asp:Button id=&quot;sbmt&quot; Runat=&quot;server&quot; style=&quot;Width:90px&quot; onclick=&quot;sbmt_Click&quot; Text=&quot;Create&quot;&amp;gt;&amp;lt;/asp:Button&amp;gt;   &amp;amp;nbsp; &amp;lt;em&amp;gt;or&amp;lt;/em&amp;gt; &amp;amp;nbsp;  &amp;lt;a href=&quot;#&quot; style=&quot;color: blue&quot; onclick=&quot;UmbClientMgr.closeModalWindow()&quot;&amp;gt;Cancel&amp;lt;/a&amp;gt; &amp;lt;/div&amp;gt;  If I’m not sure what to put in here I can always look at the existing simple.ascx control as a guide.&#160; The above will create a UI that looks like this:    Next I’ll need to write some code-behind to perform my logic. You may notice I haven’t yet upgraded this to my Repository/Domain Model.&#160; The astitute will also notice I’m not allowing registration of those already registered.  using System; using System.Collections.Generic; using umbraco.BasePages; using atomicf1.domain; namespace atomicf1.cms.presentation.controls {   public partial class createSeasonEntry : System.Web.UI.UserControl   {     protected void Page_Load(object sender, EventArgs e)     {       if (!IsPostBack) {         var reader = umbraco.BusinessLogic.Application.SqlHelper.ExecuteReader(           @&quot;           SELECT D.*           FROM Driver D           WHERE NOT EXISTS (SELECT * FROM DriverContract DC WHERE DC.DriverId=D.DriverId AND DC.SeasonId=@seasonId)&quot;,           umbraco.BusinessLogic.Application.SqlHelper.CreateParameter(&quot;@seasonId&quot;, SeasonId));         var allDrivers = new List&amp;lt;Driver&amp;gt;();         while (reader.Read()) {           var driver = new Driver()                   {                     Id = reader.GetInt(&quot;driverid&quot;),                     Name = reader.GetString(&quot;name&quot;)                   };           allDrivers.Add(driver);         }         reader = umbraco.BusinessLogic.Application.SqlHelper.ExecuteReader(           @&quot;             SELECT *             FROM Team&quot;);         var allTeams = new List&amp;lt;Team&amp;gt;();         while (reader.Read()) {           var team = new Team()                  {                    Id = reader.GetInt(&quot;teamid&quot;),                    Name = reader.GetString(&quot;name&quot;)                   };           allTeams.Add(team);         }                 DriverList.DataSource = allDrivers;         DriverList.DataTextField = &quot;Name&quot;;         DriverList.DataValueField = &quot;Id&quot;;         DriverList.DataBind();         TeamList.DataSource = allTeams;         TeamList.DataTextField = &quot;Name&quot;;         TeamList.DataValueField = &quot;Id&quot;;         TeamList.DataBind();       }     }     private int SeasonId     {       get { return int.Parse(umbraco.presentation.UmbracoContext.Current.Request[&quot;nodeId&quot;]); }     }     protected void sbmt_Click(object sender, EventArgs e)     {       if (Page.IsValid) {                         // get it from the parent. but how?                 var seasonId = umbraco.presentation.UmbracoContext.Current.Request[&quot;nodeId&quot;];         const string insertStatement =           @&quot;insert into drivercontract (datecommenced, dateterminated, teamid, driverid, seasonid)            values(getdate(), null, @teamid, @driverid, @seasonid)&quot;;         umbraco.BusinessLogic.Application.SqlHelper.ExecuteNonQuery(           insertStatement,           umbraco.BusinessLogic.Application.SqlHelper.CreateParameter(             &quot;@driverid&quot;, int.Parse(DriverList.SelectedValue)),           umbraco.BusinessLogic.Application.SqlHelper.CreateParameter(             &quot;@teamid&quot;, int.Parse(TeamList.SelectedValue)),           umbraco.BusinessLogic.Application.SqlHelper.CreateParameter(             &quot;@seasonid&quot;, int.Parse(seasonId)));         BasePage.Current.ClientTools.ReloadActionNode(false, true);         BasePage.Current.ClientTools.CloseModalWindow();         BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.save, &quot;Saved&quot;, &quot;Driver Contract for this season has been created&quot;);       }     }   } }  &#160;  The main points of interest are this line:  var seasonId = umbraco.presentation.UmbracoContext.Current.Request[&quot;nodeId&quot;];  where I find the seasonId I’ll be registering to, and:  BasePage.Current.ClientTools.ReloadActionNode(false, true); BasePage.Current.ClientTools.CloseModalWindow(); BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.save, &quot;Saved&quot;, &quot;Driver Contract for this season has been created&quot;);  where I’m giving feedback to the user.  &#160;  Define the nodes in the UI.xml file  Finally, and very importantly, I need to define my node in the /umbraco/config/create/UI.xml file.&#160; This tells umbraco what ITaskReturnUrl to use and which control to use to create entities (a custom user control or the default simple.ascx).&#160; To configure this file create a new nodeType entity, down with the others.     alias  The alias of your node defined in umbracoAppTree    header  The header of section, used in the edit page    usercontrol  The user control to create entities with    tasks/create  The assembly and type that implements ITaskReturnUrl and does the saving    tasks/delete  The assembly and type that implements ITaskReturnUrl and does the deleting     &#160;  That’s a lot to digest.&#160; Hopefully it’s pretty straight forward.&#160; If not, you know where to find me .  In the next post I’ll look at a more advanced situation of custom tree creation, where nested trees, down many levels, need to be created from the database.&#160; This was “fun” to figure out   Not asleep yet?&#160; Go checkout the next post in this series: creating custom multi-level trees in Umbraco</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/5/creating-a-custom-content-tree-in-umbraco</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/5/creating-a-custom-content-tree-in-umbraco</guid>
                    <pubDate>Wed, 11 May 2011 06:11:00 </pubDate>
                </item>
                <item>
                    <title>Using Umbraco CMS as an application framework</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/4/using-umbraco-cms-as-an-application-framework</comments>
                    <description>Some background information:  Before I get into the technical stuff, first some background about what I’m trying to accomplish and why.&#160; I participate in an online Formula 1 fantasy competition held on Codemaster’s recent F1 2010.&#160; Around our online competition, about to start it’s third season, we’ve built a small fantasy F1 world complete with News and Interviews of drivers and statistics etc, much as you would expect when visiting a real F1 site such as www.formula1.com .  The existing website is laboriously but loving maintained by one of the competitors, using raw HTML and served from a machine inside his home network.&#160; He spends many hours each week entering and calculating the statistics from each race, and more entering the fictional site content.&#160; I wanted to reduce the time he has to spent on tasks that can be automated, and also convert a web forms F1 statics website I had written back in the mid 2000’s in ASP.NET 2.0 to ASP.NET MVC 3.0 as a learning exercise and add CMS backed content.&#160; Originally I wanted to run my own F1 site and present some quirky F1 statistics, but after having some trouble sourcing enough F1 information (the old source I used in the mid 2000’s had disappeared a few years ago)&#160; I thought using our competition as the basis for the stats would kill two birds with one stone.  Wait a second, if you’re still paying attention you noticed I said I wanted to do the site in ASP.NET MVC 3.0, so why is this an Umbraco CMS post series?&#160; Well, I wrote up much of the “back end” for the statistics engine in a week or so, knocking out the domain model , NHibernate repositories etc, but was really struggling with a design I was happy with.&#160; To help on the design front I thought I’d turn to Umbraco as a prototyping tool, to help get a UI going.&#160; I knocked up a rough site template over one weekend and started adding features from there, then thought “Why not do the entire site in Umbraco, as a learning experience”.  I’m relatively new to developing CMS’s having only ever done 1 completely from the ground up, and that was as Lead Developer/Project Manager, using Kentico, so I didn’t get my hands dirty as much as I like.&#160; I’ve been playing lightly with Umbraco off and on for nearly 12 months, primarly to build this blog site and also because my company were going to use it for the project I just completed in Kentico, but I’ve never developed a “proper” site in Umbraco yet.  Here’s a&#160; link to the old site , so you can see where the starting point is and what I’m trying to replicate. All copyright belongs to Lambo.  &#160;  What is this Umbraco site going to DO?  I’m building a site that will allow the competition administrator to manage the follow:   Building an F1 Season by constructing a calendar of Races at Circuits on particular dates.  Signing Drivers to Teams to compete in the F1 season  Recording the results of qualifying and race sessions for each Race/Event.  Publishing Fantasy news articles, interviews and other pieces of interest.   &#160;  The site visitor will be able to view:   current driver and team championships  Results of previous race  Next scheduled race  Circuit statistics  Driver statistics  Team statistics  F1 News  Driver Interviews  And More… (as I think it up)   &#160;  How will I do it?  Keep in mind this is a learning exercise for me so don’t be surprised if this differs from implementation.&#160; My plan is to build a few new sections in Umbraco and build custom trees in those sections, where I source the data from custom data tables.&#160; The data tables will be using the schema I’d already mapped out when I’d done up the “back end” for my ASP.NET MVC 3.0 version.&#160; I used migrator.net to maintain that.  This is the first in a series of more than one blog post (I’m not sure how many until I finish) describing the interesting bits of this project as it progresses.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/4/using-umbraco-cms-as-an-application-framework</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/4/using-umbraco-cms-as-an-application-framework</guid>
                    <pubDate>Thu, 21 April 2011 23:29:00 </pubDate>
                </item>
                <item>
                    <title>Shared bank accounts kill romance</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/3/shared-bank-accounts-kill-romance</comments>
                    <description>There comes a point in every relationship when finances are an important discussion. &#160;There&#39;s two main options once you&#39;ve partnered up: pool resources into a shared bank account or keep you and your partners finances separate. &#160;We&#39;ll ignore the slight variations on those two main themes.  Some of my friends in couples have been married for a few years and have separate bank accounts and some, including myself, pool finances into a shared account. &#160;For me, pooling works well. &#160;Over the last five years my partner has had a few years off with no pay looking after children. &#160;One alternative is to give her an allowance to do the groceries and a little for spending on herself. &#160;My view is that that&#39;s not archaic, but untrusting. &#160;My partner is my partner. &#160;What is mine is hers and all that stuff. &#160;That includes money. &#160;As person with the smaller pay packet, perhaps it works out better for her. &#160;I think for us it&#39;s simply more convienient that we just pay bills when they come, rather than having to ask the other person for 50% or their share. &#160;When we want to make a purchase, the theory is we check its ok with the other, then go spend. &#160;There is no me and her, there&#39;s just us. &#160;Sounds romantic doesn&#39;t it? Does it?  The alternative is completely separate accounts, where each partner is free to spend their own hard earned however they choose. &#160;When it comes to bills, they each chip in their share. &#160;Sounds fair, very 2011, and very equal rights/status doesn&#39;t it. &#160;But how is it more romantic?  My theory is that when sharing accounts spontenous gift giving is less meaningful. &#160;If I buy my partner a gift, I&#39;ve just used some of her money to pay for it. &#160;Perhaps all her money. &#160;They say it&#39;s the thought that counts and where the thought in that? &#160;Me saving my money for 4 weeks only to spend $1000 on some jewelry shows I love my partner enough to put some planning and thought into saving, and that I&#39;ve actively foregone money I could otherwise spend on computer games or bike parts. &#160;I can&#39;t make that grand gesture from a shared account.  When we go out to dinner, who pays is irrelevant. &#160;There&#39;s no romance in a guy taking his lady out, when they&#39;re both footing the bill.  Probably a more sensible solution is a shared account for the household bills, where $X is automatically credit from personal account to shared account each month and each partner has their own money to make these grand gestures. &#160;Might even help guys get laid more often, and that&#39;s the real issue.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/3/shared-bank-accounts-kill-romance</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/3/shared-bank-accounts-kill-romance</guid>
                    <pubDate>Thu, 24 March 2011 02:03:00 </pubDate>
                </item>
                <item>
                    <title>2011 AtomicF1 Championship Round 4</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomicf1-championship-round-4</comments>
                    <description>Turkey I felt pretty optimistic about a good result at Turkey. I&#39;d done a fair bit of practice on time trail mode and in Grand Prix mode, and qualified well (3rd on Expert, in my Ferrari - up on Alonso at least). It&#39;s always tricky using that to judge online play. Take Malaysia as example. I qualified second in our comp but couldn&#39;t get my Ferrari off the back of the grid on Expert levelin Grand Prix mode :S Anyway, I qualified a disappointing fourth. I take solice that it was only a few tenths off second. I got swamped off the start and dropped back to last, before a bunch of noobs in front me fucked up and I got into third behind Flouncy. I can&#39;t remember Turkey very cleary now, but I do remember dropping it a number of times in a number of places. Second last after a handful of laps, I resigned myself to spending it as a test session and tried a variety of angles of attack on the corners, combined with different wheel turning angles. I&#39;m of the impression that I&#39;m turning to hard each way and it&#39;s understeering. I can&#39;t see it in the cockpit view I race in but it&#39;s fucking attrocious in the T-bar view (that may just be the view messing up my reference points). Anyway I tried putting less steering input into each turn and I think it made a difference. Hard to tell on hard and worn tyres. Either way I was glad and disappointed to see the end of the race. My lack of pace in general over the last few races is starting to cause me some concern and I began to think about playing with the way I setup my controller.  Montreal I was pretty confident of getting a good time around Montreal. My Time Trial time was solid and it&#39;s an easy circuit. On my first lap out in quali I tried to eat my dinner (home made pizza, yum!) while driving. Result? Nose meets wall. Nose goes red. Limp back to pits and come out easy on my first timed flying lap, shifting up early and generally not trying hard. Second flying lap and I focussed hard, pushing pushing pushing. I was rewarded with a time 0.3s slower than my initial flying lap. A time that would be my quali time. I felt I had a good 0.8s improvement in my, if my splits where anything to go by, buy second place on the grid was good enough. I got off the line ok, falling back to third, which I got back on turn 1. Lambo wasn&#39;t pulling away over the next lap. Then I spun and got past. I spun again a lap later. Then spun again pushing hard through the right left kink on the back straight before the hairpin (did that another time too). When I came out of the pits I was just behind Ghost and about 38seconds behind Flouncy, who I assume hadn&#39;t yet pitted.&#160; Ghost and I had a good battle for the next 12 or so laps. I&#39;d close it up to with 0.4, then I&#39;d slip a bit in the dirty air. I&#39;d catch back up and slip a bit again. Then Ghost would slip and I&#39;d be right back in it. I got him on lap 21 I think and set about chasing Flouncy for second place. Suprisingly I caught him (and dropped Ghost) pretty easily. I saw Flouncy make a few mistakes as I got closer, and I assumed his tyres were shot. I put a clean but hard pass into Turn 8 and kept it planted firmly in the middle of the track, brake late and wide while I tried my best to keep Flouncy from cutting back under me and getting round the outside. Happilly we didn&#39;t colide and I managed to drive off into Force India&#39;s first one-two of the season.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomicf1-championship-round-4</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomicf1-championship-round-4</guid>
                    <pubDate>Tue, 22 February 2011 04:29:00 </pubDate>
                </item>
                <item>
                    <title>2011 Atomic F1 Championship Round 3</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomic-f1-championship-round-3</comments>
                    <description>It all went wrong on Sunday morning when I spent 3 hours (6am - 9am) pounding round the awesome Daisy Hill Mountain Bike park with some mates. Ok it really went wrong midday Sunday when I used time trial to setup my car up for the race. The time trial setup was schmick. Consistently faster than my previous setup. Never did set a full lap on it though. That should have been enough warning. However, I set it as my DQL (Dry Qualifying setting).&#160; Quali was a mess. I set my banker and decided to go about pushing for a fast lap. I was on a half decent lap and hit Ghost how had spun coming down the hill. Broke my nose. By the time I got to the start of sector 3, I was still 0.6s up on my previous best, crash and broken nose and all. Then I clipped the inside of the final turn and got stuck. With 30 seconds left in quali it was game over.&#160; Then F1 2010 did something it started last weekend and usually does at the end of every session. Lock up. The random grid saw me at the front and I eased cautiously off the line, aware of the possible, nay problable, carnage at turn 1. That&#39;s how and where Mark84 and Flouncy got by. And I left them plenty of room. I dropped it at the top of the hill and committed the carnal mistake of taking out my own teammate :( I dropped it a few more times that lap, and with broken nose (yellow only) continued on for the next 8 laps, falling increasingly behind. I pitted on about lap 8 for options and set off. Broke my nose a few turns later and returned to the pits again. I did have a good turn of speed mid race, when I found some consistency and unlapped myself from Mark84 and Lambo, having caught them at a good few (3 to 5) seconds a lap once one fresh options with a lightening fuel load, only to drop it again a few laps later. The happened a couple more time and by lap 25 my real world activities got the better of me and I floored it into the wall (129G!) and went straight to bed, a physically and emotionally broken man.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomic-f1-championship-round-3</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomic-f1-championship-round-3</guid>
                    <pubDate>Tue, 22 February 2011 04:24:00 </pubDate>
                </item>
                <item>
                    <title>2011 Atomic F1 Championship Round 2</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomic-f1-championship-round-2</comments>
                    <description>Round 2 saw intense competition at Malaysia and China .  I wasn’t expecting much from Malaysia, as when Practicing in Grand Prix mode I was unable to qualify any better than bottom 6, even with the Ferrari.&#160; It seems Malaysia is a tough track for everyone, because I slotted in in second place on the grid, just.  The race itself was pretty uneventful as I race from second place into a second place finish.&#160; Flouncy was untouchable the entire race.&#160; I did enjoy some close racing with HolyGhost79 and he probably would have got me had he not speared off the track at the hairpin in the closing stages.  China quali was a mess for me. I pretty much only play F1 2010 for the Atomic comp now I play iRacing and I hadn&#39;t turned around China since the last AtomicF1 event. I kept missing my braking point big time at the end of the straight. I&#39;d either brake then accelerate to the corner or see the braking marker at about 75m and go flying off the road across the gravel trap. did that a few times in the race too - I think I needed to practice before the event. Maybe next time . And some wet weather practice.  The race was ruined by my start. I tried to take it easy but the wet really caught me out. I lost it on the first corner and then the second and the third. By the time I got going I was a good 20 seconds behind 2nd last place (Fredzfrogz). I managed to catch back up to fourth pretty easily but made no impression on Lambo, where the time remained pretty much constant unless someone made a mistake. Every few laps it would go up or down by a few seconds. I started Melbourne on inters and that was what China felt like; like I had started that race on inters too! When I switched to in wets @ Melbourne I immediately had more grip and felt like it was dry. I expected pretty much the same behaviour in china. Wrong!  The track started to dry a little at about the same time my front left went yellow.&#160; I was going to leave it for another lap but seeing as it went yellow on that hairpin at the end of the straight I thought I may as well pit now, just in case. Unfortunately I was too busy thinking about all these things and forgot to select Inters. Result I came out on primes. To my surprise, the primes were immediately as grippy, if not moreso than the wets in the still raining conditions! My first timed lap on slicks was 2 seconds faster than my previous. then it only got better. I think Lambo must have spun though because I caught him pretty damned fast. Before I pitted he was 22 seconds in front of me and I must have been catching at 5+ seconds a lap. Was fabulous.  Otherwise, China sucked. I still raced into a lowly position.&#160; I feel my race was definitely ruined by my start and my poor qualifying.  If I’m to stay in the hunt for the championship title, I need to perform better at the next round.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomic-f1-championship-round-2</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/2/2011-atomic-f1-championship-round-2</guid>
                    <pubDate>Thu, 17 February 2011 18:29:00 </pubDate>
                </item>
                <item>
                    <title>Domain Driven Design introduction</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/2/domain-driven-design-introduction</comments>
                    <description>At my work we have initiated FridayForum&#39;s, which are an Open Space style gathering where any interested staff can participate in conversations about their favourite software topic. &#160;The intention is for any employee to present a tech talk on a topic of their choice, and engage their peers in a discussions, hopefully gaining some new insight as well as imparting some knowledge. &#160;the intention is that each session is about an hour long. &#160;I thought I&#39;d go for 30 mins, and we ended up with nearly 2 hours of engaging discussion.  I kicked off the first session with an introduction to Domain Driven Design. &#160;I&#39;m not going to write up the entire discussion in prose, but here are the notes. &#160;With the video to come, once production has been completed.  Domain Driven Design  History   Software traditionally implemented using Transaction Script pattern.  Business logic becoming increasing duplicated over time – DRY violated.  As technology evolves the problem domain becomes increasingly complex.  Need a way to succinctly model the problem domain and reduce maintenance times.   &#160;  What is DDD?   Strives to model the problem domain using object oriented techniques.  Follows the Domain Model pattern (see Fowler’s PoEAA).  Separates important business logic from secondary concerns such as infrastructure (and persistence) and presentation.  Complexity is reduced by providing consistent terminology of artefacts used by both developers and the business, known as the ubiquitous language .  Fights increasing complexity keeping related business logic in the one responsible class.  Uses a layered architecture enabling a the domain model to be isolated within its own assembly and interact with other concerns via interfaces.  DDD is a model of the business and it’s activities.&#160; The persistence mechanism should not influence the model.   &#160;  How is DDD used?   Identify business entities and build up a domain model&#160; using the relationships between entities.&#160; Easily done from the Use Cases / User Stories describing&#160; the system.&#160; &#160;An Entity is identified by one defining property.&#160; EG a Person entity is Id’d by social security number, or drivers license.  Use the business language to name your entities.&#160; Eg. Shopping Cart, SKU, Album, Wishlist.  Analyse the domain model and extract non-obvious domain entities (may not be physical entities).  Identify Value Type objects – Value Types have no identity and should be immutable.  Iterate.  Identify Aggregates and Aggregate Roots .&#160;  Aggregates are clusters of data that are treated as a single unit for the purpose of data changes.  Aggregate roots are entities through which access to other entities should be made.&#160; The most obvious candidates for Aggregate roots are entities who are the 1 in 1:M aggregate/composition relationship.&#160; EG Invoice is the Aggregate root when dealing with InvoiceLineItems.&#160; An invoice line item is meaningful only the in the context of it’s invoice.    A model has multiple Aggregate Roots and entities can be both Aggregate roots and&#160; participate in another relationship where it is not the Aggregate Root.&#160; Depends on the boundaries you identify from the Use Cases / Business.  Once Aggregate Roots have been identified, One repository per Aggregate Root is created.&#160; When an Aggregate Root is returned, all related entities are also returned from the repository call.  Determine Bounding Contexts .&#160; Sometimes multiple models exist in the one system.&#160; Bounding Contexts helps isolate models and ensure entities are misused.&#160; EG Wishlist may mean something different to the bigpond movies and bigpond music models.&#160; The context helps determine where the entities of a particular model can be used.&#160; Thereby reducing the chance of misuse or polluting another model/context.&#160; Also definite team members to work in the context.  Use continuous integration to ensure bounding context is always valid.  Where translation between Bounded Contexts is necessary use a Context Map .  Anti-corruption layer ’s protect the domain model from being corrupted by entities from a foreign domain.&#160; Typically used when communicating with third parties, and acting as a service boundary and providing translation between the two domains.  Typically implemented using the Fa&#231;ade or Adapter patterns.     &#160;  DDD is not…   Anaemic Domain models: basically DTO’s and using the application layer services to implement business logic in a procedural manner.  Domain entities contain business logic.&#160; Application layer services should only contain logic that cannot be assigned to an entity.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/2/domain-driven-design-introduction</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/2/domain-driven-design-introduction</guid>
                    <pubDate>Sat, 05 February 2011 02:29:00 </pubDate>
                </item>
                <item>
                    <title>2011 Atomic F1 Championship Round 1</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/1/2011-atomic-f1-championship-round-1</comments>
                    <description>The 2011 Atomic F1 Championship, a series organised by Lambo, pits racing geek against racing geek with Codemasters F1 2010 as the platform.&#160; Races are held at every Sunday 6pm EST.&#160; Each round comprises to F1 2010 races over 40% with cars on equal performance setting and no aids, although reduced rules/flags is used.  I’ve been playing iRacing this year and F1 2010 felt a bit different.&#160; Mainly the speed was different, coming from a Mazda MX-5 to a Formula 1 car, a few differences are to be expected.&#160; I did spend about 30 minutes re-acclimatising myself to the feel (physics) of F1 2010 prior to racing, but moving house this past week put a damper on practice sessions of worthwhile durations on F1 2010 or iRacing.  This year I’m driving the Ferrari and racing with Lambo, last years championship winner.&#160; I expect our closest competitors to be the Flouncy/Mark84 team, as both were strong performers last year.  I’m hoping that this year my Title aspirations are bolstered by my recent iRacing experience, and so far I think it had helped.&#160; At the start of last season I was mentally prepared to dominate 100%, like do my real life friends when it comes to racing.&#160; I was at first shocked that I was clearly immediately fastest.&#160; Although I think my time trial times indicate a high level or raw speed my race aggression and resulting errors gave me poor results.&#160; More humbled by my introduction to iRacing and the talent there, I’m approaching this season more cautiously.&#160; Firstly I don’t want to get caught up in a need to win.&#160; iRacing being my preferred playground means I’m not really caring what I do in F1 2010, just that I have fun.&#160; Now the races are 40% I’ve got more time to make things happen.&#160; That sense of urgency I felt in 20% ‘sprints’ is gone and I don’t need to make passes on the first corner, because I can be assured that many people will make many mistakes during each race.&#160; The most important thing is to stay on track and stay consistently quick.  &#160;  Race 1 – Bahrain  The opening race was Bahrain, where the weather was dry and hot.&#160; Qualifying was interesting. My first timed lap was the qualifying time.&#160;&#160; I intended for that lap to just be a sighter and took it pretty easy.&#160; Unfortunately, as they tend to do, nerves go the better of me and I stuffed my other attempts up.&#160; Still, third on the grid was good.  I got away from the line good, passing Lambo for second place and approaching Flouncy in the lead.&#160; Remembering my goal for this season and noticing Lambo, my teammate, was to my left and had the inside line to turn 1, I left plenty of room and he got his spot back.&#160; Flouncy made a few errors in the next few turns and Lambo got by into the lead.&#160; I nearly made it through to but received the slightest of taps and it was enough for Flouncy to keep second place while Lambo pulled away.  The next 10 or so laps were perhaps the most fun I’ve had in the Atomic F1 comp to date. We both raced cleanly, and there were numerous swaps of positions between us.&#160; It appeared that Flouncy (as did Lambo) had a speed advantage down the straight, while I was faster through the tight stuff.&#160; I’ll say it’s down to driver skill, but it’s probably that I set my car up more for down force (6 front wing and 5 rear) and my 7th gear ratio was wrong. I wasn’t lighting the top few lights just before turn 1’s braking zone.  I watched Lambo pit on lap 8 (clearly he wasn’t that far ahead) and both Flouncy and I continued.&#160; My tyres were still in optimal condition.&#160; On lap 11 Flouncy had pulled out a 3.4s gap and that gap started to increase.&#160; I dived in to pit on Lap 13 and to my amazement came out in second, a few seconds up on Lambo.&#160; The next lap round I drop it coming out of turn 1 / into turn 2 and spun, giving Lambo the change to get by.  I followed closely, inspecting his gearbox for a few laps before losing it at the same place Flouncy did on lap one and hitting the nearby wall.&#160; Thankfully no damage was done, but with 2 laps left and a 30 second gap back to fourth, I cruised home, happy with my third place finish.  &#160;  Race 2 – Melbourne  I’m a fan of Melbourne. It’s my home race and it’s good fun.&#160; Pity it was belting down rain. I qualified fourth.&#160; Again, I knew I could go faster but I ran out of time.  I took a massive gamble on my opening stint tyre choice and selected intermediates.&#160; Unfortunately the rain was really hard and I while I survived the first corner chaos, I then lost it at the next, and pretty much every other corner for the next few laps.&#160; I dawdled round in second last place barely touching the throttle; hoping the rain would clear (65% chance of rain).&#160; It didn’t, and I pitted for full wets, coming out 2 minutes and&#160; 9 seconds behind the leaders.&#160; I pegged that gap for the next several laps.&#160; The full wets felt like slicks and I had bucket loads of grip and confidence.&#160; I was catching fifth and fourth, having their own battle, at about 5 seconds a lap!&#160; I past them when they pitted about lap 17 but my tyres were shot and I pitted on lap 20 for dry option tyres, relegating me back to second last.  I’m left to dream about what could have been if I didn’t try and second guess the weather.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/1/2011-atomic-f1-championship-round-1</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/1/2011-atomic-f1-championship-round-1</guid>
                    <pubDate>Mon, 31 January 2011 23:00:00 </pubDate>
                </item>
                <item>
                    <title>Linq to Sql and the Unit of Work pattern</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/1/linq-to-sql-and-the-unit-of-work-pattern</comments>
                    <description>I come across an interesting problem a few weeks ago.&#160; One that I should have been aware of didn&#39;t show it’s head during testing, thanks to me testing in short periods.&#160; Before I launch into the problem and my solution, I’ll take a few minutes to describe the application.  &#160;  Background  For the last 6 years I’ve been developing billing systems for phone airtime retailers, in particular satellite&#160; phones (although the particular type of phone is irrelevant to the software).&#160; My main produce is a web application and website, with support Windows Services.&#160; One Windows Service provides logging, exception handling, email, and other infrastructure, communicating with the other applications and service via WCF.&#160; The other Windows Service executes scheduled tasks and monitoring functions such as importing call data from various airtime providers and performing health checks and monitoring important state changes of business entities (such as when Contracts expire or come off suspension).  The Website and Web Application architecture follows Domain Driven Design paradigm with POCO business entities and a Repository Pattern Data Abstraction Layer, coordinated by an Application Layer.&#160; For data access I chose Linq 2 Sql, partly because at the time of initial development it was a new technology I wanted to explore.&#160; The scheduled task runner is relatively simple and the Import service was implemented using SOA with it’s own set of layers.&#160; It uses Linq to Sql for both data access and for its simple domain model. I use constructor injection with a service locator to set the repositories for the import service.&#160; This means when the Scheduled Service loads of it’s various tasks and monitors, the repository is created and injected into the import service.&#160; The Linq to Sql repository creates the DataContext in the repository constructor, one context per constructor (an obvious problem if you’ve just read the below).&#160; In both applications, service and web, I use TransactionScope to manage what is effectively a unit of work.&#160; Bad, I know, but I never got around to correctly implementing the unit of work pattern.  &#160;  I have console applications for hosting the WCF services and the and scheduled services when I’m debugging.  &#160;  The Problem  A few months ago a customer was testing on the staging/test environment, where the import service is executing in a Windows Service that never gets restarted.&#160; The tester would occasionally get emailed a message notifying that a contract could not be found for a few calls, even though he could see the phone number associated with an active contract in the web application.&#160; I tested on my machine and could not replicated the problem, even using the same test dataset.  &#160;  How Linq to Sql Repositories should be implemented  (or where the ASP.NET MVC book by Guthrie et al threw me off course)  A couple of weeks ago, with my bug list down to a bare minimum I decided to tackle this problem, especially with go-live looming.&#160; Anyone who has worked with Linq to SQL should know this, but for those that don’t, Linq to Sql uses the DataContext to communicate with the database.&#160; A DataContext is also an implementation of a Unit of Work and meant to be short lived, for only the duration of the business transaction.  &#160;  When I started working with Linq 2 Sql, the first thing I worked on was importing call data.&#160;&#160; I used the series of blog posts by Scott Guthrie and the ASP.NET MVC Nerd Dinner application to implement my repositories.&#160; This sample code created the DataContext in the constructor of each repository, assigning it to a member variable.&#160; I did the same.&#160; The Nerd Dinner application created the repository was created when the controller was created, so I used the same repository lifecycle and the repository lived for the live of the service class, which in turn lived for the life of the Windows Service application.  A DataContext instance does not recognise new database records created by other contexts, such as Linq to SQL in another application.&#160; This was the root cause of my problem.&#160; I’d create or modify an entity in the web application and the DataContext in the Windows Service hosted Import Service would not recognise the change.&#160; I never saw (or never noticed due to not repeating) this problem in my short running tests because I’d spool up the command line host for only short periods of time, resulting in a new DataContext being created for the Import Service.&#160; The Windows Service however never got a new DataContext and the problem was found.  On top of this pretty fundamental flaw, caused by me not properly recognising that, as the MSDN library said, the DataContext is intended to be short lived, I recognised that the DataContext was not acting like a true Unit of Work, in that it was not shared amongst repositories involved in the business transaction.  To fix this problem I needed a simple way of creating a DataContext in the application layer for the period I needed it.&#160; I also needed a simple way to share this DataContext amongst repositories involved in the business transaction.&#160; Skip forward a few days of trial and error and I came up with the follow:     IUnitOfWork  Lightwight interface representing a UnitOfWork. As a DataContext already acts like a UnitOfWork, I decided it should implement this interface.    UnitOfWorkManager  Static helper class to provide an ambient IUnitOfWork / DataContext to the repositories    RepositoryBase  An abstract base class all repositories implement    UnitOfWorkScope  Similar to a TransactionScope , the UnitOfWorkScope provides a one shared UnitOfWork for all repositories in that scope. The scope also calls the underlying UnitOfWork ’s Commit method when all work is completed.     &#160;  Public Interface IUnitOfWork   Inherits IDisposable   Sub Commit() End Interface  &#160;  Not much to this interface, as the DataContext handles most of the resposibility of change tracking.  Public Class UnitOfWorkManager   Private Shared _current As IUnitOfWork   Public Shared Property Current() As IUnitOfWork     Get       Return _current     End Get     Set(ByVal value As IUnitOfWork)       _current = value     End Set   End Property End Class  UnitOfWorkManager keeps track of the current unit of work.&#160; This class could do with more work, such as if multiple units of work need to exist at the same time.&#160; At the moment, it assumes all currently executing code shares the same unit of work.&#160; A problem for multithreaded code.&#160; Another easy change could be to return a new Unit of Work / DataContext if there is no current context, but not assign to be the current.  Public MustInherit Class RepositoryBase   Private _db As MyDataContext   Protected Sub New()   End Sub   Protected Sub New(ByVal db As MyDataContext)     _db = db   End Sub   Protected ReadOnly Property Db() As MyDataContext     Get       Return If(_db, CType(UnitOfWorkManager.Current , MyDataContext))     End Get   End Property End Class  All Linq to Sql repositories implement this abstract base class and make use of the ambient DataContext it provides.  Public Class UnitOfWorkScope   Implements IDisposable   Private _disposed As Boolean   Private _unitOfWork As IUnitOfWork   Private Shared _runningScopes As Stack(Of UnitOfWorkScope)   Public Sub New()     If UnitOfWorkManager.Current Is Nothing Then       UnitOfWorkManager.Current = New MyDataContext(ConfigItems.ConnectionString)     End If     _unitOfWork = UnitOfWorkManager.Current     RegisterScope(Me)   End Sub   Public Sub Commit()     _unitOfWork.Commit()   End Sub   Public Sub Dispose() Implements IDisposable.Dispose     Dispose(True)     GC.SuppressFinalize(Me)   End Sub   Private Sub Dispose(ByVal disposing As Boolean)     If Not disposing Then Return     If _disposed Then Return     UnregisterScope(Me)     _disposed = True   End Sub   Private ReadOnly Property UnitOfWork() As IUnitOfWork     Get       Return _unitOfWork     End Get   End Property   Private Shared ReadOnly Property RunningScopes() As Stack(Of UnitOfWorkScope)     Get       If _runningScopes Is Nothing Then         _runningScopes = New Stack(Of UnitOfWorkScope)       End If       Return _runningScopes     End Get   End Property   Private Shared Sub RegisterScope(ByVal scope As UnitOfWorkScope)     If scope Is Nothing Then Return     UnitOfWorkManager.Current = scope.UnitOfWork     RunningScopes.Push(scope)   End Sub   Private Shared Sub UnregisterScope(ByVal scope As UnitOfWorkScope)     RunningScopes.Pop()     If (RunningScopes.Count &amp;gt; 0) Then       Dim currentScope As UnitOfWorkScope = RunningScopes.Peek()       UnitOfWorkManager.Current = currentScope.UnitOfWork     Else       UnitOfWorkManager.Current = Nothing     End If   End Sub End Class  The UnitOfWorkScope uses the current unit of work as supplied by the UnitOfWorkManager if it exists.&#160; If not, it creates a concrete unit of work (we could use IoC here).&#160; Scopes are pushed onto a stack and popped off the stack when the Dispose method is called.&#160; When the stack is empty, the DataContext is destroyed.  &#160;  Partial Class MyDataContext   Implements IUnitOfWork   Public Sub Commit() Implements IUnitOfWork.Commit     Me.SubmitChanges()   End Sub End Class  &#160;  Finally the DataContext itself implements the IUnitOfWork interface.  This all ties together in an application layer coordinating service:  Public Class MyService   Protected _repository1 as Repository1 = new Repository1()   Protected _repository2 as Repository2 = new Repository2()   Public Sub New()   End Sub   Public Sub DoSomeWork()     Using uow = new UnitOfWorkScope()       Dim thingOne = _repository1.GetBy(3) &#39; Get thing with Id = 3       thingOne.Name = &quot;Test&quot;       _repository1.Update(thingOne)       Dim thingTwo = new Thingo()       With thingTwo         .Name = &quot;Jimmy&quot;         .Age = 12         .Occupation = &quot;Software Architect&quot;       End With        _repository2.Insert(thingTwo)       uow.Commit()     End Using    End Sub End Class</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/1/linq-to-sql-and-the-unit-of-work-pattern</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/1/linq-to-sql-and-the-unit-of-work-pattern</guid>
                    <pubDate>Sat, 29 January 2011 19:01:00 </pubDate>
                </item>
                <item>
                    <title>Brisbane floods in Bellbowrie</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/1/brisbane-floods-in-bellbowrie</comments>
                    <description>Where to start with this one.&#160; This event is easily the most catastrophic disaster I have ever been personally involved in (I’m excluding a few software projects).&#160; We’d been hearing reports of floods up north for a few weeks prior to this.&#160; My mother, and three of my siblings live at Maryborough, which was recently engulfed in flood waters, with some pretty impressive pictures shortly there-after appearing on Facebook.&#160; I could nearly track the waters progress by Facebook photo updates alone.&#160; My high school stomping ground of Beerwah wasn’t long after.&#160; Pictures of water crossing the streets, and places where I’d never seen water while I’d lived there, were posted.&#160; Then came Toowoomba.  From the images coming out of Toowoomba, it was ravished by a deluge, with torrents of water roaring through the main street, taking all in its path.&#160; At work, we joked, wondering how this was possible; Toowoomba is high up on top of a hill.&#160; What we later learned is Toowoomba is pretty much nestled inside a crater on the top of a hill (and thinking about the drive in, it makes sense), so the waters hit the hills and flowed down to the bottom, the main street, where it came together before heading down the mountain, taking everything in its path: Tree, vehicles, homes, towns.  Still the heavy rains continued and the Wivenhoe Dam, built after the disastrous Brisbane floods of 1974, to ensure Brisbane wouldn’t get a repeat, started to fill.&#160; Only a few years ago the Dam was at 8% capacity and we were all worried about it going dry and Brisbane running out of water.&#160; No such fear now, as dam levels surged (no pun intended) past 100%.&#160; Wivenhoe has a capacity of 200%, 100% for ‘normal’ and another 100% capacity for flood mitigation.&#160; Scheduled releases of water had to be made to ensure the dam did not get to 200% and more.&#160; These releases helped increase the volume of water in the Brisbane river.  Driving to work on Tuesday morning I noticed water starting to appear in places where it definitely should not.&#160; Place where I’d not seen it before.&#160; My wife and I agreed that if the rain persisted we’d come home from work, to ensure we could get our kids from daycare and not be cutoff from them and our homes.&#160; We knew that Moggill Rd goes under in heavy rain and have been lucky not be cut off once before.  By 9:30am it was obvious the rain wasn’t easing, and Day care phoned parents asking them to collect their children, so staff who didn’t reside in Bellbowrie could get home before the were cutoff.&#160; I left work and headed in to get the kids, finding the wet roads like peak hour, as many other had the same idea.&#160; I thought I’d duck in the shops and get some bread and milk, just to be sure.&#160; When I got the local shopping centre, I saw something I’d never seen before.&#160; Every single car-park was full and cars were circling, looking for a space.&#160; I decided to aborted.&#160; We’d been shopping on Sunday and had enough stocks for the week.&#160; I grabbed the kids and my Wife (from the bus stop, so she didn’t have to walk in the pouring rain) and went home.  By midday I thought it prudent to get some supplies.&#160; We had no Pepsi Max! and I went down to Coles again.&#160; I’d also been checking BOM and noticed the Brisbane River at Moggill was set to peak at 22m, 7m higher than the radio had said 3 hours earlier.&#160; I did a quick guestimation and figured that’d easily but the shops under water and food might become scarce.&#160; Coles wasn’t any better.&#160; All the Bread and fresh fruit and vege was gone.&#160; I did managed to get some milk and an Ice Break though.  Initially I thought the buying at Coles was panic buying; that people were overreacting. &#160;I was considering that Coles would go under so we&#39;d be without access to food supply for a few days. &#160;I didn&#39;t really get that Coles would be out of action for a while, weeks and perhaps months.  I also didn&#39;t consider the power would go off for days because of flooding, and until flood waters subside. &#160;Something that could be over a week based on reports at the time.  I didn&#39;t think of what would happen when the gas in my BBQ ran out.  Initially I was thinking I&#39;d have 3 or 4 days at home, stuck inside, watching TV and playing Video games. &#160;Sounded OK to me.  The rest of the afternoon was spent with one eye on the TV news reports and the other on twitter and online news, with 612 Brisbane streaming live coverage through my PC; the vision being streamed of Toowomba - flabbagasting.  I was a little concered, given my position on the Brisbane River, west of the majority of Brisbane, closer to the Wivenhoe.    Still, for me it was something new and I was yet to appreciate the devastation to come.&#160; I was nearly entertained by it all.&#160; Sad I admit .  Log of events whilst isolated in Bellbowrie because of the water level  January 11, 2011     Time   Description    0630  Took the kids to kindy at Mother Duck, in the soon to be flooded area    0645  Driving to work I noticed the high water levels in the areas that flood with heavy rain for extended periods    0645  Driving to work I noticed the high water levels in the areas that flood with heavy rain for extended periods    0930  Left work to ensure I could pick the kids up from daycare and that I wasn&#39;t left stranded on the otherside of the river    1000  Stopped at the local shopping centre to get supplies from Coles. Every carpark taken and people searching for spots to park. I didn&#39;t bother stopping    1150  Went down to Coles and found a spot. Bread, Milk, Fruit &amp;amp; Vege all gone. Trolleys lined up from checkout to back wall of every aisle. Thank god we did our shop for the week 2 days ago    &#160;  Flood level predicitions adjusted up from 15m to 22m at Moggill     January 12, 2011     Time   Description    0240  Power goes off and I start thinking about food going off and how I will make coffee in the morning. Sirens are sounding in the background. It&#39;s just got real.    0600  I get out of bed and boil water for coffee on the bbq. Breakfast as usual. The kids had &quot;pink&quot; milk with their breakfast, trying to use as much milk as possible before it goes off    0800  Got in the car for a survey of damage in Bellbowrie   Coles is flooded  Kindy is flooded  Water up to shopping center entrance on Birkin Road  Moggill Ferry tied up. Power poles normally on river banks are now in the middle of the river. River is fast flowing with large chunks of debris moving through.  Hawkesbury Rd is cut in multiple places  Kangaroo Gully Rd is cut near the Pet Motel  Moggill Rd cut near McIntyre center (which usually floods)  There is no way out of Bellbowrie and Moggill by road      1300  Attempted to get some sleep. It&#39;s hot and humid.    1400  Afternoon inspection of flood areas.   Coles &amp;amp; Shopping center has water up to 0.30m below the roof.  Corner of Church and Haweksubry Rds very flooded.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/1/brisbane-floods-in-bellbowrie</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/1/brisbane-floods-in-bellbowrie</guid>
                    <pubDate>Sun, 16 January 2011 19:34:00 </pubDate>
                </item>
                <item>
                    <title>My first day as an iRacer</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/1/my-first-day-as-an-iracer</comments>
                    <description>I’d heard about iRacing a while back, maybe a little over 12 months, but I’d also heard the graphics were rubbish, and it wasn’t a Fomula 1 game.&#160; That was enough to put me off bothering to look, especially when F1 2010 from Codemasters was just around the corner.  F1 2010 turned out to be a little worse than I wanted (read my initial impressions here ) and the online play, while fun is tained by people using grip hax and sloppy network code, amongst Many other bugs in the game, least of all the way cars are setup.&#160; I have been wanting something more realistic for a long time, ever since Grand Prix 4.  Yesterday&#160;a guy I raced with in the Atomic F1 2010 competition gave his recommendation of iRacing and knowing what a motorsport fan, and games review he is (former editor of Atomic magazine) I decided to consider it properly.&#160; I watched a videos and read a few reviews on Friday evening and was enthralled, and at $12 for 3 months thought I’d give it a shot.  I wasn’t disappointed and boy is it difficult.&#160; I also love the sound of the MX-5 that comes as the entry level car.&#160; Coupled with my Logitech G27, the use of the H-pattern gear-shifter and clutch make for a great experience.  Here’s some of the features:   Has their own governing body F.I.R.S.T., to regulate the championships, equivalent to F1’s FIA.  Runs world wide championships with $10,000 to the grand champion, as well as other perks.  Is&#160; serious ‘sport’, rather than a computer game.&#160; Thing of iRacing as like any other motorsport, except there are no physical cars and tracks.  A license system awards and takes away points for any on track incidents (contact, losing control, coming off the track), it rewards clean driving over winning (though that&#39;s big points). As you move to a higher license class, better cars open up. This completely eliminates the idiot factor, and reduces accidental contact to very rare.  It&#39;s a full championship that lasts 12 weeks (I think), with you best 8 race results counting as your points. So you race every day, not trying too hard to win, just stay on and stay clean, and in the end that&#39;s what&#39;s rewarded - so there&#39;s no more desperate lunging at the first corner - if things don&#39;t work out in a race just race again to keep your average up.  The car feel is beyond amazing. By far the best car physics ever.  The range of cars is awesome. Something for everyone.  They constantly update, patch, release new cars and tracks. This is the WoW of racing - in terms of developer care.  There are Aussie servers.&#160; Many many Aussies play. Races are always a full field.  Scheduling is usually a new race every hour or two, per series.  Every week or so they switch tracks (in a series), so you get a good go, and no more week of practice to see it all fall apart in the single race that matters (like we do with F1).  All the racers are very very good, and take it seriously, though it&#39;s welcoming for noobs. Everyone uses real names too. A nice touch that helps keep it serious.  There&#39;s an F1 car, a Williams and it shits on the Codemaster (F1 2010) cars from the moon.  There’s series of Driver instructor videos that coach you in your quest to become the next Mark Webber.&#160; From my limited experience with V8 supercar school on the gold coast, this inclusion is realistic and good advice and adds to the depth of the entire simulation.   &#160;  My first experience in iRacing was driving the Legends Ford ‘34 Coupe around Lime Rock Park. &#160;My immediate impression satisfaction with how realistically the car handled my inputs, and how the braking and acceleration effected the vehicle when turning. &#160;Within the first hour I’d got my time into the 1:05’s, which is, at least, better than another first timer I read on a blog . &#160;I had a quick go of the track in the Mazda MX-5 and was impressed with the way the clutch and gears worked realistically, even if that meant I was a terrible gear changer. &#160;I kept getting the coordination wrong and gears wouldn&#39;t go in, costing me valuable time. &#160;Well I have been driving automatics for the last 6+ years.  My Atomic friend informed me of the Races held every hour, with this week being Laguna Seca, for Rookies.&#160; Cool! I wanted to try out the corkscrew and did about 30mins practice before joining my first race, managing a qualifying time of 1:47.8 , which while not good was still a decent starting place, especially as I’d only been playing the game for about 2 hours total.&#160; I got punted off the track on the first lap and broke my car, leaving it pulling to severely to the right and my lap times terrible. I finished last.  I did two more races and all went the same way, me finishing last.&#160; My rating is now down to 2.05, from 2.50 and instead of getting closer to jumping up out of the Rookie class, I’m sinking further back.  I started getting a bit dispirited with my ability, weird, cause it was only my first day, and less than 6 hours total experience. However, I like motorsport and racing cars, and I fancy myself a bit.  Another Atomic member, and long time player told me to try the regulated Practice sessions, that include other drivers, to give me a better feel for the race environment.  I have just come off my first hour long practice (formal Practice, not Test) session now and it&#39;s like a switch flipped. Not sure why, but pretty much straight away I was not getting incidents and wasn&#39;t varying wildly with my braking. Better yet my feel for the car has come good; I can lose it and slide (albeit widely) using opposite lock , the clutch , and the gas, and not spin, go off track, or get a car control caution. I&#39;m also hitting the corkscrew consistently and plenty fast enough.  I&#39;ve just turned in a 1:46.482, which while not fast, at least put me in the middle of the pack between 1:45.057 at the top and a 1:49.020 (if you discount the 2:07.314) at the bottom. Best yet, because I&#39;ve now got a feel for the car and can get it to respond consistently to my inputs, driving has become a whole bunch more fun. &#160;An example being turn 1/2 at Leguna Seca. &#160;It&#39;s approached in 5th (in the MX-5 Cup car) and shifted down into third. &#160;It&#39;s basically a double apex turn, at least the way I drive it, although I&#39;ve seen other racers brake late and run to the edge of the track and make it a single apex. &#160;Anyway, earlier in the day if I braked near or on my limit I&#39;d lose the back end and spin out of control. &#160;Tonight I was still braking and the back end would come out and try and turn me but I&#39;d balance the steering, throttle and clutch to get the car to slide and still continue on, not recording a Loss of Control incident.  Inside iRacing&#39;s website is a detailed profiling system that makes Career Mode in F1 2010 look ridiculous. &#160;Here all your statistics are listed for every racing you participate in (something else I didn&#39;t mention is that iRacing uses your real name, and not some garble of characters like in online racing games, adding to the simulation).  Simply put iRacing is the “game” (I must be careful there as it’s not a game, but a simulator) I’ve been wanting for years.&#160; With an active team and an ever growing list of tracks and cars, I’m going to be here a while.&#160; The business case for my permanent simulator cockpit just got very real.  Update: I&#39;ve just finished listening to the first iRacing podcast and the guy pretty much exactly shares my opinion. &#160;It&#39;s a salient thought I did have when I first turned on iRacing. &#160;We both are in our mid 30&#39;s and both were avid World of Warcraft fans. &#160;With iRacing we&#39;re gaining skills that actual help us improve in the game. &#160;In WoW, you&#39;re questing and getting gold and armour pieces etc, and sure, you&#39;re gaining some better understanding of shot rotation and how to defeat bosses etc, but iRacing has a direct benefit to how well you can do in the sim (also apparently to how well you can do on real life race track). &#160;Both are similar in a way, WoW is a MMO (massively multiplayer online game) and iRacing is a MMO Sim, with 20,000+ members. &#160;And both are massive time drains. &#160;Serendipitously I cancelled my WoW sub last weekend, bored of the game and it&#39;s repetitive time wasting for no real benefit.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/1/my-first-day-as-an-iracer</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/1/my-first-day-as-an-iracer</guid>
                    <pubDate>Sun, 09 January 2011 08:39:00 </pubDate>
                </item>
                <item>
                    <title>Update your playlist and energise your workout</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/1/update-your-playlist-and-energise-your-workout</comments>
                    <description>For me, working out with tailored music dates back to when I trained in my garage gym a little over 10 years ago.&#160; I&#39;d put on my favourite Rammstien CD and get psyched up.&#160; I kept the angry vibe going by working out on the speedball or boxing bag between sets.&#160; Generally I&#39;ve always lifted weights to the soundtrack of loud music, the angrier the better; however, a few months back I stopped listening to my usual play list (headphones broke) and used the music of the gym, which is generally whatever is playing on V or Max on PayTV.&#160; After a while I noticed my gains weren&#39;t coming and I wasn&#39;t lifting as much as I used to.&#160; Then I remembered the last time this happened, and the cure was to go back to blasting my ears with my usual playlist.  Working out to your own playlist allows you to customise the music to what really motivates you .&#160; Some people find energising dance music helps give them a boost, and I&#39;m sure others might benefit from even classical music.&#160; I guess it depends on personality.  Wearing earphones also has the added benefit of allowing you to ignore anyone who might talk to you.&#160; I come to a gym to workout, not to socialise, and getting caught up in 5 minutes of conversation not only wastes what little time I have available, but cools me down and takes away my focus.&#160; With earphones in, I ignore whomever might be trying to strike up a conversation with me.&#160; Easy to do if you can&#39;t hear them!  I&#39;ve used the same ordered playlist for so long now that I time my workouts by it.&#160; It seems like my body knows what I&#39;m about to do based on the song that&#39;s playing, or about to play, and it responds accordingly.  Something else I was considering last night was stress .&#160; Loud music is stressful, and playing loud music may possibly give a small boost of adrenaline that you not get if the music volume was less.  I personally experience about a 10% increase in the weight I can lift each rep (with the same rep and set volume) when using my personalised playlist.  Here&#39;s my current playlist:     Korn  Freak on a Leash, Got The Life, A.D.I.D.A.S., All in the Family    Metallica  Fuel, Enter Sandman, Wherever I may Roam    Rammstien  Bestrafe Mich, Weisses Fliesch, Wilder Wein, Du Hast, Laichzeit    Soulfly  Back to the Primitive, Mulumbo, Jumpdafuckup, Umbabarauma    Pearl Jam  Yellow Leadbetter, Black, Porch, Why Go    Guns &#39;n&#39; Roses  Sweet Child O&#39; Mine, Out Ta Get Me, You Could Be Mine    Foo Fighters  Best of You</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/1/update-your-playlist-and-energise-your-workout</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/1/update-your-playlist-and-energise-your-workout</guid>
                    <pubDate>Thu, 06 January 2011 22:17:00 </pubDate>
                </item>
                <item>
                    <title>2011 New Years Resolutions</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2011/1/2011-new-years-resolutions</comments>
                    <description>We all know how hard general life new year resolutions might be so I&#39;m not going to make any here. Besides, I think any resolutions that important should be made as soon as you decide you need to resolve to do it.&#160; Instead, I&#39;m going to make my new years resolutions around technology I want to explore this year.  1.&#160; Windows Phone 7 development .&#160; This year I want to make at least one application in both Silverlight and XNA for Windows Phone 7.&#160; Obviously the Silverlight app will be some sort of business application, a form based sort of thing, while the XNA app will be a game.&#160; I have a few ideas for a game but I don&#39;t want to put anything concrete down yet.  2. Develop for the Cloud .&#160; It looks like cloud based services are here to stay.&#160; This year I want to develop a cloud back app.&#160; Perhaps I should be a little more specific.&#160; I want to write something that backs onto Azure or S3.&#160; I&#39;m not sure exactly what that means, but by the end of this year I want to be &quot;across&quot; cloud architecture and when and were to use it.  3. Participate in an Open Source project .&#160;&#160; Over the years I&#39;ve borrowed from Open Source, branching and making my own modifications to the trunk.&#160; This year I want to contribute to an Open Source project that I believe in, or start my own open source project.&#160; I&#39;m currently leaning to my favourite and most used open source program, media browser.&#160; There are a bunch ways I&#39;d like to extend that and this year I resolve to do at least one of those.  There they are.&#160; There&#39;s only three, but they are three that I think could easily absorb my free time in 2011.&#160; Lets see how I&#39;ve done come 31 December 2011.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2011/1/2011-new-years-resolutions</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2011/1/2011-new-years-resolutions</guid>
                    <pubDate>Sat, 01 January 2011 22:22:00 </pubDate>
                </item>
                <item>
                    <title>Dieting</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/11/dieting</comments>
                    <description>I don&#39;t think there&#39;s a real reason I decided to go on a diet starting this Monday just past.&#160; Perhaps I was feeling a bit fat.&#160; Or it may have been that I needed a short term goal to focus on.&#160; Whatever the reason, Monday just gone I started dieting, with the goal to 5 kilograms by Christmas.&#160; I thought it might also be interesting to give an example of the foods I&#39;m eating (I am eating the same food each day) so anyone interested can see what may work for them.  I should give some context to the diet and that context is me, my body type and what I do in a typical day.    A few years ago, starting January 1st 2008, my wife and I started the CSIRO diet, following the books exactly.&#160; When I started, I was 108kg and when I finished, three months later I was 89kg.&#160; Back then I was weight training (as I have been since I was 14) and but not cycling.&#160; I was pretty much getting much less cardio, with a walk/run on the treadmill for 20mins after every workout.  Following the formal end of the diet, we maintained healthy eating habits, loosely following what we had learnt in from the CSIRO diet.&#160; Though now with more kids, following the diet tightly proved a real challenge, both because the kids need lots of carbs and we don&#39;t have as much time or energy to prepare the food.  I stayed at about 91-93 until the start of 2010 when I hit the gym hard and while I still pretty comfortably fit into the same shorts I did when I was 89, I do notice I&#39;m they are a little tighter and the next belt buckle up is more comfortable.  I&#39;m 5&#39;10, and as of Monday, 95.6kg.&#160; I&#39;m not fat, I&#39;m big boned.&#160; Nah, I weight train 3 to 4 days a week (if you&#39;re new to this blog).  I&#39;m a software developer (again, if you&#39;re new to this blog) so I sit down for at least 8 hours a day, although I also sit down on my PC for a few hours each night.  I also like to cycle (again, if you&#39;re new to this blog), putting in 2 to 3 rides a week of between 30 and 40km each day.&#160; Amounts to about 1300 C for that day of cycling.  I hit the gym for between an forty-five and sixty minutes each session, going pretty hard.&#160; I used my Garmin&#39;s heart rate monitor to measure my calories intake for a typical session and it&#39;s about 700 C, with heart rate averaging 140 bpm.  I&#39;m also using Daily Burn to track how many calories I&#39;m eating.&#160; Being an analytic guy, being more precise with the calories going in and out helps me stay motivated.&#160; I can focus on making sure the numbers are right.  Daily Burn tells me I need to eat 2710 C to maintain mass homeostasis, which is a fancy way of saying I need 2710 C to maintain my body weight if I was completely sedentary.  &#160;  My plan was to replicate the CSIRO diet as best I could.&#160; That meant cutting out carbs and stopping my daily Ice Breaks for lunch, along with 100g packets of chips a few times a week, and Subway or other takeout at least once a week for lunch.&#160; It means stopping eating all takeout / junk food until Christmas.  Breakfast          Eaten at either 5:20 am on ride days or 6:20 am on non ride days.   Two weetbix  150ml of Trim Milk  Glass of 100% Orange Juice  Black double espresso (not pictured).   That&#39;s similar to what I had been eating, except half as much weetbix and no sultanas.&#160; I probably should increase that to three weetbix, or maybe through on some toast or eggs or both.      Morning Snack        My mid morning eaten at 10:00 am.  The fruit contents varies from day to day.&#160; This photo includes   1 Banana  Bunch of grapes  1 Apple   Today I&#39;ve got watermelon, rockmelon, banana, grapes.&#160; Tomorrow it might be a 1 Orange, 1 Apple, 1 banana etc.&#160; The point being it&#39;s not just 1 piece of fruit, it&#39;s a few.&#160;&#160; Don&#39;t be scared though, there&#39;s still less calories than two large biscuits or a piece of cake. Heaps less in fact!&#160; I think around 150 C according to Daily Burn.      Lunch        Lunch is a greenish salad with Tuna.   Spinach leaves  Corn kernels  Beetroot  Fetta Cheese  Tomato  Onion  95g Tuna (springwater drained)   All washed down with a Pepsi max (1 calorie)      Mid Afternoon Snack  I&#39;m getting pretty hungry by mid-afternoon and down a protein shake in water with 38.4g of protein and about 110&#160; C.&#160; That&#39;s enough to keep me going until dinner.  Dinner  Dinner does actually vary from salad and steak, to grilled onion + capscicum&#160; + carrots + broccoli + cauliflower and steak, and even pasta, yes pasta of some variety.&#160; Tuesday night was pasta back, and last night was rice with chicken and vegetables.&#160; Washed down with a&#160; Pespi Max.  Post Dinner  At about 9:00pm I go for a skinny cow ice cream in a cup , about 130 C and another protein shake, ending my day with more like 1600 C.  So with my body needing about 3500 to 4000 calories a day, 1600 calories isn&#39;t a whole lot and I&#39;ve been finding myself pretty weak in the afternoons.&#160; Gym workouts haven&#39;t really suffered but once I stop doing stuff I feel like going to sleep or feel a bit wonky.&#160; Cycling is definitely more difficult though.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/11/dieting</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/11/dieting</guid>
                    <pubDate>Thu, 25 November 2010 21:02:00 </pubDate>
                </item>
                <item>
                    <title>2010 Formula 1 Review</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/11/2010-formula-1-review</comments>
                    <description>Now the final race of the 2010 Formula 1 season has been run and won, the drivers and constructors titles decided, I can offer my thoughts on the season.&#160; This isn&#39;t a review of the season on a race by race basis.&#160; It&#39;s more a qualitative assessment of the performance of the drivers and personnel that mattered.&#160; I&#39;m also biased, so I can only offer my thoughts on the personalities I&#39;ve been following.  Sebastian Vettel  Vettel started the year as my equal favourite driver.&#160; Equal with Mark Webber.&#160; I admired his relaxed and friendly approach to F1 throughout the 2009 season, as he appeared a warm and engaging character, a driver with personality.&#160; My opinions of his character deteriorated as the season progressed. Most notable was that incident at Turkey where Vettel turned right on Webber and caused them both to spin off the track.&#160; Fortunately for Webber he was able to regain and continue through to third position.&#160; Appropriately Vettel retired on the spot.&#160; The incident didn&#39;t end there. Vettel removed himself from his car and proceeded to circle his figure around his right temple, expressing his opinion what Webber was looney and at the same time making it clear he didn&#39;t hold himself responsible in any way.&#160; Red Bull were their own worst enemy in this incident, with the moronically lopsided Helmut Marko ranting about Webber.&#160; Christian Horner, Red Bull team boss also backed his man Vettel, then he didn&#39;t, then he did.&#160; The public were not impressed and Vettel lost mega repuation points in many peoples eyes that day.&#160; I doubt his reputation has yet recovered.  Vettel can only win from pole and even then most often he can&#39;t win.&#160; Lets look at the stats: 10 poles in 2010. 5 wins.&#160; That&#39;s a terrible conversion rate.&#160;&#160; Vettel won the Malaysian GP from third, the European GP from pole, the Japanese GP from pole, the Brazilian from second (Hulkenberg had pole but Vettel got him by turn 1), Abu Dhabi from pole.&#160; So while Vettel has technically won from grid slots other than pole, he still &quot;can only win from pole&quot;.&#160; I don&#39;t remember a race in 2010 where Vettel&#39;s driven through the field to win, or even to a decent position.&#160; Granted it&#39;s hard to drive through the field when you&#39;re starting from the front row more often than not.&#160; But still, my qualitative assessment has him as not being able to come from behind for a win, when a few of his peers can (more on that later).  Credit where credit is due, the last few races were dominant performances by the 2010 world champion.&#160; Webber looked like a second class driver by comparison.&#160; It&#39;s on that basis, and the reality that he had bad luck, that I give Vettel as a worth world champion.  Fernando Alonso  The 2010 season started well for Fred.&#160; Vettel&#39;s retirement gave him a win that be probably wouldn&#39;t have otherwise got.&#160; But he was in second, the first to capitalise.&#160; Ferrari seemed to lose their way a bit from then onwards to Silverstone.&#160; I has contemplating Alonso mid season and wondering what all the fuss about him is.&#160; Why is it that so many people proclaim him as the most gifted driver in F1.&#160; To my eyes he doesn&#39;t carve up the pack like Hamilton, and he wasn&#39;t dropping the car on pole every race like Vettel.  &#39;Nando had the audacity to come out at Silverstone and announce to the world he was going to be the 2010 world champion. I laughed so hard I nearly peed myself.&#160; Then came Germany and an Alonso win.&#160; Over the next eight races he won four times, scored a second place, and a two thirds.&#160; Going into the last round he was leading the World Championship and his 2008 runner up teammate was no where.  I get it now.&#160; Alonso avoided my radar by driving perfectly.&#160; He drives to the limit of the car, and not beyond it, and he thinks hard about what&#39;s going on in the race.&#160; He is the best driver in the world.&#160; No doubt.  Mark Webber  My heart&#39;s favourite.&#160; 10 days younger than me, an Aussie, a straight shooting guy who&#39;s had to bust his nuts to get where he is.&#160; There&#39;s a whole lot to admire when it comes to Mark Webber.&#160; Up until he was joined by Sebestian Vettel, he also had the best qualifying record against teammates of any driver except maybe Schumacher, but no where near the one-sided benefits afford by Ferrari to the most successful driver ever.&#160; Webber had seen off Hiedfeld, Rosberg, Coulthard, Pizzonia, and more.&#160; Those first three names are more than testament to his speed. Unfortunately Red Bull and Vettel had different plans. Maybe it was age.&#160; Maybe it was just a lack of top end ability (the later most likely) but Webber had no answer for Vettel.&#160; Never really looked like he had a response.&#160; That said, of all the teams, Webber was the closest in outright performance to his teammate, and his Monaco drive was inspired (as it was with Williams in years past).&#160; Mark had good mid-year momentum going and for a while I really started to believe Australia would have a F1 world champ for the first time since I had started following F1, in 1987.  Unforunately I think Mark has missed his one opportunity. A near perfect storm of circumstance gave him this opportunity and I don&#39;t see it being repeated next year.&#160; I&#39;m hoping that Red Bull will offer him more support in 2011, now that their backing of Vettel has been justified with a world drivers championship.  Lewis Hamilton  The king of aggressive driving finished fourth in a title standings and were it not for Singapore and Italy he may have been world champion.&#160; At least he can take some consolation in the reality that the McLaren was not the fastest car on the track.&#160; A season long consistent reality.&#160; I was extremely impressed with how Hamilton could carve up through the field better than any other.&#160; But I suppose that is also his Achilles&#39; heel. Point in case both Italy and Singapore.&#160; If Mclaren build him a worth car in 2011 Vettel will have a very hard time defending his title.  Jenson Button  Mr smooth did proved he was exactly that this year.&#160; While he definitely did not have the measure of his teammate on pure speed, he certainly had it in tyre conservation.&#160; More than once Button managed to run very long into the race thanks entirely to his smooth driving style.&#160; He scored to wins early in the season through what I class as lucky guesses with the weather.&#160; On a dry track he looked the slowest of the top five and I think deserved to finish in fifth place in the title race.   Fillipe Massa   The sixth driver in the top three teams. Fillipe should have performed better than he did.&#160; He might have won that single race in Germany, but other than that he hasn&#39;t looked like being even remotely in the same league as his double world champion teammate.&#160; I don&#39;t know if Massa has &quot;lost it&quot; after his 2009 accident, but he surely is not the second driver Ferrari need.&#160; If I were Stefano, I&#39;d be showing Massa the door.  Robert Kubica  I&#39;m only going to mention Kubica because various commentators make a big deal of him, like he&#39;s the second (or third) coming.&#160; Kubica is fast and he absolutely crushed his teammate this year.&#160; That&#39;s not hard to do though, when your teammate is a rookie and flies off the track more often than not.&#160; A solid driver and definitely one to watch out for next year.  Nico Rosberg  I almost forgot to include Rosberg and deciphering his performance would require an Engima. &#160;Lets start with his teammate for 2010, the imcomparable? Michael Schumacher, holder of every record in F1 worth holding. Literally. &#160;No teammate has ever weather the Schumacher storm and I don&#39;t think Schumacher has ever been beaten by his teammate in the end of year championship (excluding his part first year in 1991). &#160;This year Rosberg made Schumacher look silly, slow, and old. &#160;He didn&#39;t just beat him, he embarrassed him pretty much all season long. &#160;Rosberg did a Schumacher to Schumacher. &#160;The thing is, Schumacher is in his forties and F1 is a now a young mans sport. Look at champions of late. &#160;Lewis Hamilton, youngest ever world champ in 2008. &#160;Vettel, youngest over world champ in 2010 and youngest pole sitter, and youngest race winner. &#160;Alonso only just missed out on being the youngest at his first turn too. &#160;At the time he was the youngest ever race winner. &#160;Enter a middle aged man who&#39;s been out of the game for three years. &#160;A man that started F1 when the current world champion was meagre 4 years old!  So how do we measure Rosberg&#39;s success with the team that last year won the world drivers and constructors titles? &#160;Rosberg didnt&#39; win a race in 2010. &#160;He never looked like winning a race. He did get a podium or two, so I guess that should be commended.  I like Rosberg. &#160;He&#39;s a neat, fast, and tidy driver, and an articulate dude (I had a chat with him at Crown Casino poker room at the 2009 Melbourne F1). &#160;But will he be world champion one day? &#160;The talk about Rosberg has swung away, with the hacks now all talking about future champ Bobby K (Robert Kubica) and the current Vettel, and 2008 Lewis Hamilton. &#160;Those drivers are now touted as the stars of the now and the future and Nico has been swept aside. &#160;I hope next year changes for Nico and that Schumachers efforts this year are evident in next year&#39;s Brawn. &#160;With the 2010 season only a day behind us it&#39;s not too early to be talking 2011 and Nico&#39;s chances for his first race win. &#160;I hope he gets it.  Kamui Kobayashi  The young Japanese driver impressed me this year.&#160; He scored some great results and drives, given his car was a complete pile of crap at the start of the year. He made the experienced Pedro De La Rosa look antiquated and slow.&#160; The introduction of Nick Hiedfeld as teammate for the last few races provided a better indicator, and Kamui rose admirably to the challenge.&#160; Heidfeld outqualified him 4-1 and but Kobayashi outraced him (finished higher) when his car didn&#39;t let him down.&#160; I&#39;d like to see the young Japanese driver in a better team, but I&#39;m not sure where he can fit in.  Adrian Newey  He gets special mention for being friggin&#39; awesome.&#160; For those that don&#39;t know, Newey isn&#39;t a driver, he&#39;s an engineer.&#160; Ok, saying he&#39;s an engineer is like say Jesus was a man. Newey is God of the garage. As Technical Director for Red Bull he worked his brilliance on the outfit and gave them the RB6 (and last years RB5), easily the class of the field.&#160; He is a man driven to perfection and as demanding and competitive as any driver to have been on the grid. Perhaps moreso.&#160; When he releases his (auto)biography, and I hope he does, I&#39;m on it like White on Rice.&#160; Man love right there.  &#160;  2010 Driver Rankings.  Below is my ranking of the current drivers in F1 (if some are excluded it&#39;s because they&#39;re not worth remembering). Order is important.  Tier A - Clear championship contenders on any given Sunday.&#160; Give them a car, any car, and they&#39;ll take care of the rest.  Tier B - Can win races in the right conditions.&#160; Good fast speed.&#160; Not considered championship material all else being equal  Tier C - Given a capable car should be able to regularly collect points.&#160; If given the fatest car could score podiums as often as not.  Tier D - GTFO     Tier A    Fernando Alonso    Lewis Hamilton    Sebastian Vettel    Tier B    Mark Webber    Jensen Button    Robert Kubica    Nico Roseberg    Fillipe Massa    Kamui Kobayashi    Rubens Barrichello    Michael Schumacher    Nick Heidfeld    Tier C    Nico Hulkenberg    Hiekki Kovalienen    Jarno Trulli    Adrian Sutil    Timo Glock    Vitaly Petrov    Tier D    Jaime Alguersuari    Sebastian Beumi    Lucas Di Grassi    Karun Chandhok    Bruno Senna    Sakon Yamamoto</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/11/2010-formula-1-review</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/11/2010-formula-1-review</guid>
                    <pubDate>Mon, 15 November 2010 23:56:00 </pubDate>
                </item>
                <item>
                    <title>Evolution of Natural User interfaces</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/11/evolution-of-natural-user-interfaces</comments>
                    <description>I&#39;m not going out on much of a limb here and I&#39;m assuming people have seen Minority Report.  Last week there was news of scientists getting closer to establishing the technology to enable realtime 3d holographic images. Sounds cool yeah? I bet you&#39;re thinking &#39;This will grant the ablity to watch some pretty awesome 3D movies&#39;. Well you&#39;re not thinking big enough. Lets couple this with a more evolved and precision version of Microsofts recently releaed Kinect. It doesn&#39;t even need to be much more evolved.   This device, a combination of the two emerging technologies above will give us a fantastic new user interface into our beloved computers. You could pick and choose your interaction paradigm, personalised to suit. 3d holographs would provide the objects to interact with and kinect would provide the tracking mechanisms.   If you wanted to be boring, a holographic keyboard and mouse could be used, or perhaps arrange holographic shapes gives you meaning. The interaction method could be tailored to suit your personal tastes and mental model.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/11/evolution-of-natural-user-interfaces</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/11/evolution-of-natural-user-interfaces</guid>
                    <pubDate>Thu, 11 November 2010 21:52:00 </pubDate>
                </item>
                <item>
                    <title>Left foot versus Right foot braking</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/11/left-foot-versus-right-foot-braking</comments>
                    <description>For years I&#39;ve been playing F1 racing sims and I&#39;ve been left foot braking, think the blending of the throttle with the accelerator helps with balancing the car, and it does.&#160; However, I&#39;ve never been happy with the &#39;feel&#39; of the car when left foot braking in F1 2010.&#160; I tried right footing for a bit but it didn&#39;t feel natural.&#160;&#160; My pedal setup for F1 2010 has me using the clutch as the brake, mainly because it offers less resistance than the actual brake pedal and I found it easier to moderate.&#160; When left foot braking with the harder sprung pedal I wasn&#39;t pushing it all the way to the floor the time.  I&#39;ve peformed ok left foot braking, but it still just didn&#39;t feel right.&#160; Sometimes I felt like I was trailing the brake when I could be accelerating and vice-a-versus and this was effecting my lap times.&#160; On the way to work this morning I was listening to yesterdays (#184?)&#160; Formula1blog.com podcast, where Paul Charsley talks about left and right foot braking and how left foot braking does use more petrol although it can aide balance in high speed corners.&#160; Right then I decided to really give right foot braking a proper try.  I chose Time Trials on Suzuka as my test track.&#160; Suzuka because it&#39;s the next race in my AtomicF1 racing comp and also because I don&#39;t have a ghost time on it yet.&#160; I used my race setup for this test, as I didn&#39;t care about setting a time trial time.&#160;&#160; It&#39;s been a few weeks since I&#39;ve done Time Trial, spending my time doing Practice and Career mode.&#160; The first thing I noticed was that it felt like I had driver aides on.&#160; I checked my settings and I did not, but that&#39;s how much &quot;fake&quot; grip the car gets.&#160; I felt a lot better about abandoning TT as a place to refine my setups.  I set a 1:34.xxx left foot braking.&#160; It was a tidy lap and no complaints, hitting the spots I had been hitting in Practice.&#160; Switching to right foot braking and I was 1.5 seconds faster by my second lap. I noticed the car was more stable under braking and I could brake later and turn better.&#160; While my feet felt weird, and it didn&#39;t feel as smooth, because I has literally stomping on the brake using my whole leg and then heel-toeing the gas, my god it was faster.  This is the lap time I end up with after about 20 mins trying… Note, this is a race setup not a Time Trial setup! (ie I didn&#39;t go download it from the &#39;net, I refined it myself over a few hours of&#160; (left foot braking) practice laps).    Yes. That&#39;s 12th fastest in the world, and I wasn&#39;t trying to set a Time Trial time.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/11/left-foot-versus-right-foot-braking</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/11/left-foot-versus-right-foot-braking</guid>
                    <pubDate>Thu, 11 November 2010 05:17:00 </pubDate>
                </item>
                <item>
                    <title>Fixing the GridView CSS Adapter lack of Empty data rendering</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/10/fixing-the-gridview-css-adapter-lack-of-empty-data-rendering</comments>
                    <description>Don&#39;t ask me why.&#160; No, please don&#39;t!&#160; I&#39;m playing with the CSSFriendly adapters for ASP.NET for some more CSS friendly rending of Grid Views and I just found it&#39;s not working with EmptyDataTemplates.&#160; A quick google revealed a bunch of devs complaining about it in the forums and declaring to not use CSS Adapters.&#160; I&#39;ve just invested a few hours in styling my Grid Views (and using skinning and themes. Again, don&#39;t ask :p) and I really didn&#39;t want to spend another few hours or more getting CSS to play nice with the original GridView rendering.  &quot;What the hell, I&#39;m a developer.&#160; I&#39;ll just develop me a solution&quot; :)&#160; 15 minutes later I have this:  using System.Web.UI.WebControls; using CSSFriendly; namespace TurrbalTerror {   public class SSGridViewAdapter : GridViewAdapter   {     protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)     {       GridView control = base.Control as GridView;       if (control != null) {         if (control.Rows.Count &amp;gt; 0) {           base.RenderContents(writer);           }         else if (control.DataSource != null) {           var panel = new MessagePanel();           panel.MessageType = &quot;Information&quot;;           panel.Message = !string.IsNullOrEmpty(control.EmptyDataText)                     ? control.EmptyDataText                     : &quot;Contains no data&quot;;           panel.RenderControl(writer);         }       }     }   } }  &#160;  We want to make sure that the grid doesn&#39;t have any rows because it doesn&#39;t have a data source yet, so we need to render the rows if the rows exist and then if the rows don&#39;t exist and the grid has a data source, we can display the empty data text. &#160;All that is required is that you set the EmptyDataText property on the GridView to the text you want to display when no rows are found.&#160; MessagePanel is a CustomControl I&#39;ve previously written to display and format a variety of messages to the user.  It&#39;s not a true fix because it doesn&#39;t consider whats in the EmptyDataTemplate. But for my needs it is &quot;fixed&quot; :)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/10/fixing-the-gridview-css-adapter-lack-of-empty-data-rendering</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/10/fixing-the-gridview-css-adapter-lack-of-empty-data-rendering</guid>
                    <pubDate>Sun, 10 October 2010 03:38:00 </pubDate>
                </item>
                <item>
                    <title>First ride at gap creek rd mountain bike park</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/10/first-ride-at-gap-creek-rd-mountain-bike-park</comments>
                    <description>I don&#39;t think I picked the best day to introduce myself to the Gap Creek Rd mountain bike park.&#160; It&#39;s been raining for the last few days and continues to rain today, obviously making the trails rather slippery.&#160; Add my squeaky under-performing brakes from last weekends DIY brake job and I was in for a fun time.  I had no idea which trail to pick or where to go, so I aimed the bike at the first trail I could see and started pedalling.&#160; First off I noticed a few bikes coming down the trail toward me and no bikes going in my direction, which lead me to think that these single trails might be one way only.&#160; I continued slowly, getting off the track a few times as guys in full face helmets came belting down the tracks jumping and what not.&#160; It was a hard slogged and after what felt like about 30-40 minutes I looked down at the Garmin to see how long I&#39;d been out already.&#160; 13 minutes!  Going slowly a few things became immediately apparent in these conditions and with my level of skill/familiarity with the trail/confidence with my brakes:   Need optimally working brakes  Clipless pedals are not the best idea when you need to quickly get your foot/leg out to stop falling or for other balance.  Gripping the bar with fear makes your hands ache in no time flat.  Having your saddle in a position suitable for road is instant fail, because you really need to be able to stand up a lot and move around, including getting back behind the seat for the downhills, drops, and jumps.  Logs are slippery when wet   &#160;  I eventually (after about 25 minutes) made it to the top where two guys said to me &quot;How&#39;d you stay so clean&quot;.&#160; I was pretty muddy, but they were covered in it.&#160; I explained this was my first time on the trails and they told me some places to ride and explained that I&#39;d just come up a trail suited to downhill, although it was definitely two way (I asked).&#160; I road up the road at the back of Mt Cootha and stopped to catch my breathe (try not to vomit).&#160; Rested I decided to return the way I came and try the downhill.&#160; Slowly.      The downhill was good fun though required a lot of concentration.&#160; The sort of concentration required for a good lap time on F1 2010 :p&#160; I wanted to head back to the car but got a little lost and went up another steeper, more advanced downhill.&#160; Thankfully, I think because of the rain, the trails were pretty much empty so my glacial speed wasn&#39;t bothering anyone.  I slipped and fell on this ramp thing mad of logs but wasn&#39;t going fast (ie about 4 kph) as I just got back on, walking for a while.&#160; Again I stopped to not vomit, and convinced I was now not the way I had come I turned around.&#160; Coming down, even with my brakes was moderately enjoyable.&#160; Kind of scary but still fun.&#160; By the time I got to the bottom I was feeling more confident and started to pick up speed.&#160; It helped that the track was now 1m wide instead of just quite not wide enough to fit 1 bike down :)&#160; I was hitting the jumps getting some good air (and mud).&#160; Good fun :D    Back at the car I and my bike were covered in mud.  Here&#39;s the Garmin data.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/10/first-ride-at-gap-creek-rd-mountain-bike-park</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/10/first-ride-at-gap-creek-rd-mountain-bike-park</guid>
                    <pubDate>Sat, 09 October 2010 22:26:00 </pubDate>
                </item>
                <item>
                    <title>How to bleed hydraulic brakes on a mountain bike</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/10/how-to-bleed-hydraulic-brakes-on-a-mountain-bike</comments>
                    <description>Yesterday I changed the tyres on my bike from hybrid to mountain bike in anticipation of the days ride, unfortunately the rain didn&#39;t clear so I cleaned my bike instead.&#160; Once the front wheel was back in I did a systems check and noticed the front brakes no longer worked.&#160; Ok they did, but only at about 5% effective.&#160; Time to do my first brake bleed, which I put off until Sunday (today).  The manual that came with my bike said to remove the calipers and let them hang vertically and attach a tube with a bag on the end to the fluild nipple on the calliper.&#160; First step was to procure me some mineral oil, which is the fluid used in the brake system.&#160; First step of step one was finding out where I can get mineral oil.&#160; Turns out mineral oil is also baby oil (99.95% mineral oil, 0.05% fragrance), so I ducked down to my local Coles and grabbed me the cheapest I could find: 500ml Coles brand Baby Oil for the princely sum of $2.29; this stacked up favourably to the $5.49 for 250ml of the Johnson variety and $23.50 for 1L official Shimano brake oil (100% mineral oil).  Baby oil acquired and urge to smother myself in it and roll around denied, I removed the calliper as per the instructions.&#160; I didn&#39;t have the required pipe and bag so I just let it hang and drip onto a rag on the ground.&#160; Worth a shot and I couldn&#39;t be bothered getting back in the car and driving an hour to chase down 10cm of 5ml clear plastic pipe.&#160; I followed the instructions for a while but failed miserably.  Then I flashed back to years spent helping my old man work on cars and my time spent helping him bleed brakes.&#160; I think I may have even done it for myself and mates a few times in my Uni days.&#160; For a car, one person puts pressure on the brake pedal while the other tightens and loosens the nipple on the calliper at the right time.&#160; Applying those principals to my bike I figured I should be able to do it myself.&#160; Full of confidence I put the callipers back over the discs and affixed them to the bike.  Ignoring my failed steps here&#39;s how I successfully bleed and fixed my brakes:         1. Make sure the bike is horizontal and stable.&#160; Being cheap, I secured the bike on my car rack.  2. Remove the top of the oil housing, including the membrane inside.        3. Remove the cap cover the nipple on each calliper (looks the same on the back brakes).&#160; Don&#39;t loosen it yet          4.&#160; Take the top off the mineral oil/brake fluid reservoir.&#160; If this runs dry your brake line will suck air and you&#39;ll have to start all over again.&#160; Be sure to constantly monitor the fluid level.&#160; I found full two level pumps causing fluid to squirt from the nipple is enough to need to refill the reservoir.&#160;  I used a kids medicine dispenser to ensure mineral oil didn&#39;t go everywhere.&#160; Let&#39;s just hope my kids don&#39;t get sick in the next few days!  Remember: Mineral Oil is Mineral Oil.&#160; There&#39;s not need to spend $20 on Shimano brand.      5. Now&#39;s where the real work is done.&#160; Pay attention to this because if you mess it up you&#39;ll have to start over:  With one hand on the brake lever, use the other hand to open and close the nipple.&#160; You only need to turn it slightly, maybe, 1/8 to 1/5 of a turn.&#160; Open the nipple (loosen) and pull the brake lever all the way into the handlebar.&#160; You should see oil squirt out the bottom (some oil and some air).&#160; Before you release the brake lever, close the nipple. This is important otherwise releasing the lever will suck air buck up the line and you&#39;ll have wasted your time.  Remembering to check the fluid level in the reservoir, continue this process until only oil is coming out of the nipple.&#160;  Now you can start stage two.&#160; This is a refinement of the previous action.&#160; Instead of pulling the lever all the way to the handlebar, pull it in as far as you&#39;d like the lever to be with the brakes fully applied.&#160; Remember to open the nipple before you start pulling the lever in, and remember to close the nipple before you release the lever.  Repeat this action until your brakes are working as you&#39;d desire.&#160; It should only take a few pumps    &#160;     &#160;  Oh, and remember to continually monitor the reservoir fluid level!</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/10/how-to-bleed-hydraulic-brakes-on-a-mountain-bike</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/10/how-to-bleed-hydraulic-brakes-on-a-mountain-bike</guid>
                    <pubDate>Sun, 03 October 2010 22:37:00 </pubDate>
                </item>
                <item>
                    <title>Mountain Bikes versus Road Bikes</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/9/mountain-bikes-versus-road-bikes</comments>
                    <description>I am a bit of a loner at my workplace.&#160; I&#39;m the only mountain bike rider out of six regular cyclists.&#160; We all ride to work a few times a week (at least) and up until recently have gone on lunch time rides of the 20 km &#39;river loop&#39; that takes us through Indooroopilly, Chelmer, Tennyson, Yerrongapilly, St Lucia and back to Indooroopilly on an approximate 40 minutes trip.  For the first few months we were all riding pretty much together, that is, finding it equally challenging.&#160; As the road cyclists got more conditioned I found it increasingly difficult to keep up.&#160; I know it&#39;s not my personal fitness because I borrowed my boss&#39;s bike one ride and was faster than the others.  For a while I held on to the dream that I could keep up with the road guys by just being more determined and that riding with them would make my mountain biking and overall fitness increase at a faster rate.&#160; Sounds good in theory;&#160; I&#39;m being forced to a faster pace than I would otherwise ride at and therefore am pushed harder and should improve at a greater rate.&#160; In practice I have found I cannot ride as frequently because I am pushing myself too hard and getting DOMS, meaning I can only manage only one ride like that a week.&#160; If I ride by myself I can cycle at a slower pace but I can cycle every day, thereby increasing my overall calorie consumption (a goal of mine) and getting more saddle time to condition my muscles away from the power-lifting they are used to.  I thought it interesting to post a few stats:  Here&#39;s a link to my fastest MTB lap of the &#39;river loop&#39; and here&#39;s a link to my one effort on terms bike.&#160; I&#39;m sure I could go faster on than that road bike time, given I was taking it easy not knowing what I&#39;d feel like when I got to St Lucia (expecting to hurt big-time like usual).  Below is a side-by-side comparison of my efforts on MTB and Road (from the above two links).&#160; My MTB is on the top.    &#160;  Here is a comparison from this mornings ride with Adam (term) &amp;amp; Ricky on road bikes and me on my mountain bike.&#160; I&#39;m the top line, then Adam, then Ricky.    I&#39;ve included the speed graph so we can see how the actual road speed translates to effort (as indicated by cadence and heart rate, the two most important factors) It should be immediately obvious that I had to work much harder to keep up.&#160; You can see my heart rate was much higher than both the road riders.&#160; Something that may be interesting for Adam is that his heart rate seems to drop faster than Ricky&#39;s and mine.&#160; We both drop around 20 bpm in the first &#39;dip&#39;, whereas Adam drops closer to 40 probably indicating a higher level of overall cardio fitness.&#160; This twice as large drop can be seen each trough of the heart rate graph.  During the ride the road guys were a little to fast for me to be able to catch up each time we &quot;took off&quot; and you can see Ricky&#39;s cadence drop as he drafts behind Adam.&#160; Unfortunately I had no such luck and to peddle like a hamster on speed inside his wheel.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/9/mountain-bikes-versus-road-bikes</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/9/mountain-bikes-versus-road-bikes</guid>
                    <pubDate>Wed, 29 September 2010 23:03:00 </pubDate>
                </item>
                <item>
                    <title>Initial impressions of F1 2010</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/9/initial-impressions-of-f1-2010</comments>
                    <description>If you&#39;re at this site, reading this post you may have noticed I have an entire category of posts dedicated to Formula 1.&#160; I&#39;m an F1 fan.  It&#39;s been a long time since Geoff Crammand&#39;s last installment of the seminal Grand Prix racing simulation, appropriately entitled Grand Prix (1, 2, 3, and 4).&#160; I grew up playing these games and each of them were my favourite games when they came out, and remained my favourite until the next version was released.&#160; Unfortunately GP4 came out eight years ago and there&#39;s been a massive space in the F1 gaming world since then.  I held high hopes for F1 2010 when I started seeing the videos of its production.&#160; The team at Codemasters had enlisted the help of Anthony Davidson , ex-F1 driver and all round good bloke commentator, now heard on BBC&#39;s 5Live F1 podcast (hint, go listen!) to give technical advice to the team.&#160; The team also collected an unprecedented amount of data on the tracks and handling of each car. All modelled after the current 2010 season.  F1 2010 also has a unprecedented depth of play, introducing a much needed Career mode, which can last for up to seven seasons (We can&#39;t all be Rubens Barrichello ;)).&#160; Previously a career was only possible by using third party community tools, which provided only very poor integration.  Some more of the really cool stuff is now possible in with modern broadband internet connections.&#160; Through the use of a Windows Live Game account (Xbox Live) players can share lap times with the rest of the world and their friends (separate in game tabs for the two), also downloading the ghosts of each players fastest laps, to see where you can pick up time.&#160; Sharing of setup isn&#39;t catered for in-game, but I&#39;m sure we&#39;ll see that everywhere on blogs (like this one ;)).  I had a few dramas initially getting F1 2010 to work. I believe that was because I copied the game from my SteamApps folder at work and brought it home.&#160; Either way, deleting that and re-downloading from Steam fixed my problem and I was eventually (today) able to play with my new Logitech G27.&#160; I even made a small pedal box to raise the height of my pedals.    &#160;  My initial impressions were good, if not a little humbling, after seeing some of the lap times already out there.&#160; I started at a track I hadn&#39;t raced before, Bahrain and really struggled to put together a lap for the first 30 minutes.&#160; In Time Trail mode (the only mode I&#39;ve played so far) you do times hot laps with a Ghost, and every time you run off the track your lap is invalidated (and occasionally the next lap too).&#160; At the start I was trying to hard.&#160; Expecting to just be on the pace so to speak.&#160; After I slowed down a bit and took it easier, I went faster, stayed on the track and was able to lodge a time.&#160; My first timed lap was faster than the default Ghost, so that was a start even if it wasn&#39;t saying much.  I tried Melbourne a few hours later, and was immediately on the pace; able to set a lap without invaliding, on the first attempt.&#160; Although I did still feel cramped.&#160; I also suspected the amount of steering lock on my G27 was slowing me down.&#160; That is, the steering wasn&#39;t sensitive enough and on some corners at Bahrain I had to do the knuckle shuffle to get around the corner (Turn 1).&#160; I dialled the sensitivity down from 100% to 75% (with a 5 turn experiment at 50%. egads!).&#160; Within 2 laps I was 2 seconds a lap faster!&#160; I also fiddled a tiny bit with the setup (only ride height and top gear).    My initial impression is very positive.&#160; The graphics are superb. Faultless even.&#160; Midway though the day I thought the game was a bit too easy, but changing the steering sensitivity on my wheel has made the car both more twitchy (harder to handle) and easier to catch when it breaks loose. The increase in sensitivity has removed small doubt I had.&#160;&#160; Sure, I&#39;d prefer the game had telemetry to analyse, but the Ghost and the fact the Ghost comes from my friends if I want, is so much more fun .  F1 2010 is a worthy successor to Grand Prix 4.  &#160;   Update : After I wrote the above I began the Melbourne race itself, having qualified a lowly 24th (thanks to the rain). &#160;Wanting to perform well at my &#39;Home&#39; race, I restarted the race a few times (ok, about 20 times) and I noticed some annoyances which have now been reported as fake &#39;AI&#39;. &#160;I didn&#39;t notice the timing problems described in the link, but I did noticed some idiotic behaviour which I justified at the time.  Check out the video of broken timing here (sorry gotta workout how to embed youtube vid).  Problem #1, my teammate Jarno Trulli qualified 15th. &#160;A Lotus has yet to qualify outside of Q1 in a dry session, yet here is the &#39;superb&#39; AI qualifying in the middle of the Q2 pack.  Problem #2, cars were pitting for tyres on lap 3. &#160;Thinking about it, lap 3 is lap 15 in reality (I&#39;m doing 20% race distances at the moment), so if the cars are scripted to come in, that&#39;s when they would. &#160;The option tyre would last 12 laps (20% race distance easily), so the only need to pit is one to obey the rule that states every car must run both the prime and option tyre in the race. &#160;It actually makes sense to pit early and get some clear track...  Problem #3, Trulli led the race at one point pretty much every time I restarted (probably everytime, if I went long enough into the race before a restart). &#160;That&#39;s completely wrong, but it may be justified by Trulli&#39;s tyre strategy being to pit late (ie lap 8-9 on 20%). &#160;Given only limited number of laps, he could lead the race by only being 10-15 seconds behind the leaders when they pit.  Most of annoyances could be because of the short race distance I&#39;m doing.&#160; Also, I&#39;ve found this piece on the AI on harder levels (who doesn&#39;t run the on hardest? :)). &#39; It&#39;s not on all difficulty levels, only the upper because they couldn&#39;t go quite as quick as super smooth players.&#39; (from the twitter of a developer of the game)  Thinking about the AI, I&#39;m rather disgusted at this, but perhaps there was no other choice. &#160;I&#39;d expect that many people playing F1 2010 do so to &#39;live the dream&#39; of being an F1 driver (isn&#39;t that much of the motivation behind computer games in general). &#160;Accurately depicting the drivers is paramount to a good experience in the game.  My thoughts are, if you want a realistic experience, you need to be doing longer race distances, with 100% being the obvious preference.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/9/initial-impressions-of-f1-2010</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/9/initial-impressions-of-f1-2010</guid>
                    <pubDate>Sun, 26 September 2010 05:49:00 </pubDate>
                </item>
                <item>
                    <title>Bodybuilding is all about endurance</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/9/bodybuilding-is-all-about-endurance</comments>
                    <description>Sports require certain personality traits or physical attributes that will determine a persons level of success in that sport.&#160; Runners should be lean and have low-twitch muscle fibres. Boxers need good hand-eye co-ordination and a strong jaw.&#160; Tennis players need good co-ordination and stamina, and so on.  Looking back on 20 or so years of weight training and seeing different people come in and out of the gym over the years, the attribute that strikes me as most important for a body builder/weight trainer is persistence.&#160; Going the natural route, it takes years of hard work in the gym to build a body that is going to be BIG, where people will say to you, &#39;Dude, I think you may be too muscular&#39;.&#160; If it were just a matter of time, that might be ok, but one needs to spend consistent time on the gym over a period of years. &#160;I&#39;ve seen numerous good friends come and go from the gym over the years, wanting to put on serious muscle but getting disappointed when they&#39;re not Mr Olympia sized after 3 months of &#39;hard work&#39; (lets ignore the steroid abusing pro body builders please :)).  Gaining muscle, strength and power, requires pushing the body beyond what it is comfortable doing.&#160; It doesn&#39;t get easier as you get stronger.&#160; If anything, it gets harder. As the weight increases so does the stress on joints and tendons, and the chances of injury.&#160; Breaking your muscles down to build them up also causes pain. &#160;To succeed at body building you need to be able to endure. Seems paradoxical that in an activity so focused around high-twitch muscles and short sharp bursts of power, endurance is so important. &#160;It&#39;s mental endurance that counts here, not so much physical endurance, although having a body that isn&#39;t going to break down and stop after 6 months is certainly necessary.  Remember what I said just above, about how it take years to build a decent body?&#160; That&#39;s years of near constant pain, pain that can make you not want to move at times.&#160; I&#39;m of the opinion that if you don&#39;t hurt, you&#39;re not pushing hard enough.  The other thing about BB/WT is that it&#39;s a solo sport.&#160; There are no excuses if you don&#39;t reach your goals (well, besides the cruel hand you may be been dealt by genetics :)).&#160; No one but you is going to push you to go to the gym, or to go to bed to get enough rest.&#160; No one else is going to make sure you get the right diet, and combine all these things year in year out.&#160; Having a training partner helps, but ultimately they don&#39;t know just how far you can push an those last few reps.  Increasing in mass, strength, and power is about personal focus, you get to learn something important about yourself; you gain understanding of just how much you can endure and how much you can push beyond what&#39;s comfortable.&#160; It&#39;s a great happy feeling as that massive surge endorphins come out to play, when you really start to tax your body.&#160; It&#39;s like you can and will conquer everything. Like a &#39; runners high &#39;.&#160; I know I&#39;ve had a good session when I hit that.&#160; It makes me smile, even laugh in the gym at times ( I must look mighty strange to the random people bouncing along on the treadmills).</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/9/bodybuilding-is-all-about-endurance</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/9/bodybuilding-is-all-about-endurance</guid>
                    <pubDate>Thu, 23 September 2010 22:44:00 </pubDate>
                </item>
                <item>
                    <title>Conditional breakpoints in Visual Studio 2008</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/9/conditional-breakpoints-in-visual-studio-2008</comments>
                    <description>Not sure why, but I don&#39;t remember ever setting conditional breakpoints in .NET.&#160; I&#39;ve done it countless times in VB6 in years of yore, but can&#39;t for the life of me remember doing it (recently) in .NET  It took me&#160; few minutes of looking for the watch window to stumble conditional breakpoints being in the Breakpoints window (der!).&#160; Anyway, here&#39;s a quick reminder.  1. Add your breakpoint    2. Open the Breakpoints window (Ctrl + Alt + B)    3. Find your breakpoint, right click on it and click on &quot;Condition&quot;  4. Once the Breakpoint Condition popup opens, set the Condition checkbox and enter your condition    5.&#160; Your breakpoint will indicate if it is conditional.      Now execution of my debugging software will stop in the GetElementValue method when elementName = &#39;price&#39;, the case I was having trouble with.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/9/conditional-breakpoints-in-visual-studio-2008</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/9/conditional-breakpoints-in-visual-studio-2008</guid>
                    <pubDate>Wed, 22 September 2010 21:47:00 </pubDate>
                </item>
                <item>
                    <title>One handed db presses for extra difficulty</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/9/one-handed-db-presses-for-extra-difficulty</comments>
                    <description>Last chest workout (Monday) I was wanting to add a little more difficulty to my chest workout, after easily pressing the required reps with the heaviest dumbbell set in my gym (45kg each for those keeping score at home :)).&#160; I remember an old favourite I haven&#39;t done in a while, the one handed db press.  It&#39;s simple to perform, just lie on a flat bench as usual and grab a dumbbell.&#160; I recommend using a lower weight than you would with two handed ( I used a 30kg db) and then it&#39;s a simple matter of pressing up as you would in a typical two handed db press.              &#160;  The added difficulty comes because unlike when you use two hands, you&#39;re not balanced with a single db so you need to press more slowly.&#160; And we know how increased time under tension is better for muscle &#39;damage&#39;.  Why am I mentioning this now? I was reminded of this when I got up my office chair just now and my midsection (abs, obliques, and Serratus Anterior) told me they hurt rather noticeably (not the case with my normal chest workout of flat barbell bench, incline barbell bench, flat db press).&#160; In body building land, this is a good thing :)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/9/one-handed-db-presses-for-extra-difficulty</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/9/one-handed-db-presses-for-extra-difficulty</guid>
                    <pubDate>Wed, 08 September 2010 22:23:30 </pubDate>
                </item>
                <item>
                    <title>Setting attributes of classes updated by a mocked classes in RhinoMocks</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/9/setting-attributes-of-classes-updated-by-a-mocked-classes-in-rhinomocks</comments>
                    <description>A common data pattern in most business applications is the Repository pattern.&#160;&#160; An interface for a generic repository usually looks like the following:  public interface IRepository&amp;lt;T&amp;gt; where T : DomainEntity {   IList&amp;lt;T&amp;gt; GetAll();   T GetById(int id);   void Commit(T entity);   void Delete(T entity); } public abstract class DomainEntity {   public int? Id { get; set; }   public bool IsNew   {     get { return Id != null; }   } }  Typically your service layer would co-ordinate entities and the repository to perform the required functionality.&#160; Logic concerning insertion of entities needs to test if the entity has been successfully insert by checking the IsNew property.&#160; The test for which could look like  public class Person : DomainEntity {   public string Name { get; set; }   public Date DateOfBirth { get; set; } } public class IPersonRepository : IRepository&amp;lt;Person&amp;gt; { } public class PersonService {   private IPersoneRepository _repository;   public PersonService(IPersonRepository repository)   {     _repository = repository   }   public bool InsertPerson(string name, DateOfBirth dateOfBirth)   {     var newPerson = new Person             {               Name = name,               DateOfBirth = dateOfBirth             };     _repository.Commit(newPerson)     return !newPerson.IsNew;   } }  [Test] public void Repository_insert_sets_id_field() {   // Arrange   IPersonRepository repo = MockRepository.GenerateStub&amp;lt;IPersonRepository&amp;gt;();   PersonSerivce service = new PersonService(repo);    // Act   bool result = service.InsertPerson(&quot;Test Pilot&quot;, DateTime.Now.AddYears(-30));   // Assert   Assert.IsTrue(result); }  &#160;  This test will fail though because the mock repository will not create and assign a Id primary key.&#160; We need to simulate the creation of the primary key in the commit to ensure the IsNew property on the entity returns false.&#160; This is done using the Callback method on the mock.  [Test] public void Repository_insert_sets_id_field() {   // Arrange   IPersonRepository repo = MockRepository.GenerateStub&amp;lt;IPersonRepository&amp;gt;();   PersonSerivce service = new PersonService(repo);   repo.Expect(r =&amp;gt; r.Commit(Arg&amp;lt;Person&amp;gt;.Is.NotNull)     .Callback(new Delegate.Function&amp;lt;bool, Person&amp;gt;(             person =&amp;gt; {                   person.Id = 1;                   return true;                  }           ));   // Act   bool result = service.InsertPerson(&quot;Test Pilot&quot;, DateTime.Now.AddYears(-30));   // Assert   Assert.IsTrue(result); }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/9/setting-attributes-of-classes-updated-by-a-mocked-classes-in-rhinomocks</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/9/setting-attributes-of-classes-updated-by-a-mocked-classes-in-rhinomocks</guid>
                    <pubDate>Tue, 07 September 2010 00:27:00 </pubDate>
                </item>
                <item>
                    <title>2010 Belgian Grand Prix</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/2010-belgian-grand-prix</comments>
                    <description>Red Bull where never going to have it easy at this round of the Championship.&#160; The Spa-Francorchamps circuit nestled in the Belgian forest presents the longest track on the current calendar and a unique challenge.&#160; Due to it&#39;s 7.004km length and it&#39;s undulating nature, it&#39;s a regular event to have rain on one part of the track while another is completely dry.&#160; This weekends was no different, the rain made the race even more interesting.&#160; But I&#39;m deviating now, that&#39;s not why Red Bull were going to struggle.&#160; Spa is a combination of lengthy straights and the mighty Eau-Rouge, probably the most famous and feared corner in F1.  It used to be a case where only the mega-brave or the insane took Eau-Rogue &#39;flat&#39;, some recent drivers have come unstuck try to prove to the world they&#39;ve got the biggest balls.&#160; I&#39;m talking to you Jacques Villenueve.&#160; However advances in aerodynamics and mechanical grip in recent years have tamed Eau-Rouge and we saw many drivers take the corner flat out, one handed, while the other hand operated the cards F-duct.&#160; The F-Duct disturbs / stalls airflow onto the rear wing and gives cars extra speed, as if the rear downforce has been decreased.&#160; Mclaren, pioneers of the F-Duct have the best on the grid and it was here that Red Bull were going to face a tough challenge.  Ferrari have been on the rise in the last couple of races and practice saw Alonso topping the times, but I always thought come Saturday and qualifying Red Bull would rise to the top.&#160; I wasn&#39;t disappointed, and Webber dropped his Red Bull on pole, the only driver into the 1:45&#39;s.&#160; Hamilton in the McLaren lined up alongside.  Race day and Webber bogged down on the line, and we saw his in-car footage showing the anti-stall kicking in as 5 drivers blew past him, relegating him to sixth.&#160; At the front Hamilton lead and with Button as his tailgunner, he pulled out a healthy lead.&#160; Rain fell early on and the drivers negated some slippery conditions. Throughout the race the threat of rain literally hung over everyones heads and the threat turned into action in the closing stages.&#160; Again, we saw Vettel show us why he won&#39;t be the 2010 world champion as his impatience got the better of him and he took out Jensen button and ruined his own race.&#160; I lol&#39;d.  the non surprise of the race was the ever non-surprise and &#39;somewhere there man&#39; 2008 runner-up Filipe Massa.&#160; He hasn&#39;t looked the same since his accident at the Hungoraring in 2009 and his done a stellar job of being non-descript this year.&#160; Kubica fought well to get hold in for third and he surely would have kept second from Webber if he didn&#39;t stuff his pit stop up and overshoot his box.  Schumacher showed better than he typically has this year, but still finished one place behind Rosberg.&#160; Maybe one day soon the 7 time world champ and record holder for the most race wins Schumi can actually beat his non race wining teammate.  Webber now sits in second in the championship, only three points behind Lewis, and it&#39;s all on again at Monza.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/2010-belgian-grand-prix</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/2010-belgian-grand-prix</guid>
                    <pubDate>Tue, 31 August 2010 22:49:00 </pubDate>
                </item>
                <item>
                    <title>What can we do about Fat Kids?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/what-can-we-do-about-fat-kids</comments>
                    <description>A lot is currently be said about what we can do to reduce increasing childhood obesity.&#160; Most of the blame seems to have be laid at Fast Food companies and advertising junk food on children&#39;s television.&#160; I don&#39;t go that way.&#160; I think the problem is systemic and a product of many factors around life in the 21st century.   More busy lifestyle by parents. Increased proportion of both parents workingKids are getting fatter for the same reason anyone puts on weight, they input more calories than they output.&#160; Weight control is about balancing your food intake with your energy expenditure, if you eat more food you need to do more activity.&#160; The reasons behind poor child weight nowdays are myriad.&#160; Here&#39;s some of the excuses, reasons:   Lack of education on Nutrition  Smaller house blocks  &#39;Sociopaths&#39; (ok maybe not sociopaths, but more less-desirables) on the rise and a lack of &quot;community&quot; makes parents more cautious about letting their kids play down the local parks that have been provided in the estates they live (shared space now instead of private space - see previous point).  Children are being bombarded with &quot;junk food&quot; advertising.  Shift in the way people socialise and communicate, this includes children and teenagers.&#160; i.e. the rise of social networking.  Move away from Communities in favour of precincts, such as the CBD, industrial areas, residential areas, and so on.  Parents unwillingness to accept responsibility for their children!   Ok, so with those written down, what can we do and what should we be doing as parents and members of the wider community to combat childhood Obesity, which obviously leads to adult obesity, poor health, rising hospital costs, and all the rest?   Make parents more accountable and responsible for the childs health.&#160;&#160; I&#39;m sick of parents blaming the media and the corporations for the kids weight.&#160; Ultimately the buck stops with the parent.&#160; They need to ensure their children are eating healthily and are getting the minerals they need to function and learn.&#160; The problem I see is that parents are lazy.&#160; Free short courses on nutrition probably won&#39;t help.&#160; What about online training offered as a session (or more) that can be complete in an hour or two.&#160; I&#39;d like to see a website (.gov.au) that is regularly promoted on television that offers a portal into child health issues.&#160; There are plenty of health related sites around but I think this needs to be better publicised  This won&#39;t go down well with Teachers, but how about we make the school day an hour longer and provide an hour of free sports training or at a minimum, physical activity.&#160; The government should be throwing more money at this.&#160; Many kids are in after school care anyway, so lets keep them at school longer and strongly encourage them to be active!&#160; It doesn&#39;t have to be regular Teachers providing the sport coaching, Community volunteers, like the Mum&#39;s and Dad&#39;s who don&#39;t work could give up their time on a rotating roster.&#160; Who knows, it might even help their weight.  Other issues are less direct, such as increasing the minimum allowable house block, to help provide space for kids to play and be active.&#160; Lets also return to working where we live, or least within a 15-20 minute walk.&#160; We need a more &quot;community&quot; community.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/what-can-we-do-about-fat-kids</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/what-can-we-do-about-fat-kids</guid>
                    <pubDate>Mon, 30 August 2010 06:19:00 </pubDate>
                </item>
                <item>
                    <title>TechEd Australia 2010 Day 1 (Keynote)</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/teched-australia-2010-day-1-(keynote)</comments>
                    <description>This years TechEd is held once again at the Gold Coast Convention and Exhibition Centre, for the second year in a row.&#160; Nothing wrong with the location, especially if you&#39;re from interstate, but pretty uninspiring for &#39;locals&#39; from Brisbane, such as myself.&#160; The Keynote began to Nirvana&#39;s &#39;Smells like teen spirit&#39;, which I found a little weird, given my love of the song and how big it was in my day (sound a bit old there :)), but anyway it enjoyable to here the DJ&#39;s mix of it &quot;…here we are now.&#160; Entertain us&quot;.   Microsoft Surface, the giant multi-touch tablet pc designed to act as a coffee table was brought out again and a scenario was enacted where Michael Kordahi , the host of the Key-note and a guest sat on a lounge and pretended to act and interact naturally with Surface and Windows Phone 7.&#160; Nothing new was covered and I certainly didn&#39;t suck in my breathe at the sight of the WP7 magically hooking up with surface and sync information.&#160; A touch tablet PC was also exhibited running Windows 7 and added to the scenario as the device that could be used, in conjunction with the cloud and the other two devices already mentioned to somehow add some magic to my life through the &quot;many screens&quot; paradigm. August de los Reyes spoke on Natural User Interfaces and what&#39;s next for the way we interact with our computing devices.&#160; Short and entertaining, his point was we need to investigate the differences between each interface (CLI, GUI, NUI) and use those differences to extrapolate the future of interfaces. He highlighted the&#160; He mentioned his claim to fame was designing the Windows key :)  It did give me a cool idea to add a tablet PC to my house, that docks near the kitchen.&#160; My family could use it as a central calendar and info access.&#160; The compelling argument for one hasn&#39;t been made just yet and I think it&#39;s a bit off in our future.&#160; It&#39;s not ubiquitous yet.  Kinect got a sizeable portion of the Keynote, with a demo of one of the games and just how we&#39;ll interact with it.&#160; More interesting was the brief explanation on how the device works and its &quot;view&quot; of the environment.&#160; The price was announced at AU$199, reasonable enough for me to pick one up when it comes out.&#160; Also mention was that Kinect signals a step toward more gesture based general interaction, though I can see it a long way off yet.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/teched-australia-2010-day-1-(keynote)</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/teched-australia-2010-day-1-(keynote)</guid>
                    <pubDate>Sat, 28 August 2010 08:06:00 </pubDate>
                </item>
                <item>
                    <title>Two level horizontal menu</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/two-level-horizontal-menu</comments>
                    <description>Friday night I pulled up an old site I developed that was in great need of refresh on its navigation.&#160; As far back as months ago I&#39;d wanted to use a two level horizontal menu to provide site navigation and give some awareness of the current page the user was on.&#160; The web site is actually a web application desktop app replacement and pretty much every page is a data entry page and can be neatly grouped under a category.  Months back I attempted to implement a two level menu by the usual method of searching for an example of exactly what I wanted and copy-pasting.&#160; After a few hours of fruitless searching I gave up and shelved the mini project for a while.&#160; Friday night I pulled it off the shelf and dusted it off.&#160; I started again with investigating the &#39;net for something that would already do the job.&#160; Why re-invent the wheel.&#160; By 1am I was getting to that point of losing interest until I had a thought.&#160; &quot;I&#39;m a software developer.&#160; Why don&#39;t I just make my own&quot;.&#160; With my enthusiasm building, I went to bed, promising to attack the problem in the morning.  My Web Application has the following basic structure:  - Contracts -- New Contract -- Find Contract - Invoicing -- Invoice Run -- Invoicing History - Maintenance -- Agents -- Accounts -- Companies -- Contracts -- Networks -- Plans - Control Panel -- Users -- System Settings  I had the following requirements for my two level menu:   Categories along the top level.  Sub-categories on the second level as per the above hierarchy.  Menu indicates current page  Current Category is highlighted  Current Page is highlighted.     &#160;  Saturday morning and a few hours later I had the layout and the stying done.  HTML   &amp;lt;ul id=&quot;menu&quot;&amp;gt;    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; id=&quot;contracts&quot;&amp;gt;Contracts&amp;lt;/a&amp;gt;     &amp;lt;span&amp;gt;       &amp;lt;a href=&quot;/contracts/submitcontract.aspx&quot;&amp;gt;New Contract&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/contracts/search.aspx&quot;&amp;gt;Find Contract&amp;lt;/a&amp;gt;     &amp;lt;/span&amp;gt;   &amp;lt;/li&amp;gt;   &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; id=&quot;invoicing&quot;&amp;gt;Invoices&amp;lt;/a&amp;gt;     &amp;lt;span&amp;gt;       &amp;lt;a href=&quot;/invoicing/invoicerun.aspx&quot;&amp;gt;Invoices Run&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/invoicing/invoicerun.aspx&quot;&amp;gt;Invoicing History&amp;lt;/a&amp;gt;     &amp;lt;/span&amp;gt;   &amp;lt;/li&amp;gt;   &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; id=&quot;maintenance&quot;&amp;gt;Maintenance&amp;lt;/a&amp;gt;     &amp;lt;span&amp;gt;       &amp;lt;a href=&quot;/maintenance/account.aspx&quot;&amp;gt;Accounts&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/maintenance/agent/search.aspx&quot;&amp;gt;Agents&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/maintenance/company.aspx&quot;&amp;gt;Companies&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/maintenance/contact.aspx&quot;&amp;gt;Contacts&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/maintenance/networks.aspx&quot;&amp;gt;Networks&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/maintenance/plans.aspx&quot;&amp;gt;Plans&amp;lt;/a&amp;gt;     &amp;lt;/span&amp;gt;   &amp;lt;/li&amp;gt;   &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; id=&quot;admin&quot;&amp;gt;Control Panel&amp;lt;/a&amp;gt;     &amp;lt;span&amp;gt;       &amp;lt;a href=&quot;/admin/userlist.aspx&quot;&amp;gt;Users&amp;lt;/a&amp;gt;       &amp;lt;a href=&quot;/admin/systemsettings.aspx&quot;&amp;gt;System Settings&amp;lt;/a&amp;gt;     &amp;lt;/span&amp;gt;   &amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;   Using an unordered list is the way to go with menus and while I initially tried implementing the second level as another unordered list, I had more luck (and less styling changes) using a span.  Styling   #menu {   margin:0;   padding:0;   list-style:none;   position:relative;   background:url(../images/menubg.gif) repeat-x;   height:32px;   text-transform:uppercase;   font-family: Arial, Helvetica, sans-serif; } #menu li {   float:left;   padding:0;   margin:0; } #menu a {   text-decoration:none;   padding:10px 15px;   display:block;   color:#ffffff; } #menu li:hover a, a.selected {   background:#ffffff;   color: #666666 !important; } #menu li span {   float:left;   padding: 5px 0;   position:absolute;   display:none;   left:0;   top:31px;   background:#ffffff;   color: #000;   width: 100%; } #menu li:hover span, .selectedSubMenu { display:block; } #menu li span a { display: inline; padding: 5px 15px; } #menu li span a:hover {text-decoration: underline;} .active { font-weight: bold; }   &#160;  The above worked correctly for the menu but it lacked indicating where I was in the navigation.&#160; Here&#39;s where I needed to write some JavaScript (jquery) so I could detect the current url and use some logic to select the appropriate menu item.  My physical site hierarchy reflects the categories (this is an ASP.NET Web Forms application) making it easy to determine categories, by using the id attribute of the anchor element.&#160; As long as the case is correct, it&#39;ll work.  jquery (using jquery 1.2.6)   function markActiveLink() {   var $path = location.pathname.substring(1);   if ($path) {     $(&#39;#menu li &amp;gt; a&#39;).each(function() {       var $category = $(this).attr(&#39;id&#39;);       if ($path.indexOf($category) &amp;gt; -1) {         $(this).addClass(&#39;selected&#39;);         $(this).siblings(&#39;span:first&#39;).addClass(&#39;selectedSubMenu&#39;);         $(&#39;#menu li span a[@href=&quot;/&#39; + path + &#39;&quot;]&#39;).addClass(&#39;active&#39;);              }     });     } } $(document).ready(markActiveLink);    The above jquery iterates over every top level menu item (category) and if it&#39;s the appropriate category for my url, I add the selected class category and displayed the span ( selectedSubMenu class).&#160; I then needed to find the appropriate link for the current url   $(&#39;#menu li span a[@href=&quot;/&#39; + path + &#39;&quot;]&#39;).addClass(&#39;active&#39;);   Unfortunately it didn&#39;t work as desired.&#160; The second level menu was not visible when I navigated to the page.&#160; I spent a good two hours trying to figure it out, sure that it was something simple.&#160; Finally I had to take my daughter to soccer so I had to let it go for the time being.&#160; Returning with fresh eyes, and with my brother on IM, we both looked at it and turns out it was a CSS specificity problem (doh! I knew it something simple.&#160; Hopefully won&#39;t forget that anytime soon).&#160; With my updated CSS everything worked as desired, and it only took about 12 hour. /sigh.  Updated CSS.   #menu li span.selectedSubMenu { display:block; } #menu li span.selectedSubMenu a { color: #666666; }    The final product, navigated to the Contact Maintenance page here</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/two-level-horizontal-menu</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/two-level-horizontal-menu</guid>
                    <pubDate>Sun, 15 August 2010 21:32:00 </pubDate>
                </item>
                <item>
                    <title>Solving the nested if nightmare</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/solving-the-nested-if-nightmare</comments>
                    <description>A recently read a question on a forum I frequent asking about the different ways people handle nested-if&#39;s.   I was doing something today, (C#, but it probably doesn&#39;t matter that much), where literally hundreds of statements relied on the previous being successful. At first, I started using simple nested ifs, but it just got out of hand real quick. I mean, it was in VS which handles formatting nicely, (if I was at working using the Borland C++ 5 IDE like I do at work I&#39;d be screwed), but it&#39;s still not going to cut it; I will have to come back and read this one day and I won&#39;t be able to make sense of it if I have to scroll across etc. just to read each bit.  So then I thought, well, I could have a boolean that I set true or false given the outcome of a call, and then put it in the if statement for the next; thereby bypassing all future calls. This keeps everything nicely in line, and reasonably readable, but then I thought, well heck, I could just use a for loop and break out of it. I know it seems dodgy; break and continue are not dissimilar to goto calls; but it just looks a lot cleaner.  Then I also thought I could just throw a new exception that I make myself, and just catch it at the end of the function. That&#39;s what I decided to go with in the end because I thought it was probably the *correct* way to do it, but I did think that perhaps it may be more computationally expensive than a simple break, (in this case it probably didn&#39;t matter).  What would you do, (please let me know language etc. too)?   Link here  I&#39;m not a fan of exceptions in these situations because exceptions are supposed to be for exceptional situations and&#160; there is an overhead associated with throwing exceptions (a cost a number of orders of magnitude greater! in C#). I also don&#39;t approve of throwing exceptions during validation (which may be a situation similar to what you&#39;re doing here?).  Is there a better way you could be controlling program flow? Are some of the conditionals actually guard clauses (often by inverting them)? Is it possible to use polymorphism to refactor the conditionals, similar to using polymorphism to refactor switch statements (seeing as if&#39;s could also be switches) ?  Here&#39;s the basic problem   class Program {   static void Main(string[] args)   {     bool condition1 = true;     bool condition2 = true;     bool condition3 = true;     bool condition4 = true;     bool condition5 = true;         if (condition1)     {       if (condition2)       {         if (condition3)         {           if (condition4)           {             if (condition5)             {               DoSomethingImportant();             }           }         }       }     }   }   }  You can see that maintainability is already difficult and as the conditions for execution increase the problem only increases.&#160; In some cases we can reverse the conditions and use guard conditions instead.  class Program {   static void Main(string[] args)   {     bool condition1 = true;     bool condition2 = true;     bool condition3 = true;     bool condition4 = true;     bool condition5 = true;         if (!condition1) return;     if (!condition2) return;     if (!condition3) return;     if (!condition4) return;     if (!condition5) return;         DoSomethingImportant();     } }   The guard statements get executed and nesting has been removed.&#160; In some cases we could stop here, although when using hundreds of conditions, readability is still poor.&#160; Seeing as we&#39;re using C#, an object oriented language, we can refactor the conditions as Commands and build up a composite command object that executes each command condition before calling the final desired action.   public interface ICommand {   bool Execute();&#160;&#160; }   Now the conditions implementing the command pattern.   public class Condition1 : ICommand {   public bool Execute()   {     return true;   } }  public class Condition2 : ICommand {   public bool Execute()   {     return true;   } }  public class Condition3 : ICommand {   public bool Execute()   {     return true;   } }  public class Condition4 : ICommand {   public bool Execute()   {     return true;   } }  public class Condition5 : ICommand {   public bool Execute()   {     return true;   } }   And the action we want to execute when all conditions are met  public class DoSomethingImportant : ICommand {   public bool Execute()   {     // Do Something Important      return true;   } }  And finally a class that encapsulates a conditionally executed command  public class ConditionalCommand : List&amp;lt;ICommand&amp;gt;, ICommand {   public ConditionalCommand(ICommand action, params ICommand[] conditions) : base(conditions)   {     base.Add(action);   }     public bool Execute()   {     foreach (var cmd in this)     {       if (!cmd.Execute())       {         return false;       }     }     return true;   } }&amp;lt; br/&amp;gt;  Now we input our conditional commands and desired action and execute   class Program {   static void Main(string[] args)   {     var action = new ConditionalCommand(             new DoSomethingImportant(),             new Condition1(),             new Condition2(),             new Condition3(),             new Condition4(),             new Condition5());         action.Execute();   } }</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/solving-the-nested-if-nightmare</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/solving-the-nested-if-nightmare</guid>
                    <pubDate>Sat, 07 August 2010 01:39:00 </pubDate>
                </item>
                <item>
                    <title>My favourite Google search options you didn’t know</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/my-favourite-google-search-options-you-didn’t-know</comments>
                    <description>Finding stuff on the &#39;net can be made a whole lot easier if you know the a few handy search operators.      &quot;&amp;lt;word 1&amp;gt; &amp;lt;word 2&amp;gt;&quot;  Performs a search for the phrase &quot; word 1 word 2&quot;. Not including the parentheses will search for word 1 and word 2 somewhere in the page, but not necessarily together (the typical search).    &amp;lt;word 1&amp;gt; OR &amp;lt;word 2&amp;gt;  Will search for occurrences of word 1 or word 2 but not both.    -&amp;lt;word&amp;gt;  Excludes a particular word. E.G. money -poor    ~&amp;lt;word&amp;gt;  Performs a synonym search.&#160; E.G. ~auto loans will search for truck loans, car loans, etc.    &amp;lt;#1&amp;gt;…&amp;lt;#2&amp;gt;  Finds in a range. E.G. laptop cost $1000…$15000 seaches for laptops that cost between $1000 and $1500 dollars.    inurl:&amp;lt;text&amp;gt;  Returns only pages with a url (address) containing text.    intitle:&amp;lt;text&amp;gt;  Returns only pages where the title element contains text .    site:&amp;lt;url&amp;gt;  Only search the site specified by url for the keywords supplied. E.G. funny site:www.turrbalterror.com    define:&amp;lt;word&amp;gt;  Finds the definition of word . Basically turns Google into a dictionary.    filetype:&amp;lt;ext&amp;gt;  Returns results that meet the supplied file type extension.&#160; E.G. money filetype:pdf will return all PDF documents containing the word &quot;money&quot;.&#160; This one is great for searching source code :)    info:&amp;lt;url&amp;gt;  Returns information about the supplied url (address)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/my-favourite-google-search-options-you-didn’t-know</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/my-favourite-google-search-options-you-didn’t-know</guid>
                    <pubDate>Fri, 06 August 2010 19:22:00 </pubDate>
                </item>
                <item>
                    <title>How long should you spend in the gym?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/8/how-long-should-you-spend-in-the-gym</comments>
                    <description>For a long while I&#39;ve know that 45 minutes per session has been touted as the optimal recommend time in the gym for a heavy weights session.&#160; What I didn&#39;t know was why 45 minutes.&#160; I had for a long time thought it was because the body depletes itself of testosterone and the &quot;good&quot; energy and after 45 minutes every minute is a case of diminishing returns.  I read an article today that reported something slightly different; something that in light of what else I know makes a whole lot more sense.&#160; After about 40-45 minutes of intense exercise the body starts to produce cortisol thanks to the stress it&#39;s under.&#160; (Cortisol is produce by the body during times of stress.&#160; Not just weight training stress but other stress like emotional stress).  Without boring people with long words, after that 45 minute mark the body cortisol build up results in a release of some stuff that provides the body with temporary relief from pain.&#160; However, if this goes on for too much longer this causes important (to body building) functions to come to a screeching halt (the same thing happens when doctors supply patients with corticosteroids).&#160; It&#39;s the maintained period of high intensity that does us in.  Muscles grow while you are not in the gym, while you are resting and too much gym time, even if only 45 minutes at a time, leads to overtraining and over production of cortisol, which leads to a whole other bunch of issues I didn&#39;t go into detail about like: faster aging, excessive amounts of free-radicals, and damaged DNA.  Over the years I&#39;ve tried a few different training schedules and I&#39;ve found that working each body part only once a week for about 45-60 minutes per session works the best for me.&#160; Most of my sessions are around the 45 minute mark, but sometimes I go over, usually if someone stops to talk to me (grrr) or the benches and weights I want are in use.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/8/how-long-should-you-spend-in-the-gym</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/8/how-long-should-you-spend-in-the-gym</guid>
                    <pubDate>Tue, 03 August 2010 04:54:00 </pubDate>
                </item>
                <item>
                    <title>Why formula 1 team orders are ok</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/7/why-formula-1-team-orders-are-ok</comments>
                    <description>Over the recent years there has been a lot of talk about team orders and how they&#39;re detrimental to Formula 1, especially in the Schumacher years.&#160; Throughout the Schumacher years we saw many occasions where a Schumacher team mate had to cede their position to the mighty German, the most sensational being Austria 2002. Schumacher wasn&#39;t the only one though, there has been team orders within McLaren, with Hakkinen and Coulthard too.&#160; Team orders have been around either overtly or covertly for at least as I long as I&#39;ve been following formula 1 (1987).  Austria was the most contentious because the move was blatant, with Rubens Barrichello slowing down at the chequered flag and letting Michael Schumacher by.&#160; Fans and commentators were outraged and I admit a lot of disappointment at both that move and Ferrari&#39;s obvious favouritism.&#160; As a result of that race, team orders were banned.  The big problem with banning team orders is policing.&#160; Pretty much saying &quot;Driver 2 please move over and let Driver 1 through.&#160; This is a team order&quot; is an obvious team order, but saying &quot;Driver 2, you are slower than Driver 1&quot; is not obvious and it most cases it&#39;s just a fact.&#160; A fact it may be but it&#39;s more than likely an explicit team order to move over and let the other driver through.&#160; I&#39;m certain team drivers would be instructed of &quot;code&quot; phrases that relate team orders.  In the German grand prix just run, Massa was told &quot;Alonso is faster than you&quot;, and he promptly moved over and let Alonso pass him coming out of turn 6.&#160; From that point on Alonso drove away from him, clearly faster.&#160; There&#39;s the problem.&#160; Alonso was faster than Massa, that was clear.&#160; It&#39;s the pass that might tick fans off.&#160; Alonso didn&#39;t have to work for it, Massa relived his Schumacher years and moved aside.  The way I see it, Formula 1 is a team sport.&#160; Not only is a result in the sport determined by more than just one person per car, there are two cars per team.&#160; The biggest tell for me is that there&#39;s a constructors championship, a teams championship, where at the end of each season, a team is declared the winner.&#160; Seriously, how can you have a team championship and not have team orders?&#160; It&#39;d be like soccer and not being able to instruct all your team members about the plays to use during the game.  You could argue that what difference does the order the drivers finish make to the team points, and the answer is none.&#160; However, if the lead driver is going slower than the trailing driver and the result is both end up getting overtaken, when at least one could finish further up, then that&#39;s sub optimal for the team and it&#39;s legitimate championship.  I say bring team orders back, they&#39;re a vital part of a team championship.&#160; Unfortunately that&#39;s not the case right now.&#160; The reality is Ferrari broke the rules of the sport by implementing team orders.&#160; Alonso overtook Massa, but it&#39;s not like he gained a massive boost to pass, he just maintained his usual speed, while Massa slowed.&#160; Massa slowed.&#160; That&#39;s the key here, Massa obeyed a team order and did something that possibly changed the outcome of the race.&#160; Alonso did nothing wrong by the letter of the law, regardless of if his words to the team initiated the team order to Massa.&#160; Ferrari could have said &quot;no team orders here, sorry Fred&quot; and left it at that.&#160; They didn&#39;t, so they&#39;re guilty.&#160; Massa could have ignored the team order, but that would have created much tension within the team.&#160; In most situations disobeying a directive is ground for dismissal, unless found to breech of laws (not racing or sporting laws, but actual civil law). I think it&#39;s asking a bit much to punish Massa, and therfore only the team should be punished.  What Ferrari did wrong, probably something that other teams take more care in executing was the manner in which the team order was carried out.&#160; Massa should have slowed less dramatically, or &quot;accidently&quot; oversteered out of a corner, once Alonso was close enough to discreetly take advantage.&#160; I&#39;m sure other teams knowingly execute team orders, they just exercise discretion.&#160; Team orders won&#39;t ever go away and I&#39;m sure Alonso and other drivers chase (succesfully) number #1 clauses in their contracts that in effect ensure team orders are carried out.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/7/why-formula-1-team-orders-are-ok</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/7/why-formula-1-team-orders-are-ok</guid>
                    <pubDate>Mon, 26 July 2010 17:13:00 </pubDate>
                </item>
                <item>
                    <title>One MMORPG to rule them all</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/7/one-mmorpg-to-rule-them-all</comments>
                    <description>Back in grade 11 (1996) I started to dream up my version of the future of gaming.&#160; I remember it was Grade 11 because I shared a room with my brother and it was definitely in that room that Dave had to put up with my detailing for my plan to dominate not just the gaming world, but the software world too.  Back then we gamed a lot with Quake, over IPX/SPX and a 10BaseT cable.&#160; I also gamed a lot with Grand Prix 2.&#160; A lot.&#160; Anyway, my plan was not so much to supply the games but to supply a framework: a pluggable architecture that game makers would use to plug their games into other games.&#160; My framework, which I&#39;ll refer to now as &quot;The Portal&quot; would provide an entryway to every other game in the world.  For a small recurring fee players could subscribe to The Portal and in doing so would create an avatar of themselves, perhaps even uploading a close digital representation of their real life (RL) selves.&#160; In (role playing game) RPG fashion the avatar would be assigned some base statistics and pool a points to assign to the various statistics to best represent the style of character they want to play (I realise now a better way to do this would be to get the player to answer a series of moral questions and perform a few games and puzzles to work out their play style and mental capacity).&#160; Once through the portal the player would be free to wonder through an online world where selecting a game to play is as easy as going to a location on the world.  Want to play a racing sim, travel to a race track and you&#39;ll pass through into a Formula 1 game made by a third party.&#160; The same for any other style of game; playing quake would involve travelling to maybe a spaceship that takes you to the quake planet.&#160; Maps or in Portal terminals would be provided to allow players to find location of the game they wanted to play.  Playing a game would build up attributes that would be used in the game, such as if you focused on playing first person shooters, statistics on your avatar would increase in perception and agility. Those stats would be increased across all games and your ability to succeed at games utilising those stats would be increased.&#160; It would also be possible to train your statistics outside of any game, and this would give an increase in the trained skill larger than if you just played a game focussing on said skill.  An obvious example would be if you played a boxing game you would slowly build up strength, agility, and stamina, but it if you spent time in a Portal supplied gym, your strength, agility, and stamina would increase more quickly and in turn you would be better at that boxing game.  Over time your avatar would reflect the type of games you played and in a way, the type of person you are in real life.  Why would other companies develop games to interact with The Portal?  I was hoping the portal idea would be so cool that people would flock to it, and thus, developing your game as a Portal plugin represents a great opportunity to gain access to a massive customer base.  Companies would be paid a small amount for time each The Portal subscriber spends in the game (I see a few holes in that logic now, but it sounded good circa 1996).&#160; To get started all a company (or person) would need is a copy of the SDK, which would include the game engine and a set of API&#39;s to interact with The Portal systems.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/7/one-mmorpg-to-rule-them-all</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/7/one-mmorpg-to-rule-them-all</guid>
                    <pubDate>Mon, 26 July 2010 04:42:00 </pubDate>
                </item>
                <item>
                    <title>Where is my TV on demand</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/7/where-is-my-tv-on-demand</comments>
                    <description>Consumers should demand more from their commercial content providers   Back in 2000 I first got both cable tv and cable internet connected. At the time I was a single nerd, sharing a house with another nerd.&#160; We rented a lot of movies.&#160; A lot.&#160; The local blockbuster knew us by our first names.&#160; Hell, they should have even reserved movies for us when they came out.&#160; They knew we&#39;d be in as regular as clockwork on Friday and Saturday nights, to grab the latest action or horror movie.  I guess all that girlfriend-less time gave me plenty of time to think (Keep in mind, this predates Bittorrent by a long stretch, and Napster was the only thing asserting its influence on my massive 16GB hard drive.&#160; Back on point: I got to wondering why didn&#39;t blockbuster wake up and embrace the times.&#160; Each store could be replaced with a server, or at least contain a server, filled with the digital copies of their movies.&#160; I think I actually envisaged a room full of racks containing DVD&#39;s, with a giant arm in the middle that went and retrieved them for playing; A massive version of Sony&#39;s 400 CD jukebox, which I was lusting after at the time. &#160;Anyway, this new Blockbuster would provide movies on demand, from the comfort of my own home.&#160; I&#39;d log into their website (thanks INFS3202 - Web Information Systems) and they&#39;d retrieve the DVD I&#39;d selected and stream it to my computer.&#160; Having cable I figured it was doable.  I thought more and more about it, as my trips to blockbuster increased and I by 2003 I looked forward to when we all had FTTH, which provided all our telecommunications needs: Phone, TV (on demand), Movies, Internet.&#160; I figured fibre should provide the bandwidth to give me all I wanted: and that was to never have to leave my house.  Over the next years, a number of file sharing technologies came and went, providing easy, albeit not legal, ways to obtain movies and television (there will always be usenet, but it&#39;s not very userfreindly and hard to find stuff).  For the last few years bittorrent has been king of that scene and integrating uTorrent (a bittorent client) with RSS feeds and Windows Media Center can provide a semi decent user experience for a HTPC.  What I&#39;ve also got is a Foxtel cable tv subscription, which I got pretty much solely for live motogp before OneHD telecasted motogp with any regularity.&#160; Now they do, I&#39;ve since cancelled the Fox Sport channels and don&#39;t watch foxtel myself (even though my kids and wife get plenty of use from it).&#160; My biggest gripe with pay television is that you pay $70+ a month for a whole bunch of shows that are either repeats of what is on free to air commercial TV or one of a huge number of pointless documentaries on World War 2.&#160; What my subscription buys me is a baby sitter for an hour or two a day.  Over the last couple of years the IPTV space has been improving and services like Hulu and Netflix have gained widespread acceptance as TV and Movie content providers.&#160; Hardware and infrastructure have also come a long way, with CDN&#39;s (Content Distribution Networks) spreading the content globally and providing faster access.  Australia still has an infrastructure problem, but with the National Broadband Network (NBN) starting rollout (or trials at the least) and with new estates providing FTTH and 100Mbs internet, thinks are looking good for IPTV and entertainment on demand Down Under.  This morning, while listening to the latest Hanselminutes podcast I hear that Hulu are now offering Hulu+, a monthly subscription to all their TV show content on demand many devices, for just US$10 per month.&#160; Hulu+ doesn&#39;t have the very latest TV, but it does provide access to every season of shows that are currently available on DVD.  For me, the TV and Movie On-demand business will have matured when I can pay a reasonable monthly subscription (I&#39;m willing to pay $60 a month for just TV shows, or $100 a month to include movies) for high definition content delivered to my TV, computer, mobile device, car when I want it.  I think I&#39;m not alone in this but instead of working on a solution to meet consumer demand (and the demand is there), studios are be reactionary and suing torrenters and the Australia Free To Air networks are to blame too.&#160; Instead of accepting technology changes they&#39;re stuck in trying to make as much money as they can from obsolete delivery methods.&#160; We&#39;re an ever increasing techno-savvy population and the sooner the media dinosaurs release this the happier we&#39;ll all be.  Personally, I&#39;m excited because I&#39;m currently scoping a project that should see a large Australian company make a good go of competing with Hulu, Netflix, GoogleTV, YouTube rental, and their ilk.&#160; Hopefully my decade old dream is only a year or two away.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/7/where-is-my-tv-on-demand</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/7/where-is-my-tv-on-demand</guid>
                    <pubDate>Fri, 16 July 2010 20:47:00 </pubDate>
                </item>
                <item>
                    <title>Is there an award for Software Engineers?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/6/is-there-an-award-for-software-engineers</comments>
                    <description>In my full time job as a programmer, which I undertook whilst still a uni student, I was on paid under the Clerical Award.&#160; This entitled me to the following, which was followed to the letter:    Paid $10.18/hr  38 hr week  30 minute lunch break at 12:00pm and a 15 minute break at 10:00am  RDO once a month  Holiday leave loading at 17%  Overtime as worked    I needed the cash to fund my Uni, so I wasn&#39;t complaining (out loud), but inside I wondered how a programmer, with their expected level of skill can be put under the Clerical Award.&#160; Reading through the award and how to pass through the levels (there are four) to more pay, I found if I could work without constant supervision I could progress to Level 2, once the required years had passed.&#160; That was awesome, and I moved up to $13/hr by the time I was in 3 rd year.  Needless to say when I got my first real job after graduating, I was put on a salary.&#160; I wondered though, what were my rights and should I have an award to base those rights on?&#160; -- It might be relevant to mention that in the previous job I wrote membership software for Unions, so I was pretty up to speed with the whole Award thing --.  Now, a decade later I have the answer.&#160; I&#39;m working my way through a Management degree and my current assignment has me investigating Awards.&#160; Here&#39;s what I&#39;ve found:    Information Technology Industry (Professional Employees) Award 2001 - AP812692CAV , and;  Professional Employees Award 2010 - PR988777</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/6/is-there-an-award-for-software-engineers</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/6/is-there-an-award-for-software-engineers</guid>
                    <pubDate>Thu, 24 June 2010 07:02:00 </pubDate>
                </item>
                <item>
                    <title>Now with a Like button</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/6/now-with-a-like-button</comments>
                    <description>Keeping up with the social media trend-whores, I&#39;ve added a Facebook &#39;Like&#39; button to my site.&#160; I watned to add the one requiring Javscript SDK but after messing around with it for 10 mins I gave up and went the IFrame route.  I should be finishing assignment anyway... (I think this is called procrastination :)).</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/6/now-with-a-like-button</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/6/now-with-a-like-button</guid>
                    <pubDate>Thu, 24 June 2010 05:52:00 </pubDate>
                </item>
                <item>
                    <title>DoEvents and Application Performance</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/6/doevents-and-application-performance</comments>
                    <description>DoEvents has long been held as the &quot;Fix all&quot; for any problems a VB programmer has. Time and again I&#39;ve heard developers say &quot;Just throw a DoEvents in there and that&#39;ll fix it&quot;, without understanding what DoEvents actually does.  Calling DoEvents hands control of the CPU over to the operation system. The operating system then retrieves and processes all messages in the Windows Message Queue (like paint, resize, etc). This is why when calling DoEvents inside a long running process (large loop for example), the application UI doesn&#39;t become a white screen, and you can move the form around.  The thing is, DoEvents processes all messages currently in the message queue, and thus your application will process more slowly.  A better solution is to use the PeekMessage, TranslateMessage, and DispatchMessage API calls that interact with the message queue. Using these message you can retrieve one message from the queue at a time and thus your application will complete its task faster. (Remaining message queue messages can be processed when your application work has completed and the user is looking at the UI, thinking about what to do next).  To directly simulate DoEvents you can use the following construct (when called from forms. If not called from a form, pass 0 into the second argument):   Private Type PointAPI   X As Long   Y As Long End Type  Public Type msg   hwnd As Long   Message As Long   wParam As Long   lParam As Long   time As Long   pt As PointAPI End Type  Dim lpMsg as msg  Do While PeekMessage(lpMsg, Me.hWmd, 0, 0, PM_REMOVE)   TranslateMessage lpMsg   DispatchMessage lpMsg Loop   When calling DoEvents in loops, replace DoEvents with the following construct:  Dim lpMsg as msg  If PeekMessage(lpMsg, 0, 0, 0, PM_REMOVE) Then   TranslateMessage lpMsg   DispatchMessage lpMsg End If    Details of these three win32 api calls can be found here:  PeekMessage - http://msdn2.microsoft.com/en-us/library/ms644943.aspx  TranslateMessage - http://msdn2.microsoft.com/en-us/library/ms644955.aspx  DispatchMessage - http://msdn2.microsoft.com/en-us/library/ms644934.aspx</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/6/doevents-and-application-performance</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/6/doevents-and-application-performance</guid>
                    <pubDate>Tue, 22 June 2010 21:13:00 </pubDate>
                </item>
                <item>
                    <title>Managing a Sql Server Express database over SSH</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/6/managing-a-sql-server-express-database-over-ssh</comments>
                    <description>This is an extension of Automated deploy to IIS7 using NAnt  Our staging environment resides on a virtual private server hosted by our VPS company .&#160; We&#39;re deploying the application using a NAnt script over Cygwin and SSH.  I ran into a problem executing sqlcmd (and osql) against the Sql Server Express database, getting a network error.  osql -S .\sqlexpress -d umbraco -U umbracouser -P umbracouser  Being a developer, I don&#39;t really know the inner workings of Sql Server, but I remembered that Sql Browser and TCP/IP needed to be turned on, so I followed the basic steps to do that using Sql Server Configuration Manager  Enabling TCP/IP:   but found it still wasn&#39;t working. Investigated by calling:  netstat -an | find &quot;1433&quot;  which returned nothing.&#160; So I had a play around with the TCP IP settings some more and set the IPAll entry to use port 1433.&#160; Previously it was blank    I still got no joy so I tried using the explicit tcp connections and that didn&#39;t work  osql -S tcp:10.10.10.10\sqlexpress,1433 -d umbraco -U umbracouser -P umbracouser  Frustrated I started randomly modifying the osql call (it seemed random, more of a calculated random), and found a solution.  osql -S 10.10.10.10 &#160;-d umbraco -U umbracouser -P umbracouser  With a solution working I started undoing the various things I had tried to get it to work and came up with the minimal solution   Enable TCP/IP in Sql Server Configuration Manager for the Express instance  Explicitly set the port to use in IPAll  Restart the Sql Server Express Instance.  Remove the named instance from the connection string (not sure why this works, but hey it does!)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/6/managing-a-sql-server-express-database-over-ssh</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/6/managing-a-sql-server-express-database-over-ssh</guid>
                    <pubDate>Tue, 22 June 2010 14:33:00 </pubDate>
                </item>
                <item>
                    <title>Bellbowrie Kenmore Oxley Indooroopilly Bellbowrie ride</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/6/bellbowrie-kenmore-oxley-indooroopilly-bellbowrie-ride</comments>
                    <description>RunKeeper: http://rnkpr.com/a6u6ys    This was the ride I&#39;d been dreading.&#160; Kenmore onwards is fine, I do that most every day, but it was the ride from Bellbowrie to Kenmore I thought would be the killer.&#160; There&#39;s some rather large hills on that leg, especially the one big outside the  Pinjarra Hills Sanatorium Hospital . Both in and out would be difficult, or so I thought.&#160; In reality, Bellbowrie to Kenmore was pretty mild and I did the 9.5 km ride in 23 minutes, with a higher average speed than my usual Kenmore to Indooroopilly ride to work. Sure, the aforementioned hill was hard, but it wasn&#39;t much harder than any other hill I tackle daily.&#160; At least there is a good run down the other side.  My original intention for this ride was just to see how long that leg took me, any more than that was a bonus.&#160; Twenty-three minutes being too short for a Saturday ride meant I had a to press on and Term from work had ridden Kenmore to Indooroopilly via Jindalee during the week, which made me want to check it out.  I&#39;d done a bit of aerial investigation of that leg during the week and I wanted&#160; to see if there was a way through  Rocks River Park at Seventeen Mile Rocks to Oxley that kept me off Seventeen Mile Rocks Rd.&#160; That and I enjoy riding along the Brisbane River; it&#39;s always picturesque, even when it&#39;s Brown.  Any Rocks River Park shortcut was a bit of a fail but at least I managed some cycle cross and learnt that hybrid tyres aren&#39;t really hybrid at all and offer just about zero grip once the bike is on gravel.&#160; I spied a short hiking trail up from the park and decided to take the bike up there to see where it led.&#160; It led me to the two pictures below:    From the top of the hill, overlooking the park and with the CBD in the horizon.    South from the Park overlooking Darra and Oxley.  &#160;  Coming down the Oxley side and through the gravel was ok, just as I said before, not much fun on hybrid tyres.&#160; To stay off Seventeen Miles Rocks Rd, I headed up into some estate, hoping that it&#39;d match up with what I remember of google maps during the week.&#160; I&#39;m not sure if the way I ended up going was the way I&#39;d seen on Google Maps, but it worked.&#160; There was one rather steep hill coming down Cliveden Ave, which was about the steepest hill I&#39;ve ever descending on that mountain bike on road and I was saying to myself &quot;I hope this is the right way because I&#39;m pretty certain there&#39;s no way I&#39;m going back up this hill if I&#39;ve got it wrong.&quot;&#160; Luckily, it was all cool and I came out directly opposite the Oxley train station, which I think was where I wanted to emerge.  The ride from there to Indooroopilly was pretty normal, stuff I&#39;d done a bunch during my UQ years.&#160; I did stop at Graceville for a powerade (blue of course) and a banana.&#160; Ingested, I continued on to Indooroopilly and under the train lines to head down Radnor St beside the river.&#160; Lucky it&#39;s a tight twisty road and I had no problems keeping speed with the cars through there.&#160; From there it was the usual route from Indooroopilly to Kenmore (ie Home from work), except instead of heading up Kersely Ave after the Kenmore Soccer Club, I headed through grounds and out to Marshal Lane and right onto Moggill Road.&#160; Why?&#160; Just &#39;cause.&#160; I&#39;d never been through the soccer grounds before and I wanted to see where it went.    From the top of the hill on Moggill Rd near the Sanatorium  It was pretty much smooth sailing from there, except for the ride up Moggill Road to the aforementioned Sanatorium.&#160; It was bloody hard work.&#160; The next challenge was the ride up from Bainbridge Dr to where Moggill Road breaks in two.&#160; From there it was standard riding home.  Total Journey Time: 3 hours 6 minutes Total Pedal Time: 2 hours 23 minutes  (apologies for the bad images. &#160;I only had my wife&#39;s 1.3MP phone camera with me)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/6/bellbowrie-kenmore-oxley-indooroopilly-bellbowrie-ride</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/6/bellbowrie-kenmore-oxley-indooroopilly-bellbowrie-ride</guid>
                    <pubDate>Sat, 19 June 2010 17:23:00 </pubDate>
                </item>
                <item>
                    <title>Body Building Primer</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/6/body-building-primer</comments>
                    <description>This primer is not intended to be used by someone who has never stepped foot in a gym before.&#160; It&#39;s aimed at someone who has been going to the gym for a couple of months and has decided they want to make serious gain and maximise their time in the gym.&#160; In the first couple of months you can do pretty much anything that stresses the muscles and you&#39;re going to make good gains.&#160; I&#39;d recommended not lifting heavy until you&#39;ve got your form down pat, otherwise you&#39;re more than likely going to damage yourself.  Bodybuilding can be broken into three essentially equal constituents: Training, Sleeping, and Eating. Only by paying attention to all three can maximum potential be realised.  Eating  Muscles need food to grow. If you don&#39;t eat enough food, you won&#39;t have enough energy to grow. Energy (Calories) from food has categorically three sources: Fats, Carbohydrates, and Proteins.  (I can see myself waffling on for ever here so I&#39;ll make it a little simpler, for brevities sake).  Fats for the main part are not the best place to get energy. They can be broken in to two main parts: Essential and non-essential fats. Omega 3 is an example of an essential fatty acid and can be found in many fish products, such as tuna. It&#39;s a good natural lubricant for the joints and a helps keep tendons well oiled.  Carbohydrates come in two main forms, simple and complex (but can get a little more complicated if taking GI into account). Low GI complex carbs are best for body building, and long term (throughout the day) energy. These are gained from foods such as rice (go for brown), wholegrains, sweet potatoes etc. (Simple carbs are found in sugary substances and are discernable by their sweet taste).   Application : Have a serve of low GI complex carbs earlier in the day, for sustained energy. Take high GI complex carbs an hour or so before your workout to ensure you&#39;ve got plenty of energy on tap for heavy or high rep lifting.  Protein is the most essential source of calories for body building and is the source that muscles (and other cells) use to repair themselves. Protein comes in a numerous varieties, from numerous sources. The current in protein is &#39;whey&#39; protein, which also comes in two varieties, isolate and concentrate. Isolate is more expensive and more readily absorbed and utilised by the body. An average active (non-body building) person should consume between 0.7 and 1.0g of protein per kg of body weight. A body builder should aim for between 1.5 and 2g per day. Chicken and Tuna are great examples of low fat high protein foods.   Frequency of eating is also important. Eating the same quantity of foods but spread out into more meals is recommended. Why? eating more often keeps the body&#39;s metabolism at an elevated rate. If only eating 2 meals a day, the body likes to store energy as fat to make sure it has enough to get through the day. Also, try to eat as few as carbs as possible after your workout. The reason? Your metabolism drops when you sleep and When you eat a large serve of carbs at night, your body stores the excess energy as fat. Eat a salad for dinner.  Try to aim for a Carb:Protein:Fat ratio of 40:40:20. That should give enough energy for muscle building, while maintaining a lean form. Consume your biggest meal for breakfast and gradually reduce the size (and carb content) of your meals as the day progresses.  Drink lots of water. Seeing as we&#39;re mainly water, that makes sense. In body building land: water keeps the muscles hydrated, dehydrated muscles equal muscles in catabolic state.   Fat loss rule #1: Eat less calories than you use.  forget everything else, this is the only thing you have to know (it goes get more complicated if you&#39;re trying to maintain muscle mass though, as too high a calorie deficit will result in catabolism.)  Supplements  Body building taxes the body. as a result, supplementation is essential for maximum growth. The number one supplement anyone can take is protein supplements. Hard gainers (fast metabolism and skinny) should look at getting &quot;Mass Gainer&quot; type supplements that are more biased toward carbs and have a high calorie count. This ensures that they have enough energy that their protein can be used for muscle repair and growth, not day-to-day mobility.  Creatine is great, but doesn&#39;t work for some people. The idea of creatine is that it aids energy transport within the body, giving anaerobic activities a boost. It&#39;s ideal for sprinters and body builders and can also do wonders for lean body mass gains (muscle gains).  HMB, something I found out about last weekend but am yet to trial (on myself), is apparently the bees knees. It prevents muscle catabolism (break down) for energy and encourages lean muscle gains, more so than creatine. It is a relatively new supplement and while the scientific evidence essentially supports it&#39;s claims to greatness, only time will tell (and me, when I get some on the weekend :D).  L-Glutamine helps water retention, recovery and protein utilisation in muscle synthesis.  There are horde of others, but these are the main ones.   Sample diet for muscle mass gain      05:30  Bowl of porridge with chopped up banana 200g of Diet Yoghurt 6 eggs 300ml of Orange Juice    08:00  Protein Shake in water Apple    10:30  Fruit Salad (200g of random fruit)    13:00  100g brown rice 250g chicken breast    14:30  Banana    16:30  Protien &amp;amp; Carb shake (pre-workout) + supps (Creatine)    19:00  Green Salad (no dressing) Grilled Fish or Steak (prefer Fish)       My recommended dosage of supps : 2 protein shakes a day (60g of protein - preferably in water.. arggh the taste :(), 5g of creatine (after the loading - better still, use creatine phosphate and don&#39;t load), L-Glutamine 5g twice daily. HMB - as on the container.  Sleeping/Rest  This one&#39;s easy. Your body can&#39;t grow while you&#39;re in the gym. Too many bodybuilders get frustrated by not enough growth, so they spend more time in the gym. Wrong! While you rest, the body repairs the damage you&#39;ve done while in the gym. give your body enough time to repair between workouts. Rule of thumb is a body part needs 72 hours between workouts to recuperate.  Training  This is what most people focus on. How you train can greatly affect your results (size you grow). Forget what you read in &quot;Flex&quot; and &quot;Muscle and Fitness&quot;, these guys are super-freaks and on &#39;roids.  Most people overtrain, spending too long in the gym. I&#39;d recommend all noobs to train 3 times a week, spending no longer than 45mins a session lifting weights.  At this point I should also mention that training methodology differs for virtually every person. While the basics are good for everyone starting out, once you get some experience you&#39;ve really just got trail and error as your only guide.  When it comes to muscle growth, variability is the key. The body is great for adapting to what you are doing. Do the same thing for too long and you&#39;ll stop growing. A good period for the same workout is 8 weeks. I like to cycle amongst 4 phases. 1) Nothing 2) Hi rep low weight 3) medium rep medium weight (or what I call my true body builders phase 4) power lifting - low rep, high weight, lots of sets.  Stretching : In between sets I&#39;ve found it beneficial to stretch the muscle in use. Not only does this help keep flexibility high (who wants to be beefcake if you can&#39;t move), but stretching helps encourage growth by tearing fibres.  Rep Phases : There are two main phases in any movement, the Concentric and Eccentric. Concentric phase is when the muscles are shortening, eg the upward movement of a dumbbell in a bicep curl. Eccentric phase is when the muscle is expanding. This is important to know because it&#39;s best to slowly (and controlled) move the weight through the eccentric phase and power the weight back up through the concentric phase. Power doesn&#39;t mean swing your whole body and go as fast as possible ;) You can lift heavier weights on the eccentric phase (negative). The application of this is that (to use bench as an example) you can really stack the weight on and slowly lower the weight, while getting a training partner to spot you on the concentric phase. This heavier weight obviously increases damage done, thus muscle gained.  Noobs should spend most of their time doing compound exercises. The golden three are Squats, Dead lifts, Bench Press. (compound exercises are multi-joint exercises). When you first start training, concentrate on these three as you don&#39;t have the form and haven&#39;t trained the muscles to &quot;know&quot; the exercises yet. You&#39;ll also pack the most mass on with these babies! :D. Co-incidentally, these are also the main exercises of my power lifting phase.  Examples of the phases (exercises, reps and sets) 1) Nothing Exactly that. Do nothing for 8 weeks. Sit on the couch/computer. Give your body a chance to rest and your mind a chance to prepare itself for another 12-16 weeks of torture.  2) Endurance 3 sets 20 reps is a good place to start      Day 1  Back &amp;amp; Abs    Day 2  Rest    Day 3  Shoulders, Bi&#39;s &amp;amp; fore-arms    Day 4  Rest    Day 5  Legs    Day 6  Chest and Tri&#39;s    Day 7  Rest       3) Body Building classic I follow 10,8,6,6 split (4 sets). see #2 for day break down.  4) Powerlifting 6 sets 6 reps.      Day 1  bench press. behind-head tricep extension (dumbell). preacher curl    Day 2  Rest    Day 3  squats, military press, dumbell bicep curl    Day 4  Rest    Day 5  bench press, behind-head tricep extension (dumbell), foreach curls (reverse preacher)    Day 6  dead lifts, bent over row, lat pull down    Day 7  Rest       If you&#39;re trying to tone up (e.g. for summer) - cardio every morning (but day 7) for 60 mins, and after every workout for 30 mins. If you&#39;re trying to put on mass eat crap loads of calories (better from carbs and protein).    Sample weekly workout  I&#39;m not listing weights here because that&#39;s something you&#39;ll need to figure out on your own. Just remember, the key is to keep as much intensity in the movement as possible. For mass building we generate intensity though heavy weights and low reps. This means that if you think you can manage your working sets with 1% more weight, add that 1%. Though be careful not to cross that boundary where form is sacrificed. You don&#39;t want an injury and if the weight is too heavy your likely cheating too much (a bit of cheating is ok) and not getting the maximum gain you could be. The following is basically just a reproduction of my current workout.  (format = &amp;lt;Exercise&amp;gt;: &amp;lt;reps per set&amp;gt;[, reps per set]*):  Day 1 - Chest and Tri&#39;s      Flat Bench  20, 10, 8, 6, 6    Incline Bench  8, 8, 8, 8    Dips  10, 10, 10, 10    Standing chest flies  10, 8, 8, 8    Upright French Press (ezy bar)  10, 8, 8, 8       Day 3 - Legs  Warm up with 5 mins on treadmill, minimum 8% incline.      Leg Extensions  10, 10, 10, 10    Squats  10, 10, 10, 10    Leg Presses  10, 10, 10, 10    Calf Raises (on Leg Press machine)  25, 25, 25, 25    Dead Lifts  10, 10, 10, 10       Day 5 - Shoulders &amp;amp; Bi&#39;s      Dumbbell Mil Press  15, 10, 8, 6, 6    Lat Raises  10, 8, 6, 6    Trap Raises (barbell)  10, 8, 6, 6    Seated alternate bicep curl  10, 8, 6, 6    Preacher Curls (ezy bar)  10, 8, 6, 6       Day 6 - Back &amp;amp; Abs      Yates Upright Row  10, 10, 10, 10    Chin-ups  10, 10, 10    Lat Pull down  10, 8, 6, 6    Seated Row  10, 8, 6, 6    Sit ups  30, 30, 30    Hanging leg raises  10, 10    Mower Starts*  15, 15, 15, 15      * Not sure what to call this. I&#39;m the only person in my gym that does them... Basically you grab one side of a pin weight chest fly setup and stand far enough from it such that your when your arms are fully extended toward it, the rope is tight and the weight is off the stack. Then you twist your torso away from the stack, such that the weights move up. Then turn back toward the weight and repeat. 1 set = both directions. So you after 15 you turn and face the other way and do 15 more to complete the set. Arms don&#39;t aid in the movement, it&#39;s all about pivoting the torso. Add a &quot;hip flick&quot; if you&#39;re a martial artist :D</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/6/body-building-primer</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/6/body-building-primer</guid>
                    <pubDate>Mon, 14 June 2010 13:15:00 </pubDate>
                </item>
                <item>
                    <title>Recoving lost files</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/5/recoving-lost-files</comments>
                    <description>I bought that QNAP TS-210 NAS last Friday afternoon, with the intention of making it new home of the two 1.5TB drives that currently reside in my Win 7 HTPC.&#160; The NAS would serve/stream the media files, including Music, as well as provide a backup location and probably a download device.  On arriving home on Friday evening, my first thoughts were &#39;Should I backup the drives first or just place them in the NAS and then copy the data to other machines&#39; (would require the available capacity of more than just one PC on my home network).  I decided on the non-backup option (a stupidly non geek thing to do).  The first thing the NAS did was display &quot;Initializing&quot;, and as it took a while I hoped it wasn&#39;t initializing the drives.&#160; Once it stopped &quot;Initializing&quot;, I took the drives out and put them back in the HTPC.&#160; Sure enough, they were re partitioned.&#160; With the data gone, I put the drives back in the NAS and started to build the NAS as originally intended.&#160; First I tweeted my failings and then built the RAID 0 array and formatted in Ext4 format (NAS only offers Ext3 or Ext4).&#160; Chris, a friend that works in a PC shop at Milton rang me mid RAID build asked what happened and put me on to Recover My Files (recovermyfiles.com).&#160;&#160; &#160;&#160;I put the drives back in Windows and deleted the partitions, as told by Chris.  I grabbed a copy off Uncle Torrence and fired it up.&#160; It detected a bunch (300,000+) of files starting with LostFiles_, after about 14 hours of searching (still had about 25% to go! - on 3TB drives).&#160; I stopped it because I was concerned about LostFiles_* meaning the filename would be unrecoverable.&#160; I did a search on Google for the best file recovery tools and recovermyfiles was right up there anyway, so I downloaded the lastest version from their website (the software performes the scan without requiring purchase of a license key, so you can see what will be retrieved and if it worked).&#160; I restarted the scan and let it run all night (all Saturday night) and by mid Sunday it had found all my files again J.&#160; So I started copying off onto my 500GB USB drive and other the other drives I had spare (took a trip of the USB drive to my main pc and moving from there to the main).  So in the end I got my files back and I thoroughly recommend the software&#160;($70).  I should have practiced what I preach to my non-geek friends.   Always backup  Do research products before purchasing.&#160; Even if your research doesn&#39;t lead to a changed opinion, being well informed is always a good thing when it comes to technology  RTFM</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/5/recoving-lost-files</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/5/recoving-lost-files</guid>
                    <pubDate>Mon, 10 May 2010 19:35:00 </pubDate>
                </item>
                <item>
                    <title>Adding code snippet formatting to web pages</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/4/adding-code-snippet-formatting-to-web-pages</comments>
                    <description>While building this blog I ran into the problem of getting code (as in source code) to display in a format that was easy to read. HTML provides the pre and code elements which give semantics to the source code posted in a blog. However, it doesn&#39;t look code displayed in your favourite IDE. My first attempt was a styling of the code element in css, something like:  code {   border: solid 1px #000;   background-color: #808080;   font-family: Courier; font-size: 10px; }   This helped a little, but when I pasted multiple line code, such as a C# class, the code was still hard to read. Any code snippets included need to be easily readable, and this includes syntax highlighting an numbers and I&#39;d seen this on any number of blogs I&#39;ve visited over the years, so I knew it existed.  I followed the trail and after an hour or two came to SyntaxHighlighter which by looks is what the majority of those blogs I&#39;ve visited use. It&#39;s peasy to set up. I won&#39;t go through the entire process because it&#39;ll just be rehashing the linked page. If you want a quick reference, here&#39;s the steps I took to set it up.  1. Add code to the page (master page in my asp.net case).   &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/css/SyntaxHighlighter/shCore.css&quot; /&amp;gt; &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/css/SyntaxHighlighter/shThemeDefault.css&quot; /&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/Scripts/SyntaxHighlighter/shCore.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;!-For each of the languages I want different highlighting for --&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/Scripts/SyntaxHighlighter/shBrushCSharp.js&quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/Scripts/SyntaxHighlighter/shBrushSql.js&quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/Scripts/SyntaxHighlighter/shBrushVb.js&quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/Scripts/SyntaxHighlighter/shBrushXml.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   2. Start the SyntaxHighlighter engine with your desired properites by adding the following to the bottom of your page, just above the body tag.     &amp;lt;script type=&quot;text/javascript&quot; /&amp;gt;   SyntaxHighlighter.config.bloggerMode = true;   SyntaxHighlighter.config.clipboardSwf = &#39;/Scripts/SyntaxHighlighter/clipboard.swf&#39;;   SyntaxHighlighter.defaults[&#39;wrap-lines&#39;] = false;   SyntaxHighlighter.all(); &amp;lt;/script&amp;gt;   3. Surround any code in pre tags with the brush class. The SyntaxHighlighter engine uses this to format the code in the language specified. See the SyntaxHighlighter website for details.   &amp;lt;pre class=&quot;brush: html&quot;&amp;gt;My Html Code&amp;lt;pre&amp;gt;</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/4/adding-code-snippet-formatting-to-web-pages</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/4/adding-code-snippet-formatting-to-web-pages</guid>
                    <pubDate>Fri, 23 April 2010 08:56:00 </pubDate>
                </item>
                <item>
                    <title>Getting VS2008 to display CSS in Web User Controls</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/4/getting-vs2008-to-display-css-in-web-user-controls</comments>
                    <description>Ever created a Web User Control in Visual Studio 2008 and noticed it doesn&#39;t render the control using CSS.&#160; It doesn&#39;t by design, because the IDE doesn&#39;t know where the control will be dropped.&#160; The page it&#39;s inserted into determines the style to use.&#160; One way to see the styled WUC is to drop it on a page with existing styles (either inline or linked), but that can be a PITA when all you want to see is the style of that control.  &#160;  When reading about how to get intellisense in pages with jquery a while back I came across the following method  &amp;lt;% if (false) { %&amp;gt; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/jquery.js&quot; /&amp;gt; &amp;lt;% } %&amp;gt;  Which works wonderfully.&#160; The same can be applied for stylesheets in user controls.  &amp;lt;% if (false) { %&amp;gt; &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/styles/site.css&quot; /&amp;gt; &amp;lt;% } %&amp;gt;  Because false never evaluates to true, the html is never actually included in page, but while in design, the design picks up on it and styles your pages</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/4/getting-vs2008-to-display-css-in-web-user-controls</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/4/getting-vs2008-to-display-css-in-web-user-controls</guid>
                    <pubDate>Thu, 22 April 2010 03:22:00 </pubDate>
                </item>
                <item>
                    <title>Why acceptance tests should be signed off with formal requirements</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/4/why-acceptance-tests-should-be-signed-off-with-formal-requirements</comments>
                    <description>Under most models of software development of any significant scale, user sign-off of requirements is a must before the development can continue.&#160; Even agile methodologies that involve a customer representative in the development team needs to have a set of at least significantly complete requirements before development can begin.  Why do we need firm requirements?  Requirements form the basis for generating a Tender or Quote and help generate a Statement of Work.&#160; Detailed requirements are often elicited during contract negotiations and once established, a fixed price can be agreed (if that is the model being used). Requirements sign-off is all important in formally contracting what needs to be provided in any software project deliverable.&#160; It is the signed requirements document that is brought out when there is confusion about items being in or out of scope, and determining if a product has been completed.  Defining &#39;done&#39;  A hopefully typical contract stipulates that software is &quot;done&quot; when all Acceptance Tests have passed.&#160;&#160; Although our contracts often stipulate that once all sites are Live the software has been accepted and the project is &quot;done&quot;, though in my experience this is painful because reasons for not going Live are quite often more political than technical or caused by any software defect, and it&#39;s these situations where Acceptence Test success would greatly speed up the &quot;Done&quot; state of a project (and the &quot;paid&quot; state of the Invoice).  My company traditionally produces an &quot;Operational Scenarios Document&quot; (OSD) as the document indicating the requirements and to be signed off the customer.&#160; The OSD is simply a document containing all the user stories or use cases of the system to be built.&#160; I believe this presents a more understandable view of the system to the customer, as it&#39;s written in their language.&#160; From the OSD Analysts produce a more engineering oriented &quot;Software Requirements Document&quot; (SRD), where the OSD is broken down into hundreds and thousands of numbers system requirements.  The problem with this approach is the OSD only captures user facing actions such as steps in a business process.&#160; It is up to the SRD to capture the more fine grained requirements, such as &quot;Patient information shall include a valid Medicare number in the format XXXX-XXXX-XXXX X&quot;.&#160; As such, we&#39;ve captured the necessary requirement, but there&#39;s nothing to formal (read &quot;legal&quot;) to cover either us or the customer.&#160; Generally this isn&#39;t a problem, but we&#39;ve probably all experienced scope-creep and ambiguous (signed) requirements are the genesis of scope creep.  Enter signed Acceptance Tests and Acceptance Test Driven Development (TDD at a higher abstraction).&#160; Acceptance Tests define exactly what the system has to do before the customer will consider it &quot;Done&quot;.&#160; Acceptance Tests don&#39;t just include functional requirements; they also test the non-functional stuff like performance, security, and useability.&#160; If functionally is not covered by the Acceptance Tests, it&#39;s not required by the customer.&#160; Following on the YAGNI principle.  How to define User Acceptance Tests  Now, I&#39;m not saying that no software project should be undertaken before Acceptance Tests are supplied.&#160; That&#39;s clearly unworkable in all but the situations where the customer team has experienced analysts, with a good understanding of the software development process, on board.&#160; What I do think is that an analyst or developer should always be mindful of Acceptance Tests when eliciting requirements from customers during workshops.&#160; Questions like &quot;What are the steps you take when placing an order&quot; are not enough.&#160; &quot;What state will the order be in once it has been successfully placed?&quot;, or &quot;What happens if an order is placed and there is not enough inventory to fulfil it?&quot; are necessary. Hopefully these are already questions that are being asked as part of the user story generation.  User Acceptance Tests versus Operation Scenarios  The differences between Acceptance Tests and Use Cases (or OSD&#39;s) are small but significant.&#160; Where an OSD is used by the customer to accept that you understand the requirements of the software under design, Acceptance Tests are used by customer to determine if you supplied what they asked for.&#160; Both OSD and Acceptance Test Document can be generated from the same information at the same time, so it makes sense to generate them at the same time and include both in any formal signed requirements.  How do I use User Acceptance Tests in the development process?  From a development perspective, you can use user stories/use cases/operational scenarios in tandem with acceptance tests to define the use cases to be developed in each iteration, producing software that is of business use after iteration one and the customer can hook into the feedback loop early in a project.  Critically, Acceptance Tests are generally customer supplied artefacts and as such, you as a developer have a higher level of confidence that developing a system to meet Acceptance Tests will result in a successful project.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/4/why-acceptance-tests-should-be-signed-off-with-formal-requirements</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/4/why-acceptance-tests-should-be-signed-off-with-formal-requirements</guid>
                    <pubDate>Fri, 16 April 2010 17:22:00 </pubDate>
                </item>
                <item>
                    <title>404 Handlers</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/4/404-handlers</comments>
                    <description>I&#39;m currently thinking about how I&#39;m going to approach my first Umbraco website.&#160; I know I want to go with ASP.NET MVC, for better testability, but Umbraco is currently ASP.NET Web Forms only.&#160; In my investigations I learnt about an interesting technique for getting around this problem: 404 handlers.  When a web page is requested, the server tries to map the request Url to a page.&#160; When it comes up with nothing, a 404 (Page not found) error is returned.&#160; In essence, the 404 Handler then performs custom actions, which include trying to find the page somewhere else.&#160; In this case, that somewhere else is the Umbraco site.&#160; The 404 handler can retrieve the CMS page and return the results in the response.  [DefaultAction(&quot;index&quot;)] public class RescuesController : SmartDispatchController {   private static readonly Regex headtitleRegex = new Regex(&quot;&amp;lt;h1&amp;gt;([^&amp;lt;]*)&amp;lt;/&amp;gt;&amp;gt;&quot;);    public RescuesController(IApplicationContextProvider currentCustomerProvider, IConfigurationManager configurationManager) : base(currentCustomerProvider, configurationManager) { }    public void Index()   {     RenderView(&quot;rescues&quot;,&quot;content&quot;);      string pageName = Request.Uri.LocalPath;     if (pageName.EndsWith(&quot;/index&quot;))       pageName = pageName.Substring(0, pageName.LastIndexOf(&quot;/index&quot;));      try     {       var remoteContent = GetRemoteContent(pageName);        var matches = headtitleRegex.Match(remoteContent);       if (matches.Groups.Count &amp;gt; 1)         PropertyBag.HeadTitle = matches.Groups[1].Captures[0].Value;        PropertyBag.remoteContent = headtitleRegex.Replace(remoteContent, &quot;&quot;);     } catch (WebException) {       Handle404();     }   }    public static string GetRemoteContent(string pageName)   {     return (new WebClient()).DownloadString(&quot;http://content.mysite.com/&quot; + pageName);   } }   CSS in our ASP.NET MVC site can be used to render the content.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/4/404-handlers</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/4/404-handlers</guid>
                    <pubDate>Tue, 13 April 2010 19:26:00 </pubDate>
                </item>
                <item>
                    <title>atomic flair</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/3/atomic-flair</comments>
                    <description>Today I was adding my stackoverflow.com (and associated sites) flair to this blog and thought I might add one for the other site I frequent, forums.atomicmpc.com.au .  Atomic doesn&#39;t have &quot;flair&quot; or anything like it, so I thought why not roll my own.&#160; Ok I possibly have infringed on some copyright but I haven&#39;t used any atomicmpc logos or anything so I should be ok.&#160; If not, I&#39;m happy to get rid of it.&#160; This was more for my own entertainment.  If you want to add atomic flair to your site or blog, all you need to do is embed an iframe with the with the source to the following location http://atomicflair.sharpstudios.com.au/user/id/0&#160; Where 0 is your user id from the site. To find this out just go to your profiile and look in the url at the showUser querystring value.&#160; Thats your used id.  Embed the following html into your blog and you&#39;re good to go.  &amp;lt;iframe src=&quot;http://atomicflair.sharpstudios.com.au/user/id/9541&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot; width=&quot;210&quot; height=&quot;60&quot;&amp;gt;&amp;lt;/iframe&amp;gt;  There is a limitation.  Caching - there is none, so while your atomicmpc rank and other details don&#39;t change often, each page request is going , via the IFRAME, to atomic.&#160; A better solution would be bring the data back as a png and let the user embed that.&#160; That way the img could be cached.  Technical Stuff - The app to generate the data is written in asp.net mvc.&#160; I use a technique known as screen scraping to pull the data back from the atomic website and populate my own AtomicUser instance.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/3/atomic-flair</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/3/atomic-flair</guid>
                    <pubDate>Sat, 27 March 2010 06:46:00 </pubDate>
                </item>
                <item>
                    <title>Setting up multiple sites on IIS</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/3/setting-up-multiple-sites-on-iis</comments>
                    <description>Something I&#39;ve wanted to know for a while is how to run multiple websites from the one IIS on the one machine, with one public IP address.  Today I got my answer, in the form of Bindings on the website in IIS.&#160; The trick is to use subdomains for the binding, eg blog.mydomain.com, www.mydomain.com and so on.    &#160;  Once the bindings are configured, you need to set up the CNAME entries on your DNS and wait for the DNS servers around the world to be updated.&#160; If you don&#39;t have a DNS or domain name, you can update the hosts file appropriately (typically at c:\windows\system32\drivers\etc\hosts)</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/3/setting-up-multiple-sites-on-iis</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/3/setting-up-multiple-sites-on-iis</guid>
                    <pubDate>Sat, 27 March 2010 01:37:00 </pubDate>
                </item>
                <item>
                    <title>starting umbraco</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2010/3/starting-umbraco</comments>
                    <description>A project I&#39;m leading at work is going to use Umbraco for the content management, leading me to today start investigating just what Umbraco is all about and how we&#39;ll make best use of it in the context of the web application we&#39;re developing.  Interesting for me is that this is also my first foray into the world of Content Management.&#160; Having spent my career to date working on line of business applications and enterprise software, I&#39;ve never had to develop a web site where there will be a lot of basically static content; hence there&#39;s been no need to investigate content managment systems.  Ubraco fits our needs for this project, as it&#39;s an Open Souce offering in ASP.NET and seems, now after a day of playing around, fairly simple to use and very extensible.&#160; After googling for numerous tutorials on Umbraco, I settled on getting a subscription to umbraco.tv , which suits my style of learning well.  &#160;  Day 1 was spent learing about Document Types and Templates, with a prelude into building asp.net web controls for business logic.&#160; It&#39;s been interesting thus far.&#160; Probably the biggest disapointment is that Umbraco 4 only works on ASP.NET and not ASP.NET MVC, or at least, it&#39;s not designed to work with ASP.NET MVC.&#160; With at tight deadline, I don&#39;t want to try and make it fit.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2010/3/starting-umbraco</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2010/3/starting-umbraco</guid>
                    <pubDate>Fri, 26 March 2010 07:51:00 </pubDate>
                </item>
                <item>
                    <title>Turning an asynchronous call into a synchronous call</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2009/1/turning-an-asynchronous-call-into-a-synchronous-call</comments>
                    <description>Scenario  There is a bug in our Premise Utility COM component that causes streetid&#39;s to be set to 0 whenever an existing premise record is saved.&#160; StreetId&#39;s are essential to accurate location information when despatching ambulances, so getting a fix done was high on the agenda.&#160; The problem is that for whatever (not technical) reason the bug cannot immediately be fixed in the Premise Utility.&#160; I thought I&#39;d instead create a small app that will be notified when a premise record changes and perform a reverse geo code on the supplied latitude and longitude to obtain a streetid and update the premise record.     What did I do?  The existing .NET Reverse GeoCode component inherits from System.Windows.Forms and contains an old VB OCX that performs the logic (we&#39;re still in process of converting rather our large code base from VB6/C++ to .NET).&#160; This component is designed to be called from a Windows Forms app, where the user enters the Latitude and Longitude and waits for a response, which is returned by an Event to the caller:   private void button1_Click(object sender, EventArgs e) {   var geo = new ReverseGeovalidator(100);   geo.ReverseGeovalidationComplete += new ReverseGeovalidator.ReverseGeovalidationEventHandler(geo_ReverseGeovalidationComplete);   var latitude = 62531667;   var longitude = 26988333;    geo.ReverseGeovalidate(latitude, longitude); } void geo_ReverseGeovalidationComplete(object sender, ReverseGeovalidationEventArgs eventArgs) {   // Populate the form with the data received from eventArgs }   My application needed to run unattented (Window Service), monitoring the IPC server for notifications that a Premise had changed.&#160; This meant no windows form and no waiting around for a display to be populated.  I needed my application to accept a geo-coordinate and process reverse geo-code synchronously, so I could associated the resultant streetid with the premise id of the changed premise and then commit to the database.  My original thinking was to use ManualResetEvents.WaitOne() to block the caller until the event handler had been called and I could call ManualResetEvents.Set() to continue the operation.   public class GeoCoder {   ManualResetEvent geoDone;   ReverseGeovalidationEventArgs data;   public GeoCoder()   {     geoDone = new ManualResetEvent(false);   }   public int ReverseGeoCode(int latitude, int longitude)   {     geoDone.Reset();     var geo = new ReverseGeovalidator(1500);     geo.ReverseGeovalidationComplete += new ReverseGeovalidator.ReverseGeovalidationEventHandler(geo_ReverseGeovalidationComplete);      geo.ReverseGeovalidate(latitude, longitude);     geoDone.WaitOne();     if (data != null)     {       return data.StreetID;     }     return 0;   }   void geo_ReverseGeovalidationComplete(object sender, ReverseGeovalidationEventArgs eventArgs)   {     data = eventArgs;     geoDone.Set();   } }    The problem I found was that my unit test would lock up.   [TestMethod] public void ReverseGeoCodeTest() {   var gc = new GeoCoder);   var latitude = 62531667   var longitude = 26988333;   var streetId = gc.ReverseGeoCode(latitude, longitude);   Assert.AreEqual(275535, streetId); }    After a little bit of pondering I realised that geoDone.WaitOne() is blocking on the current thread, which is the same thread the event handler is on.&#160; Result? The event handler will never get called.  I needed to put the event handler a separate thread, so it would eventually get called?&#160; But How?  After a bit of playing around, I decided (realised?) to put the entire lot on a worker thread, a la Command Pattern.&#160; I created a test Geo Processor that is essentially a job queue that co-ordinates off the queue every 2 seconds and processes them, returning the results when done.  public class GeoProcessor {   public delegate void GeoCodeEventHandler(object sender, GeoCodeEventArgs e);   private Queue workQueue;   public event GeoCodeEventHandler GeoCodeEventComplete;   // Timer will pull Coordinates off the work queue and process them   System.Timers.Timer workTimer;   public GeoProcessor()   {     workQueue = new Queue();     workTimer = new System.Timers.Timer();     workTimer.Elapsed +=new ElapsedEventHandler(workTimer_Elapsed);     workTimer.Interval = 2000;   }   // Pulls items from the queue and processes.   void workTimer_Elapsed(object sender, ElapsedEventArgs e)   {     workTimer.Stop();     if (workQueue.Count &amp;gt; 0)     {       var coord = workQueue.Dequeue()       //       // Do some processing here       //        // Raise event to notify subscriber that work is done       OnGeoCodeComplete(new GeoCodeEventArgs(275535));          if (workQueue.Count &amp;gt; 0) workTimer.Start();     }   }    // Queues the coordinate up for processing in the work queue   public void QueueGeo(int latitude, int longitude)   {     workQueue.Enqueue(new GeoCoordinate { Latitude = latitude, Longitude = longitude });     workTimer.Start();   }   protected void OnGeoCodeComplete(GeoCodeEventArgs e)   {     if (GeoCodeEventCompleted != null)     {       GeoCodeEventCompleted(this, e);     }   } }    Below is the EventArgs sub class I use to pass the &quot;processed&quot; information back, along with the geo coordinate class.  public class GeoCodeEventArgs : EventArgs {   private int streetId;   public GeoCodeEventArgs(int streetId)   {     this.streetId = streetId;   }    public int StreetId   {     get { return this.streetId; }   } } public class GeoCoordinate {   public int Latitude { get; set; }   public int Longitude { get; set; } }    This is my command pattern implementation that will run on a separate worker thread and call the GeoProcessor.  internal class GeoWorker {   GeoCoder geoCoder;   int latitude;   int longitude;   public GeoWorker(GeoCoder geoCoder, int latitude, int longitude)   {     this.latitude = latitude;     this.longitude = longitude;     this.geoCoder = geoCoder;   }   public void Process()   {     var geo = new GeoProcessor();     geo.GeoCodeEventCompleted += new GeoProcessor.GeoCodeEventHandler(geo_GeoCodeEventCompleted);     geo.QueueGeo(latitude, longitude);   }   void geo_GeoCodeEventCompleted(object sender, GeoCodeEventArgs e)   {     geoCoder.Data = e;     geoCoder.GeoDone.Set();   } }   My GeoCoder class is now only responsible for starting the worker thread that will call the GeoProcessor and then waiting for the reset event to be set.&#160; Once set (when the worker handles the event from the GeoProcessor and calls Set() on the shared Reset Event) GeoCoder can continue.   public class GeoCoder {   ManualResetEvent geoDone;   Thread geoWorkerThread;   int latitude;   int longitude;   public GeoCoder()   {     geoDone = new ManualResetEvent(false);   }   public GeoCodeEventArgs Data { get; set; }   public ManualResetEvent GeoDone { get; }   public int ReverseGeoCode(int latitude, int longitude)   {     this.latitude = latitude;     this.longitude = longitude;     GeoDone.Reset();     geoWorkerThread = new Thread(new ThreadStart(this.CallGeoWorker));     geoWorkerThread.Name = &quot;Reverse Geo Code Worker&quot;;     geoWorkerThread.SetApartmentState(ApartmentState.STA);     geoWorkerThread.Start();     GeoDone.WaitOne();     if (Data != null)     {       return Data.StreetId;     }     return 0;   }   private void CallGeoWorker()   {     var geoWorker = new GeoWorker(this, latitude, longitude);     geoWorker.Process();   } }   The asynchronous (event) call has now been turned into synchronous.  The biggest problem is the showstopper though.&#160; COM doesn&#39;t play well using this model, thanks to it&#39;s STA threading (I believe) and the event handler never gets called.  Overall a great learning experience, but I still can&#39;t call the COM wrapper object synchronously and now, admitting defeat, I am rolling my own class to calculate the StreetId and not using the OCX.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2009/1/turning-an-asynchronous-call-into-a-synchronous-call</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2009/1/turning-an-asynchronous-call-into-a-synchronous-call</guid>
                    <pubDate>Thu, 29 January 2009 18:03:00 </pubDate>
                </item>
                <item>
                    <title>Career advancement for developers</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2008/9/career-advancement-for-developers</comments>
                    <description>One of the big challenges any developer faces is knowing how to successfully progress up the ladder. What do we do to make ourselves stand out, and how do we get the skills we need to move to the next level. For me, this is a problem. Thus far in my career, I&#39;ve relied on what I consider the most important attribute: hard work. I believe the best way to get noticed and promoted is to finish projects on time (insomuch as we can control these things J) and as defect free as possible. Then, rather than relax, go chase more work. Help others out. I don&#39;t think it&#39;s long hours, and I don&#39;t look for long hours in my developers, people need balanced lives to be happy and effective. When I allocate work, I do so based on an 8 hour day, or at least what I think can be accomplished in an 8 hours day, even if I&#39;ve been told on more than one occasion by people senior to me that I work faster than most.  If you need to work 2 extra hours a day to keep up, you need to either work smarter or get more skilled. Perhaps both.  The career path for a develop goes pretty much like  Developer -&amp;gt; Developer Lead -&amp;gt; Architect -&amp;gt; (Development Manager) -&amp;gt; CIO.  I&#39;ve put Development Manager in parenthesis because in my experience as Development Lead, Development Lead performs resources scheduling activites and high level oversight roles too. At least, in my small company, I&#39;m the Development Lead and the Development Manager and Solution Architect in one. Hell, I&#39;m also a Developer. So there&#39;s a glaring Caveat, the career path depends on the company you work work.  Another thing I&#39;ve noticed is Developers will seek promotion to get paid more. Many bosses consider promotion to be management, so will promote good programmer to Managers, where they may make really bad managers. There&#39;s something called the Peter Principle, go check it out http://en.wikipedia.org/wiki/The_Peter_Principle .</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2008/9/career-advancement-for-developers</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2008/9/career-advancement-for-developers</guid>
                    <pubDate>Thu, 11 September 2008 05:56:00 </pubDate>
                </item>
                <item>
                    <title>Unit testing and development of data aware applications in .NET</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2007/12/unit-testing-and-development-of-data-aware-applications-in-net</comments>
                    <description>What is Unit Testing?  Unit testing is a technique used to validate fine grained functionality in object oriented programming (although you can &quot;unit test&quot; routines in procedural languages too, unit testing is most widely associated with OOP).  A basic unit test might look like:  [Test] public void AdditionTest() {   int result = MathLib.Add(1, 2);   Assert.AreEqual(result, 3); }    where we are inputting known values to a method and comparing the results against what we expect to happen.   Why Unit Test?  The two main benefits of unit testing are:  • Correctly written unit tests ensure the accuracy of the behaviour of your classes. • Unit Testing encourages Separation of Concerns and the layering of software (e.g. removing domain code from the UI). This is fundamental to developing maintainable software.  There are other benefits of unit testing and test driven development, but talking about them would lead me off topic, so I&#39;ll leave that for another rant.    Data Aware Applications in .NET  Visual Studio provides a fantastic environment to enable developers to quickly piece together functional applications in minimal time. (When I mention .NET, I am automatically including the Visual Studio IDE in any conversation, as who develops .NET apps without Visual Studio? Consider the two synonymous). Much of this power comes from the ability to use DataSets, and in particular typed DataSets, and bind them to data aware controls.  The quickest way to get an application up and running is to use the suppled tools and create datasets, dragging them onto your forms (be they WebForms or WinForms) and let VS work its magic. You can then double click on a button and hook-up the UI controls (such as text and check boxes) to work with the data. Validation is often performed in the UI code.  Here we have already run into problems. How do we Unit Test? There are two main problems. How do we run a unit test, given our code resides in UI layer and, and how can we construct repeatable tests when we&#39;re talking to a database?    Designing for Unit Tests  This is where unit testing encourages Separation of Concerns. This first thing we need to do is break out our code into three parts, which we just identified through the problems we&#39;re having unit testing.  Firstly, we need to separate the UI from the business logic (and validation, which does belong with the business rules). We need to create a separation that will allow testing to be performed. To do this we use interfaces, e.g. IView, where the interfaces represents the UI or View .  Secondly, we need to be able to separate the data layer so we can repeat tests and specify the input values of our function (just like in the example earlier). To do this we pull the data access of the UI and put it behind a DataMapper parent class. Each DataMapper will normally correspond to a table or view in the database and will be used to populate the business objects. Business objects will have no knowledge of it&#39;s DataMapper (but the DataMapper will know about the business object, so it can create, read, update, delete).  Most business aware applications are structured so that each form represents a piece of business activity, for example making a booking. It&#39;s best to encapsulate (any small) business objects into actual business services. This is known as Service Oriented Architecture (SOA), which you may have heard lots about. We&#39;re going to call this service layer the Model and define the services through particular IService interfaces.  For the purpose of unit testing we can create mock objects that for each necessary DataMapper (created within the test assembly) that will simulate a connection to a database and provide idempotent behaviour.  It&#39;s not quit complete yet, we need something that synchronises the View with the Service. That synchronisation object is known as the Presenter. Look at that. I&#39;ve just described the MVP (Model-View-Presenter) pattern.   Example  It&#39;s probably a good idea to throw in an example at about this time. A simple case that of logging in to a website.  The UI needs to be able to take a username and password and pass it to the business layer for validation, and then give feedback to the user.  Here&#39;s what the unit test might look like  [Test] public void TestUserLogin() {   // Normally would use ILoginView, but for the test, we need to simulate login button presses   MockLoginView mockView = new MockLoginView();   ILoginService mockService = new MockLoginService();   LoginPresenter presenter = new LoginPresenter(mockView, mockService);   mockView.LoginButtonPressed();   Assert.IsTrue(presenter.LoggedIn, &quot;Error in Login logic&quot;); }   And here are the interfaces for the MVP pattern.  public delegate void LoginRequestHandler(object sender, LoginRequestedEventArgs e); public interface ILoginView {   event LoginRequestHandler LoginRequested;   string Username { get; }   string Password { get; }   IList Errors { set; } } public sealed class LoginRequestedEventArgs : EventArgs {   private string username;   private string password;   public LoginRequestedEventArgs(string username, string password)   {     this.username = username;     this.password = password;   }   public string Username   {     get { return this.username; }   }   public string Password   {     get { return this.password; }   } } public interface ILoginService {   bool Login(string username, string password); }  &#160;  Now we need to define the mock objects, so here&#39;s the Mock UI  internal class MockLoginView : ILoginView {   private string username;   private string password;   private IList errors;   public MockLoginView() { }   public void LoginButtonPressed()   {     this.Username = &quot;Rob&quot;;     this.Password = &quot;fr34kyp455w0rd&quot;;     OnLoginRequest();   }   #region ILoginView Members   public event LoginRequestHandler LoginRequested;   public string Password   {     get { return password; }     set { this.password = value; }   }   public string Username   {     get { return this.username; }     set { this.username = value; }   }   public IList Errors   {     set { this.errors = value; }   }    #endregion   public string GetErrors()   {     System.Text.StringBuilder sb = new System.Text.StringBuilder();     foreach(string error in this.errors)     {       if (sb.Length &amp;gt; 0) sb.Append(&quot;, &quot;);       sb.Append(error);     }     return sb.ToString();   }   protected void OnLoginRequest()   {     if (LoginRequested != null)     {       LoginRequested(this, new LoginRequestedEventArgs(this.username, this.password));     }   } }   Notice, I&#39;ve got a function LoginButtonPressed that simulates the user clicking on the login button in the UI.  Now the mock service object  internal class MockLoginService : ILoginService {   public bool Login(string username, string password)   {     MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();     UTF8Encoding enc = new UTF8Encoding();     string hashedPassword = enc.GetString(hasher.ComputeHash(enc.GetBytes(password)));     User user = new User(1, username, hashedPassword, (uint)Roles.Viewing);     return LoginManager.ValidateUser(user, password);   } }   Glueing the View and Model together is the Presentation  public class LoginPresenter {   private ILoginView view;   private ILoginService service;   private bool loggedIn;   public LoginPresenter(ILoginView view, ILoginService service)   {     this.view = view;     this.service = service;     view.LoginRequested +=new LoginRequestHandler(view_LoginRequested);   }   private void view_LoginRequested(object sender, LoginRequestedEventArgs e)   {     // go to the model and determine if able to login.     this.loggedIn = service.Login(e.Username, e.Password);   }   public bool LoggedIn   {     get { return this.loggedIn; }   } }   In production code, the User object would be retrieved from the DAL (data access layer) by a method such as UserDataMapper.FindUserByUsername(username); which would either retrieve the object from disk, or retrieve it from memory (eg a Hashtable/Dictionary inside the UserDataMapper class), if it had previously been retrieved.   The problem with .NET  Visual Studio allows you to create TableAdapters, which &quot;sort of&quot; fill the role of the DataMappers ( I say &quot;sort of&quot; because, without getting sidetracked on the differences, there are differences ). Visual Studio also creates corresponding DataRow objects that represent business objects (or at least, that&#39;s the intent ;)). All this can be done through the tools in VS, without typing even one line of code.  Problem is, this doesn&#39;t easily separate the DAL from the business objects. Once again, unit testing is made more difficult.   Further Discussion  In unit test shown above, I test presenter.LoggedIn outside of the View. I chose this way because in practice, the Presentation should be a Page Controller (http://martinfowler.com/eaaCatalog/pageController.html ) which depending on the results of the Login Request would either redirect the user to the new page or return the user to the login page. Having navigation at this level is desirable because it removes the decision from the UI. Which is a good thing when you want to abstract the view.  Another way to implement this pattern is to maintain a reference to the presenter inside the view and then call presenter.LoggedIn.  As with any design, it&#39;s always a matter of weighing up the pro&#39;s and con&#39;s of each and coming to a decision that best suits the particular system under construction.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2007/12/unit-testing-and-development-of-data-aware-applications-in-net</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2007/12/unit-testing-and-development-of-data-aware-applications-in-net</guid>
                    <pubDate>Sat, 01 December 2007 05:59:00 </pubDate>
                </item>
                <item>
                    <title>Software stagnation versus capitalism</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2007/7/software-stagnation-versus-capitalism</comments>
                    <description>If there&#39;s something Microsoft is definitely guilty of, it&#39;s making bucket loads of money. Of itself, there is nothing wrong with making money in our capitalism economic system. What irks many people is the manner in which Microsoft make their money. Many consider the way they go about their business, to be dirty business. In fact, even the courts nearly agreed. I don&#39;t think it&#39;s necessary to go into the details of the 1999 Anti-trust suit against Microsoft. Nor do I think it&#39;s necessary to detail numerous attempted and successful takeovers.  I don&#39;t intend for this to start a Microsoft versus everyone else debate, or another Linux versus Windows war. They have already been done to death.  What is also transparent to many is the originality of software produced by the giant. It is for the most part obvious that many of Microsoft idea&#39;s are based on products or ideas belonging to other parties. What Microsoft do is take ideas and add improved marketing, and often, improved functionality (even if many call it bloat ).  Microsoft, through their marketing and brand recognition, make software more accessible. Linux variants may be better products, from a technical viewpoint, but are they more usable? It is also more than likely that because Microsoft was the major player when PC&#39;s (and the internet) really boomed in the 90&#39;s, we&#39;ve all become accustomed to Microsoft&#39;s standard shortcut keys and UI layouts, making a transition to a variant seem more trouble than it&#39;s worth to casual users, or those only wanting to &quot;get the job done&quot;. Although I could write thousands of words on how good the UI&#39;s of Microsoft products are, products like Visual Studio, in comparison to competitors.  I think I&#39;ve waffled on long enough that it&#39;s time to actually get to the point. I was inspired by this thread. Microsoft has a new HD Photo format they hope will replace JPEG. Microsoft has a &quot;tendency&quot; to implement de-facto standards, those not ratified by the ISO or other standards body, potentially causing cross platform compatibility issues or worse, hampering progress.  If Microsoft&#39;s main aim, as tycjo102 suggest, get the patent and then make squillions from this new format, is this a bad thing if the state of the art progresses?  When does the good done by open software knowledge become mitigated by software stagnation caused by a lack of financial incentive? Are speculative concerns of possible damage that might be incurred to future knowledge or progress enough to thwart Microsoft&#39;s attempts to introduced yet another new &quot;standard&quot;?</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2007/7/software-stagnation-versus-capitalism</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2007/7/software-stagnation-versus-capitalism</guid>
                    <pubDate>Sat, 14 July 2007 06:15:00 </pubDate>
                </item>
                <item>
                    <title>Is postgraduate study now par for the course in IT?</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2007/7/is-postgraduate-study-now-par-for-the-course-in-it</comments>
                    <description>All evidence in the current IT job market points toward a employees market. It is increasingly hard to find qualified staff, as more and more position are made available. It is safe to say that if you are a developer you&#39;re on the pain free end of the stick.  There is one statistic that confuses me though. It seems an increasing number of developer resumes that pass through our HR department are from candidates with postgraduate study. Approximately 80% of the resumes are in this category. Within this number, perhaps a further 40-50% of candidates have both undergrad and post grad IT qualifications (be it Information Technology, Multimedia, Engineer, Computer Science). Although, almost all post graduate study is coursework. There may be a few reasons for these trends:   People that have research post graduate awards tend to stay in research, going on to study Ph.d&#39;s or continue in other research related roles.  People that have research post graduate awards tend to be &quot;pre-employed&quot;, going on to a employment with a large company upon graduation. Thus they never enter into the job market.  What&#39;s being taught in undergraduate courses is no longer specific enough for the majority of graduates to get work easily.  Once graduating under grad, the person couldn&#39;t find work and instead chose to do post grad instead of sitting idle.    Years ago grade 10 was enough to get a job, that minimum requirement crept up to grade 12 and then under-grad. Now many graduates are of the belief that a post-grad award is needed. While I do think a successful undergrad degree is necessary for a developer, my personal experience (yes, I too will soon join the ranks of those with post-grad degrees) says that post-grad coursework degrees are essentially a waste of time and money. On the job expereience is more beneficial.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2007/7/is-postgraduate-study-now-par-for-the-course-in-it</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2007/7/is-postgraduate-study-now-par-for-the-course-in-it</guid>
                    <pubDate>Tue, 10 July 2007 06:17:00 </pubDate>
                </item>
                <item>
                    <title>The Interviewing process</title>
                    <author>Rob Gray</author>
                    <comments>http://www.robertgray.net.au.aspx/posts/2007/7/the-interviewing-process</comments>
                    <description>I&#39;m aiming to kick off the new year with a big development push next year and as part of this initiative I&#39;m hiring some new staff. I&#39;ve been reading a great book (I could even call it my new Bible :)) on software develement management called &quot;Joel on Software&quot;, written by Joel Spolsky. He&#39;s suggested a format for interviewing which he&#39;s refined over the years to ensure he hires only quality staff. Or as he puts it &quot;Smart people that get things done&quot;, as opposed to just smart people, or just people who (appear) to get things done.  I was previsouly using many of Joel&#39;s suggested techniques, but suprisingly not using the perhaps the most important. I hadn&#39;t been getting candidates to write actual code as part of the Interview. I&#39;ve since discovered that writting code during an interview is paramount to getting quality employees. I&#39;m not claiming that it&#39;s the only thing but I do believe without it you haven&#39;t got a chance.  My code question is quite simple, even more-so than Spolsky&#39;s problem of writting a function that reverses a string in-line. I ask the candidates to write a function that reverses a string in C# (or VB.NET). I choose C# because we&#39;re hiring only .NET developers. If they have never used C# I&#39;ll allow VB.NEt, simply because it&#39;s so closely related to C#.  I expect to see something like:   &#160;  public static string Reverse(string input) {   char[] temp = new char[input.Length];   int len = input.Length -1;   for (int index = 0; index &amp;lt; input.length; index++, len)   {   temp[index] = input[len];   }   return new string(temp); }     I didn&#39;t have even one candidate who could produce this. The last candidate (and if his references check out, new employee) nearly had it, in VB.NET, but his syntax wouldn&#39;t have compiled and he added strings together. Looks okay, but the problem is that strings are immutable and a new instance needs to be created each time you call Substring(), or join two or more strings together, as was happening every iteration. Using Substring does have one benefit though, it&#39;s aware of surrogate pairs, which my implemenation above is not. The most annoying thing about this question (and the responses I got), was that when I asked my brother to do it he did it in 1 minute, in C# and it was right. Every candidate I interviewed had AT LEAST a Masters level IT degree, yet my brother could manage it, aged only 19 and with a Cert IV in IT.</description>
                    <link>http://www.robertgray.net.au.aspx/posts/2007/7/the-interviewing-process</link>
                    <guid>http://www.robertgray.net.au.aspx/posts/2007/7/the-interviewing-process</guid>
                    <pubDate>Fri, 06 July 2007 06:18:00 </pubDate>
                </item>
        </channel>
    </rss>

