Friday, 28 December 2012

Visual Studio 2012 thank you

There I was about to enjoy a nice sleep-in on the first day of my holidays on Monday when the doorbell rang. It was a courier delivering a nice thankyou present from Microsoft for helping test out and provide feedback on Visual Studio 2012.

Visual Studio Thank You box

Opening up this large box revealed a smaller gift inside

Visual Studio Thank You box opened

It's a shiny glass cube with writing etched in the middle of it..

Visual Studio 2012 Crystal Cube

And it turns out this is a nice match to a similar present I received a few years ago for testing Visual Studio 2008 (which means I must have missed out 2010). The 2012 version is an identical size, but has one corner bevelled off so it can sit on that corner. The new cube also has the latest rendition of the Visual Studio logo.

Visual Studio 2008 and 2012 cubes side by side

Putting them together, yes they are the same size!

2012 cube on top of 2008 cube

Wednesday, 26 December 2012

I know that it's Christmas..

Because people will even dance to some guy trying to play the 'cello..

David practising his 'cello

Always nice to have enthusiasm when you're doing a sound checkSmile 

This year we visited our friends across town who were organising a Christmas Eve service at Playford Uniting. I was also able to be part of their band playing a few Christmas carols.

Because the Road to Christmas is on again..

Actors being King Herod and his entourage

And even made the Channel Seven news. Over the 3 evenings that this runs there's probably a few thousand people from the local community (and beyond) that come and experience sights, sounds, smells and tastes of Bethlehem. It's a great atmosphere and lots of fun too.

Because the cactus that flowers at Christmas is in flower..

Cactus flower

and the flowers came out first in the morning on Christmas day this year too.

Because the table is set for Christmas lunch..

Table set for Christmas lunch

Because Narelle cooked up a storm..

Christmas lunch ready to eat

Yummo

Because the pudding is ready..

Christmas pudding

Extra yummo.

Because Mum & Dad gave me a new Super Soaker!

David unwrapping a super-soaker water pistol

So it turns out that the CFS actually recommend having one of these around the house. If you did have a bushfire go through then then a key thing is to put out any spotfires in gutters or roof spaces once the main fire front has passed. They reckon a super soaker is good as you can pop your head up through the manhole into the ceiling and squirt any smouldering embers that might have found their way inside.

The other benefit is that it means I can now compete with my son's soaker he got as a LobsterPot Christmas present a couple of years ago (thanks Rob!)

Because it is a chance to take a breath..

Borrowing from the theme of Pete's message at the Christmas Eve service. After a busy and in many ways difficult year this year it is good to have a couple of weeks off and take a breath (or two).

Time to spend with family. Time to sleep in (maybe). Time to do a bit of riding in preparation for the Tour Down Under in a few weeks. Time to reflect on the year that was and the year that is to come. Time to appreciate the extraordinary gift that is the real reason for celebrating Christmas.

Monday, 17 December 2012

The Email dilemma

Following on from my investigations into services that summarise DMARC reports, I've come to the conclusion that the reason for the warnings about email from hotmail.com is because even though the account on hotmail is configured to send as @gardiner.net.au, it still is sending the email officially using the @hotmail.com sender address.

While it passes the SPF rule (that permits the hotmail email servers to send @gardiner.net.au emails), this explains why DMARC warns that the email is "unaligned" – eg. the email says it's from @hotmail.com instead of @gardiner.net.au.

The problem is that while Hotmail does support adding additional email accounts, it only lets you configure ability to pull emails (via POP3) – you can't enter a SMTP server to send emails. Sent emails always go out via the Hotmail SMTP servers.

One solution might be to migrate the Hotmail user over to GMail and configure GMail to pull their @hotmail.com email instead (and unlike Hotmail, GMail can be configured to use a different SMTP server for addtional email accounts).

But just as I was considering this option, I came across the news that Google is discontinuing support for Exchange ActiveSync! Why does this matter? Well as a Windows Phone user this has the potential to be a show stopper. ActiveSync is the protocol used to sync my GMail, Contacts and Calendar between my phone and Google. Whilst it does say that existing configured devices will continue to function, too bad if I change phones in the future.

So another option might be to switch my domain's email over to Hotmail instead of staying with GMail, unless Microsoft can release updates for Windows Phone that restore compatibility with GMail after January 31st.

Sunday, 16 December 2012

Passed 70-480

Last Thursday, I took advantage of the promotion that Microsoft is running (until 31st March 2013) to give everyone a chance to take the 70-480 Programming in HTML5 with JavaScript and CSS3 exam for free, and was pleased to find that I passed (scoring 860).

Completing this means I now gain the following certifications:

As an MCT, it also means I could now teach courses in those areas too.

It is nice to pick up the MCSD. This is the replacement for what was previously the MCPD certification. I had qualified for "ASP.NET Developer 3.5", but never got around to completing the .NET 4.0 version (due to not having taken the WCF exam).

Wednesday, 12 December 2012

DMARC and SPF updates

A while back I added a DMARC entry in DNS for my @gardiner.net.au domain. The existence of this entry then means I get daily email reports which include data from a number of email servers (eg. Google, Yahoo) about emails received from my domain and whether they were regarded as legitimate or spam. I don't receive copies of the emails themselves – just a summary of how many emails the destination site thought were legitimate and how many were rejected because they thought they were spam.

Microsoft have just announced that they too are now sending DMARC reports, so this prompted me to review my current SPF and DMARC settings to ensure that they're working properly.

The trouble with the DMARC reports are that they come via email with an attached zipped .XML file, which means you can't just view them... you have to download them, unzip them, then open it in IE (or Notepad), and scan through the XML to try and make sense of it. Wouldn't it be nice if there was a tool or service that summarised this for you?

Well it turns out there are some. I've decided try try two out - http://dmarcian.com and DMARC Analyzer.

Both of these services allow you to upload existing DMARC reports or set up email forwarding to automatically send the reports directly. You can then log in and view a summary.

I uploaded the data from the last 7 days. Here's some examples of the kind of report you get from each service:

dmarcian.com

Graph of DMARC results for last 7 days

The details for data from the 9th of December:

image

GARDINER.NET.AU - 3 msgs, 3 IPs
  • SPF-Authorized Servers - 2 groups , 2 msgs, 2 IPs, 100% auth'd
  • Other Servers - 1 group , 1 msg, 1 IP, 0% auth'd
  • 65.54.190.25 (bay0-omc1-s14.bay0.hotmail.com), 1 msg, 0% auth'd
  • 1 msg, disposition: None (monitor only) [none], DMARC-DKIM: fail (raw: none, d=none), DMARC-SPF: fail (raw: pass, dom: hotmail.com)
  • DMARC Analyzer

    Graph of DMARC results for last 7 days

    I was curious that both of them flagged a potential problem with an email. Sometimes this can be because it is actually spam – an email sent from an address that was not part of the authorised sender list as defined in the SPF record. But in this case, the error indicated that the email did come from a legitimate source.

    Next step to confirm that my SPF record is correct. A quick trip to the SPF Record Testing Tools confirmed that yes, my SPF record was in effect, but that there was also an error message I hadn't noticed previously:

    PermError SPF Permanent Error: Too many DNS lookups

    So it turns out that there are limits on how many DNS lookups are allowed for SPF records. 10 to be precise.

    My old SFP record was:

    v=spf1 a mx ip4:203.59.1.0/24 include:aspmx.googlemail.com include:hotmail.com include:gmail.com include:live.com -all

    It does looks like there's some redundancy there with two similar includes covering GMail and another two for Hotmail/Live. Simplifying things down (and hopefully not losing any accuracy) I've changed the record to this:

    v=spf1 a mx ip4:203.59.1.0/24 include:hotmail.com include:_spf.google.com ~all

    This now passes validation. Note that I've reverted back to ~all (a 'Soft' fail which means that recipients won't outright reject emails if there is a problem with the new rule). I'll switch back to -all (a 'hard' fail) after a week or two once I'm happy that nothing is broken!

    I'll also be interested to see if the DMARC reports contain passing results for the hotmail emails.

    Wednesday, 31 October 2012

    Passed 4, failed 3

    It seems my 'perfect record' of passing Microsoft exams has finally come to an end.

    During August and September a large number of new exams were made available for 'beta' testing before their public release. Somehow I managed to take 7 exams over this time – most relating to developing Windows 8 applications.

    The final results are now published I've passed:

  • 70-483: Programming in C#
  • 70-485: Advanced Windows Store App Development using C#
  • 70-486: Developing ASP.NET MVC 4 Web Applications
  • 70-487: Developing Windows Azure and Web Services
  • But unfortunately I didn't do so well for the remainder:

  • 70-481: Essentials of Developing Windows Store Apps using HTML5 and JavaScript
  • 70-482: Advanced Windows Store App Development using HTML5 and JavaScript
  • 70-484: Essentials of Developing Windows Store Apps using C#
  • Failing the HTML5 exams wasn't much of a surprise. I really didn't know the technology very well at all, so it was reasonable that I didn't pass. I figured that it would still be a good learning experience to do the exams, and hopefully I'd pick up a few concepts along the way.

    70-484 was a bit more disappointing, but I'm pleased that by the time I took 70-485 I'd had a chance to spend a bit more time playing around with developing for Windows 8 – and that obviously paid off. I think I would like to do that one again in a few months, most likely after I've published some apps to the Windows 8 Store – that should then qualify me for the MCSD Windows Store Apps.

    Another exam that I wasn't offered in beta was 70-480 – "Programming in HTML5 with JavaScript and CSS3". That may be another one to do in the future, as then I'd also add MCSD Web Applications.

    One side-benefit of passing 70-483 means that I achieved the following certification: Programming in C# Specialist

    Tuesday, 16 October 2012

    Happy ending

    One day during the school holidays, I organised to ride with my two oldest kids down part of the veloway and finishing up at Christies Beach. They both rode very well, and we met up with the rest of the family and Narelle's parents for fish and chips overlooking the sea.

    WP_000141

    I wouldn't put myself in the same category as Jen but I think it's still a nice photo.

    Sunday, 14 October 2012

    TechEd 2012–Day 3

    Caramel slice!On the Friday I went to 3 sessions:

    • DEV331 - A Modern Architecture Review: Using the New Code Review Tools
    • DEV333 - LightSwitch 2012 - Even Faster
    • WPH234 - Windows Phone Marketplace - Satisfy More Customers and Make Money

    I also ran my Instructor-led Lab - DEV ILL100 - Designing Windows 8 HTML apps in Blend. A nice lab which is probably more about getting familiar with Blend than with Windows 8 apps.

    Locknote arena

    After the closing keynote, I didn't need to rush off as Narelle and I were staying another night, flying home Saturday afternoon.

    I also had a chance to briefly visit the beach. After quite a few visits to the Gold Coast, I had never until now managed to get down to the foreshore. A bit cool to think about going for a swim by that time, but nice to finally see it.

    Gold Coast beach

    Saturday, 13 October 2012

    TechEd 2012–Day 2

    Ben car racingThursday was a busy day. I got to these sessions:

    • DEV221 – Design and Layout for Windows 8 and Windows Phone Style Apps
    • WPH323 – Build smart: Developing for Windows Phone and Windows 8
    • DBI225 – Big Data for Relational practitioners
    • DEV326 – Agile planning tools in TFS 2012

    Band BashI also helped in the hands-on labs and assisted with "ILL102 - Creating an Windows 8 Line of Business Application - HTML & JavaScript from Start to Finish".

    In the evening was the party night – lots of food and entertainment options! I watched Ben show off his prowess on the racing simulator, then after a good feed we headed to the main arena to catch some comedy, quiz show and an 'open mike' band bash. Turns out there's some pretty talented musicians masquerading as IT/Dev-types!

    Friday, 12 October 2012

    TechEd 2012–Day 1

    DJ with touch screenWednesday I caught the following sessions throughout the day:

    • DEV211 – What's New in VS2012 with Adam Cogan
    • DEV212 – Windows 8 Development with Nick Hodge
    • DEV214 – Windows 8 Development Part 2 with David Burela
    • DEV215 – .NET 4.5: Just the good stuff with William Tulloch

    I ran my first 'ExamCram' session – on Exam 70-599 - Pro: Designing and Developing Windows Phone 7 Applications.

    With Windows Phone 8 just around the corner, I wasn't sure if I'd have many takers. As it turns out, I had exactly one person. A high student-teacher ratio!

    I was also on the late-shift for helping in hands on labs today, from 5pm – 8pm.

    Thursday, 11 October 2012

    TechEd 2012–Day 0

    Island Beach Resort, view from streetA few weeks late, but I've finally got around to writing about this year's Microsoft TechEd conference held in September. Tuesday morning, Narelle and I flew up to Brisbane then caught the train down to the Gold Coast.

    This year I'd booked accommodation at Island Beach Resort, quite close to the convention centre. It might have 'Resort' in the name, but it's really just some nice apartments.

    After checking in, I headed down to the convention centre for registration and then the Technical Learning Guide orientation meeting. Unfortunately this was at the same time as the the first "kick off" session. The meeting was useful as there was a different company organising the hands-on labs this year.

    The keynote welcome included some interesting Kinect-inspired performance art, and then a talk (and videos) from Jason Silva. Jason was a very intense presenter – much the same as in his videos. I did like his positive attitude towards the future. His talk was very fast-paced and full-on, such that I think I'm still chewing over the things he said (even a week later).

    I happened to see him again as one of the guests on ABC-TV's QandA program about a week after TechEd. To my surprise he said almost exactly the same things, word for word. Not quite as impressed after that.

    Wednesday, 29 August 2012

    Heading to TechEd Australia 2012

    I'm looking forward to heading off to Microsoft's TechEd 2012 conference on the Gold Coast in a couple of weeks. This year I'll be joined by colleagues Ben and Imran, who will both be first-time attendees.

    It promises to be a particularly good year for getting up to speed on new products, with Visual Studio 2012, Windows 8 and Server 2012 hot off the press, and Windows Phone 8 just around the corner.

    I'm again working for part of the conference as a Technical Learning Guide assisting in the hands-on labs. I've also been selected to lead an instructor-led lab on "Designing Windows 8 HTML apps in Blend" (Session 'DEV ILL100'), and an exam cram session on 70-599 "Pro: Designing and Developing Windows Phone 7 Applications".

    It will also be great to have Narelle along, who be enjoying a few days holiday while I'm busy learning, instructing and cramming.

    Tuesday, 21 August 2012

    MatrixGroup is hiring

    Are you a good great .NET software developer who'd like to work for a company providing solutions to the commodity handling industry (eg. grain and mineral ores)?

    You might want to apply for this position with MatrixGroup, and have the pleasure of working with a bunch of enthusiastic, smart people… and yes I work there too Smile

    Friday, 17 August 2012

    Windows 8 Mail and Exchange using a self-signed certificate

    The following steps allowed me to get the Windows 8 Mail app to talk to an Exchange server which uses a self-signed certificate:

    1. Open up Internet Explorer in 'Administrator' mode
      1. Go to the Windows 8 desktop
      2. Right-click on the Internet Explorer icon
      3. Highlight 'Internet Explorer'
      4. press Shift-Ctrl-Enter to launch IE in 'Administrator' (elevated permission) mode
    2. Browse to the Exchange server's Outlook Web Access page – eg. https://yourexchangeserver.com/owa
    3. Ignore any warning about certificates – click on 'Continue to this website'
    4. Click on the red certificate warning in the address bar
      certificate error
    5. Click on 'View certificates'
      Untrusted certificate
    6. Click on 'Install certificate' button
    7. The 'Certificate Import Wizard' appears
    8. Leave 'Store Location' as current user
    9. Select 'Place all certificates in the following store', and click on the 'Browse' button to select 'Trusted Root Certification Authorities'
      Certificate import wizard
    10. Complete wizard
    11. Click on 'Yes' to install certificate
    12. Close IE and reopen (in non-admin mode) to confirm when browsing to the OWA URL that you no longer are warned about an invalid certificate

    You should now be able to use the 'Add an account' to add your Exchange account.

    Friday, 3 August 2012

    My next Windows Phone(s)

    It's been an interesting journey since I purchased my first Windows Phone. The Samsung Omnia 7 had a brilliant screen and was a joy to use. It would occasionally spontaneously restart, but I put that down to firmware/OS bugs that would be fixed in subsequent updates. Many updates later however, the problem actually got worse rather than better.

    While it was away on the last of a number of visits to the service centre, I purchased a second hand (but excellent condition) HTC Mozart. A very capable phone, it was a good replacement.

    Samsung finally agreed to a replacement (only after I filmed my handset rebooting – somehow they'd never managed to reproduce the problem themselves) – but by this time the Omnia 7 was no longer in stock, so I was given an Omnia W instead.

    The Omnia W didn't seem quite as 'solid' as the 7 – probably the plastic case vs the 7's metal, but it worked well (and didn't spontaneously reboot like it's predecessor).

    Nokia Lumia 800

    I would have stuck with the Omnia W but for the fact that I was one of the winners of a new Nokia Lumia 800 for entering my Asthma First Aid app in the Windows Phone Apps Download Challenge.

    The Mozart and Omnia W have now found new homes with friends and family, and I'm enjoying my nice blue Lumia.

    Monday, 9 July 2012

    Feature Toggle libraries for .NET

    The idea of "Feature Toggles" is described in detail by Martin Fowler. Essentially these give you the ability to enable/disable parts of your application via configuration.

    It's a simple enough concept that you can quite easily do this in code. Just add a new setting to your app.config file:

    <appSettings>
        <add key="Feature.Toggle1" value="True"/>
    </appSettings>

    And then within your code, check the config value to enable the feature:

    if (bool.Parse(ConfigurationManager.AppSettings["Feature.Toggle1"]))
    {
    
    }

    Ideally you'd make that check a bit more robust, but you get the idea!

    Alternatively, you might want to use one of these libraries that implement feature toggling. I searched for .NET feature toggle libraries and found these four. Coincidentally they are all hosted on Github, and all but nToggle are available as NuGet packages.

    NFeature

    https://github.com/benaston/NFeature

    NFeature requires you to create an enum which defines all the feature toggles. It then generates extension methods such that you can check the config setting for a feature. Features are configured through a custom section in the .config file. Features can optionally be configured to depend on other features, and to have availability times set.

    <configSections>
        <section name="features" type="NFeature.Configuration.FeatureConfigurationSection`1[[NFeatureTest2.Feature, NFeatureTest2]], NFeature.Configuration" />
    </configSections>
    
    <features>
        <add name="MyFeature" state="Enabled" />
        <add name="MyOtherFeature" state="Previewable" />
        <!-- will only be available to users who meet the feature-preview criteria -->
        <add name="MyOtherOtherFeature" state="Disabled" />
    </features>
    public enum Feature
    {
        MyFeature,
        MyOtherFeature,
        MyOtherOtherFeature,
    } 
    
    ...
    
    if ( Feature.MyFeature.IsAvailable( featureManifest ) )
    {
        //do some cool stuff
    }

    NFeature appears to be actively maintained with recent commits.

    FeatureToggle

    https://github.com/jason-roberts/FeatureToggle

    FeatureToggle provides base classes from which you inherit for each feature toggle you want to implement. These toggles are configured in the <appSettings/> section in the .config file.

    <appSettings>
       <add key="FeatureToggle.MyAwesomeFeature" value="false" />
    </appSettings>
    public class MyAwesomeFeature : SimpleFeatureToggle {}
    
    ...
    
    if (!MyAwesomeFeature.FeatureEnabled)  
    {
       // code to disable stuff (e.g. UI buttons, etc)
    }

    FeatureToggle also has support for WPF and Windows Phone apps.

    It is actively maintained with recent commits.

    FeatureSwitcher

    https://github.com/mexx/FeatureSwitcher

    In FeatureSwitcher, toggles are created by implementing the IFeature interface. You then use the Feature<> generic class to test whether a particular toggle is enabled.

    Toggles are configured in the .config file via custom sections.

    <configSections>
      <sectionGroup name="featureSwitcher" type="FeatureSwitcher.Configuration.SectionGroup, FeatureSwitcher.Configuration">
        <section name="default" type="FeatureSwitcher.Configuration.DefaultSection, FeatureSwitcher.Configuration"/>
        <section name="features" type="FeatureSwitcher.Configuration.FeaturesSection, FeatureSwitcher.Configuration"/>
      </sectionGroup>
    </configSections>
    <featureSwitcher>
      <default featuresEnabled="true"/>
      <features>
        <feature name="FeatureSwitcher.Examples.BlueBackground" enabled="true"/>
      </features>
    </featureSwitcher>
    class BlueBackground : IFeature {}
    
    ...
    
    if (Feature<Sample>.Is().Enabled)
    {
    
    }

    FeatureSwitcher is actively maintained with recent commits.

    nToggle

    https://github.com/SteveMoyer/nToggle

    nToggle uses a custom section in the .config file. The examples given show usage with a WebForms app, though it should work with all kinds of applications.

    <configSections>
      <section
        name="nToggle"
        type="nToggle.Configuration.ToggleConfigurationSection"
        allowLocation="true"
        allowDefinition="Everywhere"
        />
    </configSections>
    
    <nToggle>
      <toggles>
        <add name="TestFeatureOn" value="True"/>
        <add name="TestFeatureOff" value="False"/>
      </toggles>
    </nToggle>
    <%@ Register assembly="nToggle" namespace="nToggle" tagprefix="nToggle" %>
    <nToggle:WebFeatureToggle ID="FeatureToggle1" EnabledBy="TestFeatureOff" runat="server" >
        <span id="enabledby"> Feature Turned Off</span>
    </nToggle:WebFeatureToggle>
    protected void Page_Load(object sender, EventArgs e)
    {
        WebFeatureToggle1.RunActionWhenEnabled(CodeToRunIfEnabled);
    }
    
    protected void CodeToRunIfEnabled()
    {
        //your code
    }

    nToggle has not been updated for 7 months at time of writing.

    Friday, 8 June 2012

    Improving my home wireless network

    I noticed my wireless connection was a bit slow this morning, so after restarting the router (standard "IT Crowd – Have you tried turning it off and on again" approach), I thought it would be worth seeing what other wireless networks were nearby and whether they might be causing some contention for bandwidth.

    Years ago I used NetStumbler to do this kind of thing, but it hasn't been updated in a long time and doesn't work with the wireless card on my laptop. Instead I found inSSIDer. It does the job nicely - this is what it revealed:

    wireless channels before

    My network is the orange one named 'Gardiner'. I've obscured some of the labels to protect privacy.

    After switching my router's wireless settings to channel 5, it now looks like this:

    wireless channels after

    That looks better.

    Other options for improving my wireless performance would be upgrading the router to something that support 802.11n (my trusty old Billion only handles 11b + 11g), and maybe getting a wireless repeater to improve coverage around the house.

    Sunday, 27 May 2012

    White men can't?




    Posted by Picasa

    It is very important to test out new play equipment before letting the kids have a go :-)

    Friday, 25 May 2012

    NDepend v4

    I read today that NDepend v4 is now released.

    Probably the most interesting change is the addition of CQLinq – a LINQ language for querying the code base that supersedes the old “CQL” query language (that was more SQL-like).

    It also includes support for Visual Studio 11 and a new API to make creating your own static analyzer(s). There’s a full list of new features in the Release Notes.

    Upgrades from v2/3 are half the cost of a v4 license.

    If you haven’t upgraded from v3 to v4, you can still download the latest v3 from the downloads page (it will say that your v3 license key isn’t valid for v4 but give you a link to the v3 download)

    Friday, 18 May 2012

    Editing project files in Visual Studio

    The standard way of editing a project file in Visual Studio is to first unload the project, and then right-click on the unloaded project in the Solution Explorer and choose 'Edit Project'.

    Wouldn't it be nice if this was just one step instead of two?

    Well one option is to install the Visual Studio PowerCommands or VSCommands extensions, both which add an 'Edit Project' option to the context menu for loaded projects. But if that's the only feature you want then installing one of those extensions might be more than you need.

    Another option is to create a Visual Studio Macro and then add that to the context menu manually. Here's how:

    1. First, go to the Tools menu and choose Macros | Macros IDE.
    2. A "Microsoft Visual Studio Macros" IDE window appears.
    3. In the project explorer, right-click on 'MyMacros' and choose Add | Add Module
    4. Name the module "Projects"
    5. Within the new module, add the following code:
      Public Sub EditProject()
          ' Ensure only projects are selected
          For Each item As SelectedItem In DTE.SelectedItems
      
              If Not (TypeOf item.Project Is Project) Then
                  MsgBox("Can't open project(s) for editing as non-project items are selected", MsgBoxStyle.Exclamation)
                  Exit Sub
              End If
          Next
      
          ' ensure solution is active
          DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
      
          For Each item As SelectedItem In DTE.SelectedItems
              DTE.ExecuteCommand("Project.UnloadProject")
              DTE.ExecuteCommand("OtherContextMenus.StubProject.EditProjectFile")
      
          Next
      
      End Sub
    6. Save and close the Microsoft Visual Studio Macros IDE
    7. Now you can associate a keyboard shortcut with this new macro, and you can optionally add it to the project context menu.

     

    1. To add it to the context menu, go to Tools | Customize
    2. Select the 'Commands' tab and choose 'Project and Solution Context Menu | Project' in the Context Menu dropdown list.
    3. Scroll down the list of controls for this menu to find the location where you'd like this macro to appear.
    4. Select an existing control, and click on 'Add Command'
    5. Select the 'Macros' category
    6. Select the new macro from the Commands list and click on 'OK'
    7. Click on 'Modify Selection' to rename the menu item for the macro to 'Edit Project'
      Visual Studio Customize dialog window

    Now, when ever you use the keyboard shortcut or the new 'Edit Project' from the Solution Explorer's context menu, the selected project(s) will now be opened in the text editor.

    Wednesday, 16 May 2012

    Asthma First Aid–Windows Phone app

    Screenshot of Asthma First Aid running in Nokia Lumia 800Asthma First Aid is a free app I've developed in conjunction with Asthma Australia. It informs you of the recommended first aid steps for someone experiencing an asthma attack.

    It features a one-click phone dialler to call '112' – the international standard mobile emergency number.

    Everyone should know Asthma first aid, so if you have a Windows Phone, install it today

    Download from the Windows Phone Marketplace.

    Thursday, 5 April 2012

    SQLSaturday #139 (on a Tuesday)

    PASS SQL Saturday LogoI think continuous learning is an integral part of being a professional. On the 24th April 2012, there's an opportunity for everyone with an interest in SQL Server to further their knowledge at the first SQLSaturday event to be held in Adelaide – SQLSaturday #139 (even though it's on a Tuesday!)

    As the number suggests, these events have been running around the world for a while, but this is the first (hopefully of many) to be held here.

    The schedule is now finalised and includes two tracks. Speakers include Paul White from New Zealand, Paul te Braak and Peter Ward from Brisbane, Raja N from Sydney, and local luminaries such as Rob Farley, Roger Noble, Andrew Butenko and even me!

    So what would you expect to pay for a conference featuring this kind of expertise and experience? $200? $500? $1,000? Well you could pay that if you wanted to, but actually the whole day is free, thanks to the generosity of the event sponsors and speakers.

    So run (don't walk) to the registration page and sign up today. Then go and let your manager/boss/social secretary know where you'll be on the 24th!

    See you there Smile

    Monday, 2 April 2012

    Naked ADSL and VoIP 2012-style

    Last month I decided to take the plunge and switch over to a Naked ADSL service. Late last year Internode had announced that they'd sorted out number porting for customers on existing ADSL2+ connections (like myself). More recently they also introduced a new tier in their Naked plans, so that leaving my old 50G ADSL2+ plan was now viable.

    The switch to a naked line happened on the scheduled day. I didn't realise however that the porting of the phone number would take a few extra days. Not a huge problem, but something to be aware of.

    Going naked means you have no dial-tone. If you want to keep your phone number, it needs to be ported to a VoIP service. In this case to NodePhone (Internode's VoIP offering), with $10/month call credit. I've been using first FreeCall and more recently PennyTel as outgoing VoIP providers for a number of years. It will be interesting to see how NodePhone compares.

    Two nice features that I discovered was it comes with voicemail (you can customise the greeting, and you can get email notifications), and caller ID is included (instead of paying $6/month for the privilege)

    I configured my trusty ATA (a Sipura SPA-3000) with new settings to work with NodePhone. All seemed fine, but it didn't work – the registration was failing. A call to Internode Support didn't identify any issues other than I was using more recent firmware than they were aware of, and that I was using relatively 'old' hardware. In any case just when I was about to give up for the evening, I noticed that it had started working all by itself.

    All seemed fine for a few days, then I noticed that we were getting calls going to voicemail but no missed calls were on the phone. Strangely the ATA was still saying it was registered but most calls never rang the phone – they just redirected to voicemail.

    Another call to Internode support, but again no joy. Nothing looked out of place with my settings, but they could see the registration was dropping out regularly (which would explain the calls not coming through to the handset). Their final suggestion – get some new hardware.

    It turns out I bought the Sipura SPA-3000 way back in 2005. 7 years is a good innings for consumer hardware, so maybe it was time to update to something more current. The Gigaset C610 seemed to be well regarded so I picked up one from Internode's Adelaide office.

    It was pretty straightforward to configure, but annoyingly I then discovered I was having the same problem still. Another call to Internode Support, but this time they said they had TWO active registrations – one for the Gigaset and one for the SPA! That didn't make sense, as the SPA was now sitting on a shelf – no power, no network. They reviewed the modem and Gigaset settings and things seemed to settle down, and I received an incoming call ok.

    So hopefully that's the way things will stay for now on Smile

    Friday, 16 March 2012

    MetroYam–Windows Phone Edition

    Part 3 of a series on developing .NET clients for Yammer.
    For the Windows Phone version of MetroYam, I'm again making use of RestSharp to handle the REST API work. Once difference when using RestSharp on Windows Phone compared to the desktop is that it only exposes asynchronous methods.
    So instead of calling Execute, we have to call the ExecuteAsync method. Here's the equivalent code:
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using Gardiner.MetroYam.Core;
    using RestSharp;
    using RestSharp.Authenticators;
    
    namespace Gardiner.MetroYam.PhoneClient
    {
        public class Network
        {
            private readonly ObservableCollection<MessageViewModel> _messages;
    
            public Network( ObservableCollection<MessageViewModel> messages )
            {
                _messages = messages;
            }
    
            public void Request()
            {
                var client = new RestClient();
    
                client.BaseUrl = "https://www.yammer.com/";
                string consumerKey = "12312341234243";
                string consumerSecret = "kciud84kf0943kdi4kdk3kr";
    
                var oauth_token = "84944kf984444";
                var oauth_token_secret = "kdiekemfckcfiejekmekduj454j4";
    
                var request = new RestRequest( "api/v1/messages.json", Method.GET );
                client.Authenticator = OAuth1Authenticator.ForProtectedResource(
                    consumerKey, consumerSecret, oauth_token, oauth_token_secret
                    );
    
                // 2011/03/28 20:39:12 +0000
                request.DateFormat = "yyyy/MM/dd HH:mm:ss zzzzz";
    
                client.ExecuteAsync<MessageList>( request, Callback );
            }
    
            private void Callback( RestResponse<MessageList> response, RestRequestAsyncHandle arg2 )
            {
                var users = new Dictionary<int, User>();
                foreach ( Reference reference in response.Data.References )
                {
                    if ( reference.Type == "user" )
                        users.Add( reference.Id, new User { Fullname = reference.FullName, Photo = reference.mugshot_url } );
                }
    
                foreach ( var message in response.Data.messages )
                {
                    var vm = new MessageViewModel();
                    vm.Body = message.Body.Plain;
                    vm.Created = message.created_at.LocalDateTime;
                    User user = users[ message.sender_id ];
                    vm.Sender = user.Fullname;
                    if ( user.Photo != null )
                        vm.PhotoUrl = user.Photo;
                    else
                        vm.PhotoUrl = new Uri( "https://c64.assets-yammer.com/images/no_photo_small.gif" );
    
                    _messages.Add( vm );
                }
            }
        }
    }
    Rather than a single method that returns the list of messages, we now have a class that populates an ObservableCollection. A reference to this is passed in via the constructor. The ViewModel page that calls this code is straight forward:
    using System;
    using System.Collections.ObjectModel;
    using Caliburn.Micro;
    using Gardiner.MetroYam.Core;
    
    namespace Gardiner.MetroYam.PhoneClient
    {
        public class MainPageViewModel : Screen
        {
            private ObservableCollection<MessageViewModel> _messages;
            public ObservableCollection<MessageViewModel> Messages
            {
                get { return _messages; }
                set { _messages = value;
                    NotifyOfPropertyChange( () => Messages );
                }
            }
    
            public MainPageViewModel()
            {
                Messages = new ObservableCollection<MessageViewModel>();
            }
    
            protected override void OnInitialize()
            {
                base.OnInitialize();
    
                var network = new Network( Messages );
    
                network.Request();
            }
        }
    }
    Through the magic of Caliburn Micro, the public properties exposed by this ViewModel class are automatically bound to the View. In this case we are using a simple Panorama-based Windows Phone application template. The result is an application that looks like this:
    Screenshot of MetroYap on Windows Phone
    It would be nice leverage the Reactive Extensions (Rx) library to simplify access to the ExecuteAsync method, but because this method doesn't just return an IAsyncResult this is a little more involved. Sergy posted to the RestSharp group an example last year, but I assume the RestSharp and Rx libraries have changed since then as his code no longer compiles for me.

    Thursday, 15 March 2012

    Internal MSBuild Error: MSB0001

    Encountered this weird error from MSBuild today:

    Unhandled Exception: Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: MSB0001: Internal MSBuild Error: Global Request Id 6 has not been assigned and cannot be retrieved. 
    ============= 
    Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: Global Request Id 6 has not been assigned and cannot be retrieved. 
       at Microsoft.Build.Shared.ErrorUtilities.ThrowInternalError(String message, Object[] args) 
       at Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(Boolean condition, String unformattedMessage, Object arg0) 
       at Microsoft.Build.BackEnd.SchedulingData.GetScheduledRequest(Int32 globalRequestId) 
       at Microsoft.Build.BackEnd.Scheduler.HandleRequestBlockedOnInProgressTarget(SchedulableRequest blockedRequest, BuildRequestBlocker blocker) 
       at Microsoft.Build.BackEnd.Scheduler.ReportRequestBlocked(Int32 nodeId, BuildRequestBlocker blocker) 
       at Microsoft.Build.Execution.BuildManager.HandleNewRequest(Int32 node, BuildRequestBlocker blocker) 
       at Microsoft.Build.Execution.BuildManager.Microsoft.Build.BackEnd.INodePacketHandler.PacketReceived(Int32 node, INodePacket packet) 
       at Microsoft.Build.BackEnd.NodeManager.RoutePacket(Int32 nodeId, INodePacket packet) 
       at Microsoft.Build.BackEnd.NodeProviderInProc.RoutePacket(Int32 nodeId, INodePacket packet) 
       at Microsoft.Build.BackEnd.NodeEndpointInProc.SendData(INodePacket packet) 
       at Microsoft.Build.BackEnd.InProcNode.OnNewRequest(BuildRequestBlocker blocker) 
       at Microsoft.Build.BackEnd.BuildRequestEngine.RaiseRequestBlocked(BuildRequestBlocker blocker) 
       at Microsoft.Build.BackEnd.BuildRequestEngine.IssueBuildRequest(BuildRequestBlocker blocker) 
       at Microsoft.Build.BackEnd.BuildRequestEngine.IssueUnsubmittedRequests() 
       at Microsoft.Build.BackEnd.BuildRequestEngine.EngineLoop()

    Turns out I had a <Target /> with a DependsOnTarget attribute which referenced a target that didn’t exist (because I’d forgotten to <Import /> the related .targets file in which it was defined!)

    Wednesday, 14 March 2012

    MetroYam–WPF desktop edition

    Part 2 of a series on developing .NET clients for Yammer.

    The first client that I've attempted is the WPF version. This version of MetroYam uses the MahApps.Metro library to enable a Metro interface.

    Once the UI framework is in place it's time to register with Yammer for an API key. You need this to access their REST API. Initially this just allows you to access the Developer Yammer group, but it may be possible to apply to Yammer to make it useable by any Yammer user.

    Using the OAuth1 integration test from the RestSharp source code repository as a guide, I created a class with the following method prototype accessing a feed of messages (keys changed to protect the innocent!)

    public List<MessageViewModel> Request()
    {
        var client = new RestClient();
                
        client.BaseUrl = "https://www.yammer.com/";
        string consumerKey = "0121212121212";
        string consumerSecret = "abcdefgkskdlasllkjsfien3234sdfsdf";
    
        client.Authenticator = OAuth1Authenticator.ForRequestToken( consumerKey, consumerSecret );
        var request = new RestRequest( "oauth/request_token", Method.POST );
                
        var response = client.Execute( request );
    
        var qs = HttpUtility.ParseQueryString( response.Content );
        var oauth_token = qs[ "oauth_token" ];
        var oauth_token_secret = qs[ "oauth_token_secret" ];
    
        request = new RestRequest( "oauth/authorize" );
        request.AddParameter( "oauth_token", oauth_token );
        var url = client.BuildUri( request ).ToString();
        Process.Start( url );
    
        var verifier = "01234"; // <-- Breakpoint here (set verifier in debugger)
    
        request = new RestRequest( "oauth/access_token", Method.POST );
        client.Authenticator = OAuth1Authenticator.ForAccessToken(
        consumerKey, consumerSecret, oauth_token, oauth_token_secret, verifier
        );
        response = client.Execute( request );
    
        qs = HttpUtility.ParseQueryString( response.Content );
        oauth_token = qs[ "oauth_token" ];
        oauth_token_secret = qs[ "oauth_token_secret" ];
    
        Debug.WriteLine("oauth_token {0}", oauth_token);
        Debug.WriteLine( "oauth_token_secret {0}", oauth_token_secret );
    
        request = new RestRequest( "api/v1/messages.json", Method.GET );
        client.Authenticator = OAuth1Authenticator.ForProtectedResource(
        consumerKey, consumerSecret, oauth_token, oauth_token_secret
        );
    
        // 2011/03/28 20:39:12 +0000
        request.DateFormat = "yyyy/MM/dd HH:mm:ss zzzzz";
    
        RestResponse<MessageList> responseList = client.Execute<MessageList>( request );
    
        var users = new Dictionary<int, User>();
        foreach ( Reference reference in responseList.Data.References )
        {
            if ( reference.Type == "user" )
                users.Add( reference.Id, new User() { Fullname = reference.FullName, Photo = reference.mugshot_url});
        }
    
        var messages = new List<MessageViewModel>();
    
        foreach ( var message in responseList.Data.messages )
        {
            var vm = new MessageViewModel();
            vm.Body = message.Body.Plain;
            vm.Created = message.created_at.LocalDateTime;
            User user = users[ message.sender_id ];
            vm.Sender = user.Fullname;
            if ( user.Photo != null )
                vm.PhotoUrl = user.Photo;
            else
                vm.PhotoUrl = new Uri( "https://c64.assets-yammer.com/images/no_photo_small.gif" );
    
            messages.Add(vm);
        }
    
        return messages;
    
    }

    The Models (Message, User etc) and ViewModel (MessageViewModel) are declared in the 'portable' class library. This library is shared between the WPF, Windows Phone and Metro-style WinRT applications.

    This particular method causes a web browser to load part-way through to obtain a user authentication code. Normally an application would cache the oauth_token and oauth_token_secret values returned by the "oauth/access_token" call so the user wouldn't need to re-authenticate the app each time.

    The relevant XAML to render this is pretty straightforward (not that I'm a XAML expert like Nigel!)

    <ListView Grid.Column="1" Grid.Row="1" Margin="20,0,0,0" x:Name="Messages" 
                HorizontalContentAlignment="Stretch"
                ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ListView.ItemTemplate>
            <DataTemplate>
    
                <Border Padding="0,0,0,10">
                    <DockPanel ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
                        <Image Width="48" Height="48" Source="{Binding PhotoUrl, IsAsync=True}" DockPanel.Dock="Left" VerticalAlignment="Top" />
                        <TextBlock Text="{Binding Path=Sender}" DockPanel.Dock="Top" FontWeight="Bold" />
                        <TextBlock Text="{Binding Path=Created}" DockPanel.Dock="Top" />
                        <TextBlock Text="{Binding Path=Body}" DockPanel.Dock="Bottom" TextWrapping="WrapWithOverflow" />
    
                    </DockPanel>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    And to confirm that this does actually render something, here's a screenshot of work in progress:

    SNAGHTML86cea1b

    I like how the <Image/> element can take an Internet URL so that it just loads the photos automatically.

    My plan is that the client will default to the 'company' list in the first column, but let you add additional columns to follow other groups that you are a member of.

    Monday, 12 March 2012

    MetroYam–a .NET Yammer client (or three)

    I first came across Yammer via Rob Farley when I was working at LobsterPot Solutions. Imagine Twitter and Facebook morphed together, but just for a single organisation instead of publishing your thoughts for all the world (or your friends) to see. Yammer knows what information to show you because it's based around your email address – so for example everyone who has a @gardiner.net.au email address would be able to collaborate on Yammer together, without worrying that their posts would be seen by non @gardiner.net.au users.

    It's quite a useful platform for sharing resources and information amongst colleagues, and a useful adjunct to other communication methods (face to face, phone, email, IM). The basic offering is free, but if you want to have more administrative control you pay for the privilege.

    Access is via their website, but they also provide a number of platform-specific clients too. Unfortunately the Windows client isn't the greatest. I've had problems where it wouldn't auto-update, and the current version doesn't seem to have working notifications. It also uses Adobe AIR, which is just one more think to keep patched. It's enough to make you want to write a decent client and maybe learn a few things along the way – queue 'MetroYam' – my name for a Yammer client built on .NET.

    My plans are to build a number of clients:

    • a WPF-based Windows desktop application
    • a Metro-style app for Windows 8 (WinRT)
    • a Windows Phone app

    It will be interesting to see how much code I can share and reuse between the 3 different platforms.

    The inspiration for the user interface I plan to build is MetroTwit (my Twitter client of choice) – a great example of the Metro design that Microsoft are building into Windows 8.

    Screenshot of MetroTwit

    It won't be a carbon-copy but they've got some good ideas worth emulating.

    Resources

    Yammer API

    Lucky for me, Yammer have a REST API that they provide for things just like this. It uses OAuth for authentication and JSON.

    Caliburn Micro

    I plan to use Caliburn Micro for all three clients. I've used CM before, for my 'Aussie Toilets' Windows Phone app, so this will be a chance to explore it's support for WPF and WinRT.

    RestSharp

    For the WPF and Windows Phone clients, I'm going to make use of the RestSharp library to access the Yammer API as it provides support for JSON as well as OAuth. RestSharp doesn't support support WinRT, so I'll need to take a different approach there.

    Visual Studio 2010 + 2012 beta

    And finally it's happened! You can open solutions and projects in 2012 not be restricted from still using them in 2010. I'll need to use VS 2012 to build the WinRT projects, but I'll also need to still use VS2010 to build the phone app (as the 2012 beta doesn't come with Windows Phone support yet).

    Thursday, 8 March 2012

    Updating Castle Windsor from v2.5 to 3.0

    Late last year the Castle Windsor project released a major update to some of their components - from version 2.5 to 3.0.
    There are some breaking changes (most of which are documented in the BreakingChanges.txt files for Core and Windsor), so I thought I’d highlight a few issues that may be more common.

    ILogger

    The logging abstraction interface has been simplified, with a number of the overloaded methods removed. Whereas before you might have called ILogger.Debug("A message {0}", arg1), you now would use ILogger.DebugFormat() instead.

    Component Registration

    In 2.5 you could use the .Unless() method to add a condition (usually to skip registration if component already exists). This method has been replaced with the equivalent OnlyNewServices() method.

    IHandler

    This used to have a 'Service' property. Now refer to the IHandler.ComponentModel property which has a Services collection property.

    Facilities

    Previously, the IWindsorContainer.AddFacility() method required a string 'key' parameter. This is has been removed.

    IHandler.AddCustomDependencyValue

    This method has been removed, but you can now use the new Depends() method instead during registration. eg.
    Component.For<IService>()
    .ImplementedBy<Service>()
    .DependsOn(
        Dependency.OnValue("parameterName", "CustomValue")
    );

    IKernel.RemoveComponent

    This method has been removed. There is now no way to remove a component.

    Compatibility

    NServiceBus 2.x

    NServiceBus 3 is compatible with Castle 3.0, but if you're still using NServiceBus 2 and don't want to upgrade, you'll need to update your WindsorObjectBuilder class. Have a look at the source code in NServiceBus 3 and adapt it to your implementation - https://github.com/NServiceBus/NServiceBus/blob/master/src/impl/ObjectBuilder/ObjectBuilder.CastleWindsor/WindsorObjectBuilder.cs

    NHibernate

    As of NHibernate 3.2, there is no longer a requirement to have a Castle-compatible bytecode provider.
    The old NHibernateIntegration facility is not compatible with Castle 3.0 or NHibernate 3.2.
    There is an alternate facility- 'Castle NHibernate' that leverages FluentNHibernate for configuration. This has been patched to work with Castle 3.0 but not NHibernate 3.2 (partly because FluentNHibernate hasn't been updated for 3.2). It also depends on Castle.Transactions, which ironically whilst at version 3.0 isn't actually compatible with Castle 3.0 either.
    So for the meantime if you want to use NHibernate with Castle 3.0 you're going to have to get your hands dirty and update those dependant projects yourself.
    I've done some of this on forks of these projects, and will update this post to the source repositories when they are available.

    Friday, 2 March 2012

    Windows 8 first impressions

    Microsoft made the "Consumer Preview" of Windows 8 available yesterday, and last night I downloaded it and installed it on my laptop (using Boot to VHD so I wouldn't affect my existing Windows 7 install).

    First impressions are very positive. The install went smoothly, and I was able to add in my GMail account for the email and calendar apps.

    One of the first things that I noticed was the weather app was using Fahrenheit. This is when I discovered a handy keyboard shortcut [Windows]+[C], which brings up the 'Charms' which are where you can change settings for the current application (and so it was changed to Celsius).

    The new interface is quite a contrast from the traditional Windows desktop. Windows Phone users will find more similarities with the new desktop Metro interface. You can see the 'Metro-style' apps on the screen-shot above. They're the ones with the nice icons/tiles. The other icons are from the install of Visual Studio 2011. (This is a regular Windows application so you get regular icons). Again, like Windows Phone, the tiles for the new apps can update themselves (eg. the calendar, email etc).

    The email and calendar apps are simple but functional. A number of other apps aren't fully working at the moment (most due to 'regional' restrictions).

    So far it has been rock solid - no crashes to speak of.

    The Metro interface lends itself well to touch interaction. Unfortunately I don't have a touch screen on my laptop but I can certainly see how touch would work pretty well with it - again the similarities to Windows Phone help.

    I'll be experimenting with seeing what's involved with porting some of my Windows Phone apps to work on Windows 8.

    Saturday, 28 January 2012

    Imagine Cup Video - Team Apptenders from Croatia

    Some of the first projects I worked on when I began my stint at UniSA many years ago was a number of multimedia applications for the School of Physiotherapy (now part of the School of Health Sciences). This was pre-Web, so these were CD-ROM apps - back when the CD burner cost about $6,000 and blank CDs were $20 each! Hence my interest in this Imagine Cup entry - using a Kinect controller to aid in physiotherapy exercises. Brilliant! Having a speech pathologist sister in-law also makes me wonder whether the Kinect could have applications in that field too. It does have a 4-microphone array, but I I'm not sure if it could pick up facial gestures with enough detail.

    Sunday, 22 January 2012

    Tour Down Under 2012

    It's January again, and amongst other things means it's time catch up with the other riders of the Mud, Sweat & Gears bike club and participate in the Tour Down Under Challenge Tour ride.

    This year's 138km ride started in Norwood and finished up in Tanunda (in the Barossa Valley).

    Here's a map of the ride (the green line) and an elevation graph showing the two steep climbs - Anstey's Hill (20km) and Menglers Hill (120km). I entered the map on RunKeeper, but not sure why it doesn't show distances after 90km.

    Route map of ride

    This year was one of the best organised. There were regular refreshment stops along the route, and they were all well provisioned with water, and some with the obligatory bananas and fruit cake. I think there was over 6,000 participants this year – a bit less than last year (but then Lance wasn't riding this year so that's to be expected)

    I rode again with Dad (as featured in the January 18th edition of the Hills & Valley Messenger newspaper!). I've managed to do a bit more riding to work over the last few months, and I think that combined with a few longer training rides with Dad on the previous weekends made for a much better ride than last year for me.

    image

    We left Norwood just before the official start time of 6.30am and got to Tanunda around 1pm. Apart from a sore behind and tired legs I felt pretty good at the end. It was nice to make the finish and ride under the big arch (if you're too slow then they make you detour around so the professional riders have a clear run to the finish).

    It was a really good day to ride, with the first few hours being nice and cool. The sun only started to break through towards the end of our ride. By the time we finished it was pretty warm.

    image

    Tanunda is a pretty town and it was nice to have large leafy trees providing shade to the spectators watching approach to the finish line.

    Special thanks to Narelle's parents who transported Dad, I and our bikes to Norwood in the morning, and also followed us around the route, including right to the finish line. I'm glad I got a lift home with them rather than some people I saw who were riding back to Adelaide again after the ride!