Going Up…or…Going Down? OOP Inheritance!

A completely trivial subject coming up next…triggered by a really interesting debate I was involved with the other day. When dealing with class hierarchies and chaining constructor arguments through this hierarchy, what’s our mindset as to the direction of travel? Do we think of passing arguments ‘up’ or ‘down’ the hierarchy of objects (initially anyway, before any other classes constructors in the chain are called in the opposing direction)? The direction of travel, granted, is totally inconsequential and we’re dealing with a non-concrete concept anyway, but it seems like fun to debate it so why the hell not!

I’ve always thought of constructor arguments being chained ‘up’ to the ‘base’ class; which is where we hit the area that causes conceptual confusion. It was debated that a ‘base’ class ‘underpins’ something and sits as a stabilising entity at the ‘root’ (i.e. the bottom of a pyramid, the roots of a tree, foundations of a building, etc.). This is an argument that seems to make a lot of sense and made me wonder if I’d been inverting this in my head all along. There seemed to be a split amongst us about the direction of travel, which turned out to be a good laugh to be fair!

So, the word ‘base’ – it certainly does lend some weight to the idea that we should be visualising our way travelling down, even though I’ve always thought of flying my way upwards in this regard! I decided to see what I could find, to answer what is (most likely) the most non-sensical and unimportant of questions!

1) A poor man’s Google image search for ‘object inheritance example’:

Object Hierarchies.

Object Hierarchies.

There are some interesting things going on here, just from a visual perspective. Classic inheritance (X ‘is a’ Y) appears to show the ‘base’ class at the top of (or in a spatial sense, above other ‘nodes’) most object hierarchies; this is where I would always term a base class as a parent class. You can also see the odd example of the containment/delegation model on show (X ‘has a’ Y) with objects shown to the side of the referencing class (now we have to think about left and right! πŸ™‚ ).

2) Blogs and Wiki:

The above two resources again show a hierarchy configuration with the parent class being above inheriting (or derived classes) – This is how I’ve always viewed it. Sub-classes inherit from the parent class, like a family tree; this is where you consider a parent class as a family predecessor (like a Grandfather), with sub-classes inheriting traits (genes), like classes that represent the Father and subsequent Child. Therefore, passing of constructor arguments, in my head, occurs in an upwards direction (from the Child back towards the Grandfather).

3) Good Old Fashioned Book:

The next source is a good old fashioned book (a really great one, by the way): Pro C# 7: With .NET and .NET Core.

Again, the terms ‘parent’ class are substituted frequently for ‘base’ and when inheritance is discussed derived classes are denoted as ‘sub-classes’ or ‘children. These are always visualised with the parent/base class being shown at the top of the hierarchy tree. My mindset has always followed the logic outlaid here:

Constructor chaining occurs through the use of inheritance. A sub-class constructor method’s first task is to call its superclass’ constructor method. This ensures that the creation of the subclass object starts with the initialization of the classes above it in the inheritance chain.

4) Default behaviour of a Class Diagram:

Lastly, what does a standard class diagram look like if you drag and drop a set of related classes on to it (Enemy being the top level superclass, with Blob and Green Blob being descendants):

Example Classes.

Example Classes.

Example Default Diagram Generated.

Example Default Diagram Generated.

In this instance, the Enemy ‘base/parent’ class is shown at the top of the diagram with it’s derived classes below it. When a Green Blob is created we are chained through Blob to Enemy (via the ‘base’ keyword) in the first instance to instantiate the Enemy class members first – did we go up or down to get there, that is the question (and are we going up or down when each sub-classes constructor is called, in turn πŸ˜‰ )?

So, from a hierarchical perspective, should we illustrate diagrams with a ‘base’ class ‘above’ or ‘below’ it’s derived class? Or perhaps the debate lies in the semantics of how the process works? It could just be however the particular developer is wired in regards to how they see hierarchies in their own minds.

Everything I see (visually and from every resource I seem to pick up) seems to back up my initial idea that constructor arguments should be considered to be chained ‘up’ through the hierarchy to a parent class (as opposed to chained ‘down’ to the parent class), but it doesn’t hold true to everyone (with the classic debate over the word ‘base’, suggesting something lurking at the ‘root’); Perhaps my mind is slightly warped! Is there an answer, I don’t know. Does it matter to anyone, again I’m not sure! Has it been a waste of a few hours on a Saturday, almost certainly!

I did have a good search for constructor chaining in relation to going ‘down’ and it does come up, but only in regards to overloading constructors and chaining calls on a singular object (not inheritance-based). I couldn’t see an object hierarchy illustrated with the base class at the, um, base (as a bottom node) – but it could well be out there! If anyone else fancies chipping into a debate that probably has never been once asked for (ever!) then feel free to pitch in a comment below, I’d love to hear from you. Cheers all and happy coding!

Experimenting with Azure CDN

With the gradual piecing together of the Lego bricks forming the slow move over of the Frog & Pencil website to a more managed approach (building of a custom CMS and an all-around better ASP.NET MVC architecture) I thought it would be interesting to document the move over of Frog & Pencil images to a CDN. I was inspired to give this a go after watching Scott Hanselman make the switch for his podcast site images and other Azure Friday videos, as documented here:

Scott Hanselman lifting and shifting images over to a CDN.
Azure CDN with Akamai.

It seemed like a relatively painless process and is a step in the right direction for our site as a whole; so, let’s give it a go!

NOTE: A short way into this post I realised that I was making a few missteps. This is cool, I think, as I would rather document the journey I took with the mistakes listed, to be honest – #KeepingItReal! However, for sanity (mine and yours) I’ll specify the ‘correct’ order of events that you should follow here that you can marry up with the ramblings below:

  1. Sign in to the Azure Portal.
  2. Create a storage container, if you don’t already have one.
  3. Download and utilise a storage explorer application (such as Azure Storage Explorer).
  4. Create a CDN Profile and CDN endpoint (that ties explicitly to your storage container, in this instance).
  5. Go to your DNS settings and generate a CNAME property, mapping a custom domain to your CDN if you wish to.
  6. Optionally, learn how to programmatically interact with your storage container.

Azure Portal – First Steps (documenting the journey)

First things first, we must hop on over to the Azure Portal. I searched the marketplace for ‘CDN’ and clicked create in the right-hand pane, as shown:

Creating a CDN

Creating a CDN.

The next phase involves configuring a CDN profile. The profile needs to be given a name and should be attached to an Azure Subscription. I’ve created a new Resource Group, by specifying a name for it, but it is possible to select an existing one for use here. There are some guidelines surrounding Resource Groups, such as items within a group should share the same lifecycle; more details can be found within this handy documentation article, read away!

The Azure CDN service is, of course, global but a Resource Group location must be set, which governs where resource metadata is ultimately stored. This could be an interesting facet to consider if there are particular compliance considerations regarding the storage of information and where it should be placed. I’m going with West Europe either way; a nice, easy choice this time around.

As for pricing, I have decided to head down the Akamai route, using the Standard Akamai pricing tier. I will have to see how this ultimately pans out cost wise over time, but it seems reasonable:

Azure CDN Provider Pricing

Azure CDN Provider Pricing.

At this point, we can explicitly create a CDN endpoint (where resources will be ultimately exposed). The endpoint has a suffix of ‘.azureedge.net’ and I’ve simply specified the first part of our domain, ‘frogandpencil’ as the prefix.

This is where I hit a bit of a revelation with the ‘Origin Type’ drop down. You can select from Storage, Cloud service, Web app or Custom origin (which is cool!), of which I want to use Storage. After selecting this I can pick an ‘Origin hostname’. The light bulb moment here, for me, is that I should have created a storage container first! I’d watched enough videos to have dodged this little problem, but I still managed to stumble…all part of the learning process πŸ˜‰

So… Let’s Create a Storage Container

Back to the market place then. The obvious pick seems to be ‘Storage account – blob, file, table, queue’, so I’ve gone ahead and clicked create here:

Setup Azure Storage.

Setup Azure Storage.

When creating the storage account there are a fair few options to consider, a good number that read as if they will impact pricing. I had to use the documentation found here to make choices. I settled on the setup described here (for images, and as the site isn’t yet using https, I’ve gone with the secure transfer feature being disabled – one for review in the future):

As an overview, the guidance suggests the use of the ‘Resource manager’ type of ‘Deployment model’ for new applications. There doesn’t seem to be a penalty for using the ‘StorageV2’ ‘Account kind’, which extends the types that can be stored outside of just blob data, so that is what I am going for.

Performance wise, the ‘standard’ option seems like an acceptable setting at the moment and for the kind of data I’ll be storing (images for now, and possibly other static content later down the line) I can opt out of any geo-redundant replication options. In the event of resource downtime, I can easily switch to the use of resources local to the website. Plus, there will not be any data being lost really, all easily rebuilt and recoverable.

As for the ‘Access tier’, I’m heading down the ‘Hot’ route as images will be accessed quite frequently (we have the CDN to consider here so I might tinker later on down the line).

I then pick a Subscription, give the Resource Group a name and select my region of choice before continuing.

I then get a new blade on the dashboard (which took a minute to create) and, on accessing, am presented with the following:

Storage Setup.

Storage Setup.

Managing the Storage Container

The first and perhaps most obvious choice for managing and actually getting some content up into the storage container is the Azure Storage Explorer, which I’ll be downloading and using.

After a painless install process, you should see the following, where you will be asked to connect to Azure Storage:

Connect to Azure Storage.

Connect to Azure Storage.

I simply used my Azure account sign in details here. I did notice however that the Azure Portal does expose, under ‘Access Keys’ (within the storage container dashboard), keys and connection strings. I’m assuming this is for other kinds of, including programmatic, access; which I’ll give a go I think as part of this post (as a wee bonus).

I used the right-click context menu to create a new container called ‘images’ and then used the upload button to push up a test image:

Azure Storage Explorer Upload Image.

Azure Storage Explorer Upload Image.

Again, against the container I used the right-click context menu to select ‘Set Public Access Level…’, which I’ve set as follows to allow public access to the blob data but not the container:

Container Public Access Setup.

Container Public Access Setup.

I now have a blob container with a single image in it with appropriate access rights configured. The question is can I access the image in its current state? We’re looking pretty good from what I can see.

Successful Access.

Successful Access.

Adding a custom domain

Next up, I plan on adding a custom domain to the storage account. To do this, I access the ‘Custom domain’ option as shown here:

Register Custom Domain.

Register Custom Domain.

I followed option 1 as listed here and created a CNAME record to map frogandpencilstorage.blob.core.windows.net to images.frogandpencil.com (I’m happy to wait for this to propagate).

Register images.frogandpencil.com.

Register images.frogandpencil.com.

Once the CNAME record is created you simply have to place your target URL in the text box provided and hit save.

New CNAME property.

New CNAME property.

Lastly, let’s take it for a spin and see whether we can access the image in the storage container via the custom URL…and voila:

Custom Domain Active.

Custom Domain Active.

Back to the CDN bit!

We’ve come full circle! With a storage container in place I can now use that to feed a configured CDN. Consequently, I backtracked and followed the previously listed steps being sure to select my ‘Origin hostname’ to point to the newly created storage container:

CDN Profile & Endpoint Configuration.

CDN Profile & Endpoint Configuration.

On clicking create it takes a short time for the CDN to be configured.

So, what do I do now

Looking through the videos I made another discovery. This is where I want to adjust the previously created CNAME property (that I setup for the storage container) and hook this up to the CDN endpoint instead. The portal exposes custom domain mapping for a CDN much like for a storage container:

Change CNAME to map to CDN.

Change CNAME to map to CDN.

Portal CDN Custom Domain Mapping.

Portal CDN Custom Domain Mapping.

Again, I had to wait a short time for the CNAME property change to propagate but, after that, I was all set. I then spent a little time verifying that the CDN was up and running. There are quite a few optimisation options including the ability to set a custom ‘Origin path’ (such as ‘images’) but I’m leaving these be for the time being.

The Bonus Section – Programmatically Add Items to Azure Storage

As promised, this next section discusses (in a very bare bones fashion) what is required to write to an Azure storage container. I’ve created a stub Console Application to get running with and the process itself is simple (not considering errors, existence checks and threading, of course!).

You need to:

  1. Reference the WindowsAzure.Storage NuGet package.
  2. Add a reference to System.Configuration (if you want to put connection strings, folder paths and container names in configuration files and read them out).
  3. Then simply follow the code outlined below to get started.

In my test setup, the ‘SourceDirectory’ is looking at ‘C:\test-files\’ (contains just images) and the ‘TargetContainer’ is called ‘images’, as per my earlier configuration. The connection string can be obtained from the Azure Portal, under ‘Storage Account > Settings > Access Keys’.

Test Files ready for upload.

Test Files.

Storage Access Keys.

Storage Access Keys.

The App.config for the test application is structured like this, with the connection string being set to the correct value as per the information found in the Azure Portal.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
  <connectionStrings>
    <add name="FrogAndPencilStorageConnection" connectionString="[OBTAINED_FROM_THE_AZURE_PORTAL]" />
  </connectionStrings>
  <appSettings>
    <add key="SourceDirectory" value="C:\test-files\"/>
    <add key="TargetContainer" value="images"/>
  </appSettings>
</configuration>

Then, finally, the actual test code which…

  • Attempts to connect to the storage container creating a CloudStorageAccount object, based on the connection string information supplied.
  • Then uses the CloudStorageAccount object to get create a new CloudBlobContainer object (based on the container name stored in the configuration settings).
  • Finally, utilise this CloudBlobContainer, along with information about the files to process, to actually perform the upload.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;

namespace WriteToAzureStorageTestApp
{
    /// <summary>
    /// Test application for writing to Azure Storage.
    /// Basic, test code only (throwaway code).
    /// </summary>
    internal class Program
    {
        #region Main (Entry Point) Method

        /// <summary>
        /// Main entry point method for this console application.
        /// </summary>
        /// <param name="args">Optional input arguments.</param>
        private static void Main(string[] args)
        {
            DemoWritingToAzureStorage();
        }

        #endregion Main (Entry Point) Method

        #region Private Static Methods

        /// <summary>
        /// Private static demo method illustrating how to upload to Azure Storage.
        /// </summary>
        private static void DemoWritingToAzureStorage()
        {
            // First use the FrogAndPencilStorageConnection connection string (for Azure Storage) to obtain a CloudStorageAccount, if possible
            CloudStorageAccount.TryParse(ConfigurationManager.ConnectionStrings["FrogAndPencilStorageConnection"].ConnectionString, out CloudStorageAccount storageAccount);
            if (storageAccount != null)
            {
                // We have a CloudStorageAccount...proceed to grab a CloudBlobContainer and attempt to upload any files found in the 'SourceDirectory' to Azure Storage
                Console.WriteLine("Obtaining CloudBlobContainer.");

                CloudBlobContainer container = GetCloudBlobContainer(storageAccount);

                Console.WriteLine("Container resolved.");

                Console.WriteLine("Obtaining files to process.");

                List<string> filesToProcess = Directory.GetFiles(ConfigurationManager.AppSettings["SourceDirectory"]).ToList();

                UploadFilesToStorage(container, filesToProcess);
            }

            Console.WriteLine("Processing complete. Press any key to exit...");
            Console.ReadLine();
        }

        /// <summary>
        /// Private static utility method that obtains a CloudBlobContainer
        /// using the container name stored in app settings.
        /// </summary>
        /// <param name="storageAccount">The cloud storage account to retrieve a container based on.</param>
        /// <returns>A fully instantiated CloudBlobContainer, based on the TargetContainer app setting.</returns>
        private static CloudBlobContainer GetCloudBlobContainer(CloudStorageAccount storageAccount)
        {
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

            return blobClient.GetContainerReference(ConfigurationManager.AppSettings["TargetContainer"]);
        }

        /// <summary>
        /// Private static utility method that, using a CloudBlobContainer, uploads the
        /// files passed in to Azure Storage.
        /// </summary>
        /// <param name="container">A reference to the container to upload to.</param>
        /// <param name="filesToProcess">The files to upload to the container.</param>
        private static void UploadFilesToStorage(CloudBlobContainer container, List<string> filesToProcess)
        {
            // Process each file, uploading it to storage and deleting the local file reference as we go
            filesToProcess.ForEach(filePath =>
            {
                Console.WriteLine($"Processing and uploading file from path '{ filePath } (then deleting)'.");

                // Upload the file based on name (note - there is no existence check or guarantee of uniqueness - production code would need this)
                container.GetBlockBlobReference(Path.GetFileName(filePath)).UploadFromFile(filePath);

                RemoveFileFromLocalDirectory(filePath);
            });
        }

        /// <summary>
        /// Private static utility method for deleting a file.
        /// </summary>
        /// <param name="filePath">The file path (full) to delete based upon.</param>
        private static void RemoveFileFromLocalDirectory(string filePath)
        {
            // Only attempt the delete if the file exists
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
        }

        #endregion Private Static Methods
    }
}
Test Upload Application Running.

Test Upload Application Running.

Test Files Uploaded.

Test Files Uploaded.

There you have it; a rather around the houses and off the wall tour of setting up an Azure storage container and then linking this to an Azure CDN. Plenty of images still need to be brought over into the new storage container (and a few code changes to boot), but I feel like I am on a pilgrimage to a better place. I hope this proves useful nonetheless and, until the next time, happy coding!

Addendum

After a further play I realised that the C# example I’d knocked up was not setting the content type correctly on upload, as follows:

Incorrect Content Type.

Incorrect Content Type.

To this end, I adjusted the UploadFilesToStorage method to set the content type on a CloudBlockBlob before the upload is triggered, as illustrated here:

/// <summary>
/// Private static utility method that, using a CloudBlobContainer, uploads the
/// files passed in to Azure Storage.
/// </summary>
/// <param name="container">A reference to the container to upload to.</param>
/// <param name="filesToProcess">The files to upload to the container.</param>
private static void UploadFilesToStorage(CloudBlobContainer container, List<string> filesToProcess)
{
	CloudBlockBlob blockBlob;

	// Process each file, uploading it to storage and deleting the local file reference as we go
	filesToProcess.ForEach(filePath =>
	{
		Console.WriteLine($"Processing and uploading file from path '{ filePath } (then deleting)'.");

		// Upload the file based on name (note - there is no existence check or guarantee of uniqueness - production code would need this)
		blockBlob = container.GetBlockBlobReference(Path.GetFileName(filePath));

		// Correctly configure the content type before uploading
		blockBlob.Properties.ContentType = "image/jpg";

		blockBlob.UploadFromFile(filePath);

		RemoveFileFromLocalDirectory(filePath);
	});
}

You should then see items with the correct content type in the container:

Correct Content Type.

Correct Content Type.

To access images via the custom domain, essentially my CDN, I had to ‘purge’ it also at this point.

Again, happy coding.

Generic Value Type List CSV Extension

I came across a piece of code on my travels whereby a comma-separated string was split and then parsed into long values, ultimately returned to the method caller as a list of longs. A similar method was also created for operating on and converting values to integers, not particularly DRY code, but functioned fine. All good and well, I thought, but there is no reason not to encapsulate this into a method (I opted to create a string extension) that encompasses working with value types in general. Not a 100% solution, but a start on the right track.

Thirty minutes of tinkering yielded the following string extension (and supporting unit tests), which is currently constrained to value types only but could possibly be further constrained. I’ve provided some unit test declarations to give you an idea of its usage. It has a piece of cheeky boolean handling in it which is perhaps not best placed as you can quickly end up on a dark road when shoehorning type-specific code into generic methods. For now, though it seems like an acceptable solution:

Here’s the extension for starters:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace GenericExtensions
{
    public static class ObjectExtensions
    {
        /// <summary>
        /// Public static string extension can convert a comma-separated list (string)
        /// into a List of type T (where T is a struct, just to make this more constrained).
        /// </summary>
        /// <typeparam name="T">The struct type to attempt a conversion to (for each value in the comma-separated string).</typeparam>
        /// <param name="csvString">The comma-separated source string to split into values and then attempt conversions on.</param>
        /// <param name="errorList">A List of type string that catches conversion errors.</param>
        /// <returns>A list containing types of T where a conversion is possible.</returns>
        public static List<T> GetValuesFromCsvString<T>(this string csvString, out List<string> errorList) where T : struct
        {
            List<T> convertedValues = new List<T>();

            errorList = new List<string>();

            // Only proceed (and attempt conversions) where the string provided contains content
            if (!string.IsNullOrWhiteSpace(csvString))
            {
                // Trim up csv values (we don't want whitespace to intefer with the conversion)
                IEnumerable<string> trimmedCsvValues = csvString.Split(',').Select(csv => csv.Trim());

                // Attempt the conversion for each value in the comma-separated list (value to type T). Note errors if and when they occur and store
                // errors/converted values in the appropriate lists
                foreach (string csv in trimmedCsvValues)
                {
                    try
                    {
                        // Trigger manual conversion for bool types. Not the most ideal but sufficient for basic needs
                        if (typeof(T) == typeof(bool))
                        {
                            switch (csv.ToLowerInvariant())
                            {
                                case "1":
                                case "yes":
                                case "on":
                                    convertedValues.Add((T)Convert.ChangeType(true, typeof(T), CultureInfo.InvariantCulture));
                                    break;

                                case "0":
                                case "no":
                                case "off":
                                    convertedValues.Add((T)Convert.ChangeType(false, typeof(T), CultureInfo.InvariantCulture));
                                    break;

                                default:
                                    // Conversion is not possible
                                    throw new InvalidCastException();
                            }
                        }
                        else
                        {
                            // Standard conversion attempt for other structs
                            convertedValues.Add((T)Convert.ChangeType(csv, typeof(T), CultureInfo.InvariantCulture));
                        }
                    }
                    catch (Exception ex)
                    {
                        errorList.Add($"Could not convert value '{ csv }' to type '{ typeof(T).Name }'. Exception type: { ex.GetType().Name }; Exception Message: { ex.Message }");
                    }
                }
            }

            // Return successfully converted values
            return convertedValues;
        }
    }
}

Lastly, here are the unit tests to put the extension through its paces:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;

namespace GenericExtensions.Tests
{
    [TestClass]
    public class ObjectExtensionTests
    {
        [TestMethod]
        public void GetValuesFromCsvString_ConversionToBool_InRangeValuesConverted()
        {
            List<bool> booleans = "1,0, 1 ,20,test,on,off,No,YES,  3.40282347E+38   , 99.9 "
                .GetValuesFromCsvString<bool>(out List<string> errorList);

            Assert.IsTrue(booleans.Count == 7);
            Assert.IsTrue(errorList.Count == 4);

            Assert.AreEqual(true, booleans[0]);
            Assert.AreEqual(false, booleans[1]);
            Assert.AreEqual(true, booleans[2]);
            Assert.AreEqual(true, booleans[3]);
            Assert.AreEqual(false, booleans[4]);
            Assert.AreEqual(false, booleans[5]);
            Assert.AreEqual(true, booleans[6]);
        }

        [TestMethod]
        public void GetValuesFromCsvString_ConversionToLong_InRangeValuesConverted()
        {
            List<long> longs = "test,99.9,9223372036854775807 ,9223372036854775808"
                .GetValuesFromCsvString<long>(out List<string> errorList);

            Assert.IsTrue(longs.Count == 1);
            Assert.IsTrue(errorList.Count == 3);

            Assert.AreEqual(9223372036854775807, longs[0]);
        }

        [TestMethod]
        public void GetValuesFromCsvString_ConversionToFloat_InRangeValuesConverted()
        {
            List<float> floats = "1,99.998, 3.40282347E+38 ,9223372036854775808, random string "
                .GetValuesFromCsvString<float>(out List<string> errorList);

            Assert.IsTrue(floats.Count == 4);
            Assert.IsTrue(errorList.Count == 1);

            Assert.AreEqual(1f, floats[0]);
            Assert.AreEqual(99.998f, floats[1]);
            Assert.AreEqual(3.40282347E+38f, floats[2]);
            Assert.AreEqual(9.223372E+18f, floats[3]);
        }

        [TestMethod]
        public void GetValuesFromCsvString_ConversionToInt_InRangeValuesConverted()
        {
            List<int> ints = "1,2147483647, random string  , 3.40282347E+38 ,-2147483649, 22 "
                .GetValuesFromCsvString<int>(out List<string> errorList);

            Assert.IsTrue(ints.Count == 3);
            Assert.IsTrue(errorList.Count == 3);

            Assert.AreEqual(1, ints[0]);
            Assert.AreEqual(2147483647, ints[1]);
            Assert.AreEqual(22, ints[2]);
        }

        [TestMethod]
        public void GetValuesFromCsvString_ConversionToShort_InRangeValuesConverted()
        {
            List<short> shorts = "1,2147483647, random string  , 32767 ,32768,-32768,-32769,-2147483649, 22 "
                .GetValuesFromCsvString<short>(out List<string> errorList);

            Assert.IsTrue(shorts.Count == 4);
            Assert.IsTrue(errorList.Count == 5);

            Assert.AreEqual(1, shorts[0]);
            Assert.AreEqual(32767, shorts[1]);
            Assert.AreEqual(-32768, shorts[2]);
            Assert.AreEqual(22, shorts[3]);
        }

        [TestMethod]
        public void GetValuesFromCsvString_ConversionToUInt16_InRangeValuesConverted()
        {
            List<UInt16> UInt16s = "1,2147483647, random string, -1, -22,  , 65535 ,65536,-32768,-32769,-2147483649, 22 "
                .GetValuesFromCsvString<UInt16>(out List<string> errorList);

            Assert.IsTrue(UInt16s.Count == 3);
            Assert.IsTrue(errorList.Count == 9);

            Assert.AreEqual(1, UInt16s[0]);
            Assert.AreEqual(65535, UInt16s[1]);
            Assert.AreEqual(22, UInt16s[2]);
        }
    }
}

A very quick prototype piece of code for sure, which needs further testing. I’d also like to performance test this implementation and perhaps get a better idea of how Convert.ChangeType works under the hood. I hope everyone is having a super weekend and take care until the next time. πŸ™‚

Session State Behaviour & Async Headaches

I was battling a little issue today surrounding an action method no longer being called asynchronously; the issue turned out to be related to some recent session-based code being added to our code base. In short, the minute session is detected in the underlying code, the ‘default’ behaviour for session state handling throws a monkey wrench in asynchronicity, regardless of the operation being performed on session data (i.e. writing to the session or just reading from the session). This, for me, turned into a performance headache.

There is an attribute that can be placed at controller level that states ‘I’m reading from session only, please continue to allow asynchronous operations’, which when used looks like this:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class TestController : Controller
{
          ……
}

However, if you want to implement a control mechanism at the action level you need to travel down the custom controller factory/attribute route. This post turned out to be a lifesaver: Session State Behaviour Per Action in ASP.NET MVC

In short, this setup enables you to set session state behaviour handling at the action level by adorning the target method with a custom attribute; bonza!

When inspecting this and underlying, base class, implementations you will most likely discover that it’s not immediately clear how to handle scenarios where overridden methods exist (where methods match by name but differ by signature). This, for me, caused several crunches into the dreaded AmbigiousMatchException.

The implementation below shows my modified override of the DefaultControllerFactory GetControllerSessionBehavior method that is designed to a) avoid exceptions and b) only try to ‘discover’ the attribute and apply custom session state behaviour handling where a single method is ‘matched’ (based on the supplied RequestContext). If the custom attribute is not found, or more than one method is found matching by name (or another error occurs) base logic kicks in and takes precedence:

        /// <summary>
        /// Public overridden method that looks at the controller/action method being called and attempts
        /// to see if a custom ActionSessionStateAttribute (determining how session state behaviour should work) is in play.
        /// If it is, return the custom attributes SessionStateBehaviour value via the Behaviour property, in all other instances
        /// refer to the base class for obtaining a SessionStateBehavior value (via base.GetControllerSessionBehavior).
        /// </summary>
        /// <param name="requestContext">The request context object (to get information about the action called).</param>
        /// <param name="controllerType">The controller type linked to this request (used in a reflection operation to access a MethodInfo object).</param>
        /// <returns>A SessionStateBehavior enumeration value (either dictacted by us based on ActionSessionStateAttribute usage or the base implementation).</returns>
        protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
        {
            try
            {
                // At the time of writing base.GetControllerSessionBehavior just returns SessionStateBehaviour.Default but to make this robust we should just call
                // base.GetControllerSessionBehavior if the controllerType is null so any changes to the base behaviour in future are adhered to
                if (controllerType != null)
                {
                    // Defensive code to check the state of RouteData before proceeding
                    if (requestContext.RouteData != null
                        && requestContext.RouteData.Values != null
                        && requestContext.RouteData.Values["action"] != null)
                    {
                        // Attempt to find the MethodInfo type behind the action method requested. There is a limitation here (just because of what we are provided with) that
                        // this piece of custom attribute handling (for ActionSessionStateAttribute) can only be accurately determined if we find just one matching method
                        string actionName = requestContext.RouteData.Values["action"].ToString();
                        List<MethodInfo> controllerMatchingActionMethods = controllerType.GetMethods(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
                            .Where(method => method.Name.Equals(actionName, StringComparison.InvariantCultureIgnoreCase)).ToList();

                        // In order to avoid ambiguous match exceptions (plus we don't have enough information about method parameter types to pick the correct method in the case
                        // where more than one match exists) I needed to rig this in such a way that it can only work where one matching method, by name, exists (works for our current use cases) 
                        if (controllerMatchingActionMethods != null && controllerMatchingActionMethods.Count == 1)
                        {
                            MethodInfo matchingActionMethod = controllerMatchingActionMethods.FirstOrDefault();

                            if (matchingActionMethod != null)
                            {
                                // Does the action method requested use the custom ActionSessionStateAttribute. If yes, we can return the SessionStateBehaviour specified by the
                                // developer who used the attribute. Otherwise, just fail over to base logic
                                ActionSessionStateAttribute actionSessionStateAttr =
                                    matchingActionMethod.GetCustomAttributes(typeof(ActionSessionStateAttribute), false)
                                        .OfType<ActionSessionStateAttribute>()
                                            .FirstOrDefault();

                                if (actionSessionStateAttr != null)
                                {
                                    return actionSessionStateAttr.Behaviour;
                                }
                            }                       
                        }
                    }
                }
            }
            catch
            {
                // If any issues occur with our custom SessionStateBehavior inferring handling we're best to just let the base method calculate this instead (best efforts 
                // have been made to avoid exceptions where possible). Could consider logging here in future (but we're in an odd place in the MVC lifecycle, could cause
                // ourselves more issues by attempting this so will only do if absolutely required)
            }

            return base.GetControllerSessionBehavior(requestContext, controllerType);   
        }

This appeared to be a pretty robust solution in my case (and we gained back the asynchronous processing on the targetted methods = big plus), so, hopefully, this comes in handy for others at some point.

Cheers all!

Classes and instances…what gives!

My brother, who is a DevOps and integrations whizz, got around to quizzing me, after hearing chatter amongst the nearby developer folk in his building, about the wonderful world of classes and instances, as they pertain to C#.

I reeled off the best explanation I could as I sipped on the best damn gin ever (actually, voted the UK’s best, check this out) and scoffed down some superb steak and chips. I didn’t think my musings were all that bad, but I got to thinking that formalising and solidify my thoughts on the matter wouldn’t hurt. Last aside, if you’re in Norfolk and fancy a good meal this is worth hitting up:

The Boars Spooner Row

What is a steak…I mean, class!?

Food on the brain! Ok, in layman’s terms, a class simply defines a template or blueprint for anything being represented in a given computer program. This blueprint contains (but doesn’t have to and is not limited to), on a basic level, properties that describe the thing being templated and methods that represent actions or functions (that may or may not receive external stimuli, or variables) the, for want of a better term, thing can perform. The class, in and of itself, does nothing up until the point it is brought into life…meaning when an instance is created (ignoring static classes, for the purposes of this explanation).

So, what is an instance?

Instances, typically, are brought to life for actual use, in C#, using the new keyword and all we are doing here is bringing an occurrence (to try and avoid typing instance, again) of a given blueprint into being so the descriptive values of the object can be accessed and the functionality triggered.

I would normally use the tried and tested example of vehicles to show how this actually works, with a little dip into inheritance to boot, but I’m going off piste with the first thing that came into my head…different types of homes is what I’m going with.

Let’s start with a blueprint (or class) for a Home. I don’t want this to be too complicated but going too trivial may not get the key points across, so hopefully this middle ground will make sense:

/// <summary>
/// The blueprint, in our application, for a place
/// to live.
/// </summary>
public class Home
{
	#region Private Readonly Data Fields

	/// <summary>
	/// Every home is expected to have rooms. This value, as it's marked
	/// as readonly, can only be set with a value here as part of the declaration or
	/// as part of a 'constructor' (that is involved in building an instance of a home) - in this 
	/// first iteration the number of rooms in a home isn't going to change (we'll come back to this!).
	/// </summary>
	private readonly int numberOfRooms;

	#endregion Private Readonly Data Fields

	#region Private Data Fields

	/// <summary>
	/// A private variable that keeps track of whether the 
	/// door to the home is open or closed. The door to a home
	/// can only be opened/closed by triggering the OpenDoor/CloseDoor
	/// methods on an 'instance' of the type, no direct 
	/// access is allowed = encapsulation.
	/// </summary>
	private bool doorOpen = false;

	#endregion Private Data Fields

	#region Public Properties

	/// <summary>
	/// Allow an object user to get a value representing if a home's
	/// door is open or closed, without allowing them to directly 
	/// change the state of the door.
	/// </summary>
	public bool IsDoorOpen
	{
		get
		{
			return doorOpen;
		}
	}

	/// <summary>
	/// Much like with IsDoorOpen, allow an object user to get a 
	/// readout of the number of rooms in this home without any direct
	/// access to change it at this point (and the underlying variable
	/// is currently readonly anyway, disallowing changes at this time).
	/// </summary>
	public int NumberOfRooms
	{
		get
		{
			return numberOfRooms;
		}
	}

	#endregion Public Properties

	#region Constructor

	/// <summary>
	/// The 'constructor' for a Home that is used to setup object
	/// state for each and every instance of a home.
	/// </summary>
	/// <param name="roomCount">The number of rooms that are in this house (provided by the object user).</param>
	public Home(int roomCount)
	{
		numberOfRooms = roomCount;
	}

	#endregion Constructor

	#region Public Methods

	/// <summary>
	/// Public method that triggers an action on this home, i.e. opens
	/// the door of this home.
	/// </summary>
	public void OpenDoor()
	{
		// Opens the door to the house
		doorOpen = true;

		// Perhaps other things happen as a result of this...
		Console.WriteLine("The door on this home has been opened.");
	}

	/// <summary>
	/// Public method that triggers an action on this home, i.e. closes
	/// the door of this home. 
	/// </summary>
	public void CloseDoor()
	{
		// Closes the door to the house
		doorOpen = false;

		// Perhaps other things happen a result of this...
		Console.WriteLine("The door on this home has been closed.");
	}

	#endregion Public Methods
}

I’ve outlined the starting concept of what I think a ‘Home’ looks and feels like. A home has, from my very barebones view (forgetting about things like walls, ahem!):

  • A number of rooms.
  • A door.
  • A way for the door to be opened and closed.

Obviously, homes are far more complicated than this, but this will get us going. Regardless of the keywords and definitions used this is nothing more than a blueprint, an instance of an object is required to start interacting with a home, as follows:

        /// Create an instance of a home, using the blueprint provided, and open
        /// then close the door (as well as read out the number of rooms).
        /// </summary>
        private static void PlayWithAHome()
        {
            // Use the 'Home' class blueprint to create an 'instance' of a Home so we can actually start reading/triggering facets of it
            // The Home blueprint demands, in this case, that we provide the number or rooms (as part of the constructor)
            Home testHome = new Home(6);

            // Let's use our home...
            Console.WriteLine($"The home has { testHome.NumberOfRooms } rooms.");               // How many rooms does the home have
            Console.WriteLine($"The door is { (testHome.IsDoorOpen ? "open" : "closed") }.");   // Is the door open or closed (should start closed)

            // Let's open the door (we should get a console readout as part of triggering this functionality on a Home)
            testHome.OpenDoor();

            Console.WriteLine($"The door is now { (testHome.IsDoorOpen ? "open" : "closed") }.");   // Is the door open or closed (should now be open)

            // Stop the application so we can read the output
            Console.Read();
        }
Home object being used.

Home object being used.

A simple run through then; a home has a blueprint that defines it will contain a certain number of rooms, a door, a way to read out the number of rooms and whether the door is ajar (private fields and properties) and a mechanism for opening and closing the door (methods). This is the class (or type). To actually get a readout on the number of rooms and start opening and closing the door we need to build the home, end of; this is the instance.

There are a few extra comments in the Home class that discuss ‘readonly’ variables, ‘getter only’ properties (which ties in encapsulation) and the constructor; I’ll leave you to peruse them as I’ve covered the meat of classes and instances at this point.

Sideline question…how does inheritance come into this

Just before my poor Mum looked destined to snooze off at the dinner table, meaning for everyone’s sanity the subject had to be changed, we also skimmed inheritance; so I’ll give one brief example below using our ‘Home’ class from before (modified to make it simpler this time around).

Inheritance, in short, is the idea of building a ‘chain’ of related classes, by building common functionality into a ‘base’ class and then reusing/overriding this functionality in one or more sub-classes; basically, new classes can be created using an existing class as a starting point. The core concept behind classical inheritance is the ‘is a’ relationship between types; below we have a one man tent, bungalow and house; these can all be prefixed with the term ‘is a’ to establish a valid sounding relationship (a house ‘is a’ home, for example).

Firstly, although not required for inheritance, I’ve created an interface, or contract, that outlines the common functionality that any implementing class must define (and subsequently, will be tied to subclasses). This wasn’t mandatory for the example I was putting together but I’ve opted to roll with it.

namespace HomeApplication
{
    /// <summary>
    /// Public interface that defines the properties and behaviours
    /// that all homes should exhibit. The Home class will use this interface
    /// that basically states that the class will implement the described properties/methods - 
    /// This can be thought of as a contract (a promise that the facets will be found on the class).
    /// </summary>
    public interface IHome
    {
        /// <summary>
        /// Homes all have a certain number of floors, or living 'levels'.
        /// </summary>
        int NumberOfFloors { get; }

        /// <summary>
        /// Homes all have a certain number of rooms.
        /// </summary>
        int NumberOfRooms { get; }

        /// <summary>
        /// Homes all have a way to tell if the door is open or closed.
        /// </summary>
        bool IsDoorOpen { get; }

        /// <summary>
        /// Homes (for my example) are expected to have a way to open the door.
        /// </summary>
        void OpenDoor();

        /// <summary>
        /// Homes (for my example) are expected to have a way to close the door.
        /// </summary>
        void CloseDoor();

        /// <summary>
        /// Homes (for my example) are expected to have a way to turn on the heating.
        /// </summary>
        void TurnOnHeating();
    }
}

Using our IHome interface, the Home class outlines common functionality and properties to be shared by all subclasses; we are ultimately just using, as stated before, this class as a starting point to create other classes.

This class has been listed as abstract (which is not a requirement for implementing inheritance), which means that a ‘Home’ is an abstract concept only and I want to disallow users from creating an instance of this type; only instances of subclasses should be created. In as short a description as possible, virtual members provide a default implementation but can be optionally overridden by subclasses, abstract members, however, require subclasses to provide the full implementation (we are simply stating, in this case, that subclasses should implement a particular flavour of functionality). Other than that, I’ve described other pertinent details in the comments within the class definition itself.

using System;

namespace HomeApplication
{
    /// <summary>
    /// The blueprint, in our application, for a place
    /// to live. This is 'abstract', meaning no one can create
    /// a home as an instance to use directly, they can only create
    /// sub-classes of 'Home' for use in an application.
    /// </summary>
    public abstract class Home : IHome      // IHome defines a contract that 'Home' has to conform to (and therefore, that all sub-classes will be locked in to)
    {
        #region Public Properties

        /// <summary>
        /// Allow an object user to read the number of floors
        /// in this home (this value can only be set privately
        /// within this class, not from another class or sub-class directly).
        /// </summary>
        public int NumberOfFloors { get; private set; }

        /// <summary>
        /// Allow an object user to read the number of rooms
        /// in this home (this value can only be set privately
        /// within this class, not from another class or sub-class directly).
        /// </summary>
        public int NumberOfRooms { get; private set; }

        #endregion Public Properties

        #region Public Virtual Properties

        /// <summary>
        /// Allow an object user to obtain a value that represents if
        /// the door is open or closed. This is virtual as I want to allow
        /// derived types to optionally override how this is determined.
        /// </summary>
        public virtual bool IsDoorOpen { get; private set; }

        #endregion Public Virtual Properties

        #region Constructor

        /// <summary>
        /// When an 'instance' of a home is created we expect
        /// to be provided with the number of floors and rooms
        /// available within the home.
        /// </summary>
        /// <param name="floors">The default number of floors on offer.</param>
        /// <param name="rooms">The default number of rooms on offer.</param>
        public Home(int floors, int rooms)
        {
            // Store the provided values in the appropriate properties
            NumberOfFloors = floors;
            NumberOfRooms = rooms;
        }

        #endregion Constructor

        #region Protected Methods

        /// <summary>
        /// Protected members or only accessible from within this type and from direct
        /// descendant types, not from an external class. I want sub-types to possibly alter
        /// how many rooms (by adding a room) can be found in the home.
        /// </summary>
        /// <param name="numberOfRooms">The number of rooms to add.</param>
        protected void AddExtraRooms(int numberOfRooms)
        {
            NumberOfRooms += numberOfRooms;
        }

        #endregion Protected Methods

        #region Public Virtual Methods

        /// <summary>
        /// Public virtual method that closes a home's door - this represents
        /// the 'default' implementation only. This is virtual as I want derived 
        /// classes to be able to optionally override how this process 
        /// happens (see the OneManTent, for example).
        /// </summary>
        public virtual void CloseDoor()
        {
            // Closes the door to the house (enhanced to fully use auto properties)
            IsDoorOpen = false;

            // Perhaps other things happen a result of this...
            Console.WriteLine("The door on this home has been closed.");
        }


        /// <summary>
        /// Public virtual method that opens a home's door - this represents
        /// the 'default' implementation only. This is virtual as I want derived 
        /// classes to be able to optionally override how this process 
        /// happens (see the OneManTent, for example).
        /// </summary>
        public virtual void OpenDoor()
        {
            // Opens the door to the house (enhanced to fully use auto properties)
            IsDoorOpen = true;

            // Perhaps other things happen as a result of this...
            Console.WriteLine("The door on this home has been opened.");
        }

        #endregion Public Virtual Methods

        #region Public Abstract Methods

        /// <summary>
        /// Final method...this is abstract as we are enforcing a situation whereby derived
        /// types of 'Home' have to implement this themselves (every home's method of heating will
        /// vary in my test setup) - there is no default implementation.
        /// </summary>
        public abstract void TurnOnHeating();

        #endregion Public Abstract Methods
    }
}

Our other classes are utilising inheritance directly, using the Home class as a ‘template’ and using ‘overrides’ where applicable to provide their own spin on functionality, as required.

For example, all types support opening and closing of the door; however, tents override this functionality to take the ‘zip getting stuck’ into account. Houses allow for extensions to be built, which ultimately means that further rooms get added to the home. Further in line comments are there for more in-depth explanations as to what is going on.

using System;

namespace HomeApplication
{
    /// <summary>
    /// Blueprint that defines what a house looks like
    /// (this 'is a' home in my example).
    /// </summary>
    public class House : Home       // A house 'is a' home, but has some differences, which this class outlines
    {
        #region Constructor

        /// <summary>
        /// The constructor for a house consumes values that represent
        /// the number of floors and rooms that are available - these are passed
        /// directly to the Home base classes constructor.
        /// </summary>
        /// <param name="floors">The default number of floors on offer.</param>
        /// <param name="rooms">The default number of rooms on offer.</param>
        public House(int floors, int rooms) 
            : base(floors, rooms)
        {

        }

        #endregion Constructor

        #region Public Methods

        /// <summary>
        /// This method is house specific, in my example (could apply to a bungalow, of course, but
        /// I've opted to not allow this for now). A house can have an extension added by calling the protected
        /// (only accessible from the Home class or derived types, like this 'House') AddExtraRooms method. The room
        /// count for this House will therefore be increased by one.
        /// </summary>
        public void AddAnExtension()
        {
            Console.WriteLine("Adding an extension to the house (+1 rooms).");
            AddExtraRooms(1);
        }

        #endregion Public Methods

        #region Public Overridden Methods

        /// <summary>
        /// This represents what happens when the heating is turned on in a house 
        /// (remember, this was marked as abstract on the base class so this class
        /// has no choice but to offer up some kind of implementation). Super toasty
        /// central heating is on offer here!
        /// </summary>
        public override void TurnOnHeating()
        {
            Console.WriteLine("Turning on the central heating in the house.");
        }

        #endregion Public Overriden Methods
    }
}
using System;

namespace HomeApplication
{
    /// <summary>
    /// Blueprint that defines what a bungalow looks like
    /// (this 'is a' home in my example).
    /// </summary>
    public class Bungalow : Home        // A bungalow 'is a' home, but has some differences, which this class outlines
    {
        #region Constructor

        /// <summary>
        /// The constructor for a bungalow consumes a value that represent
        /// the number of rooms that are available - this is passed
        /// directly to the Home base classes constructor. Notice that we are internally
        /// setting the amount of floors to 1 (illustration only, to show how a derived type
        /// can take control of it's own state).
        /// </summary>
        /// <param name="rooms">The default number of rooms on offer.</param>
        public Bungalow(int rooms) 
            : base(1, rooms)            // Bungalows - we only allow a single floor in our example
        {

        }

        #endregion Constructor

        #region Public Overridden Methods

        /// <summary>
        /// This represents what happens when the heating is turned on in a bungalow 
        /// (remember, this was marked as abstract on the base class so this class
        /// has no choice but to offer up some kind of implementation). A Coal fire
        /// have been selected as the weapon of choice in this case.
        /// </summary>
        public override void TurnOnHeating()
        {
            Console.WriteLine("Lighting up the coal fire in the bungalow.");
        }

        #endregion Public Overriden Methods
    }
}
using System;

namespace HomeApplication
{
    /// <summary>
    /// Blueprint that defines what a one man tent looks like
    /// (this 'is a' home in my example).
    /// </summary>
    public class OneManTent : Home      // A one man tent 'is a' home, but has some differences, which this class outlines
    {
        #region Public Properties

        /// <summary>
        /// The door for a tent has an added element to worry about...the bloody zip!
        /// If the zip is broken the door (in my example) is classed as stuck open, might not
        /// be true to reality but serves as illustrative only.
        /// </summary>
        public bool IsZipBroken { get; set; }

        #endregion Public Properties

        #region Public Overridden Properties

        /// <summary>
        /// Overriden functionality from the 'Home' base class. If the zip is broken
        /// the door is classed as open. If the zip isn't broken we simply read if the door
        /// is open or closed from the base class.
        /// </summary>
        public override bool IsDoorOpen
        {
            get
            {
                return IsZipBroken ? true : base.IsDoorOpen;
            }
        }

        #endregion Public Overridden Properties

        #region Constructor

        /// <summary>
        /// The constructor for a one man tent consumes a value that represent
        /// the number of rooms that are available - this is passed
        /// directly to the Home base classes constructor. Notice that we are internally
        /// setting the amount of floors to 1 (illustration only, to show how a derived type
        /// can take control of it's own state).
        /// </summary>
        /// <param name="rooms">The default number of rooms on offer.</param>
        public OneManTent(int rooms) 
            : base(1, rooms)                // Tents - we only allow a single floor in our example
        {

        }

        #endregion Constructor

        #region Public Overridden Methods

        /// <summary>
        /// A tent overrides how a the door is opened. If the zip is broken the tent
        /// door is stuck open. Otherwise, the door opens as normal (via functionality
        /// found on the 'base' class).
        /// </summary>
        public override void OpenDoor()
        {
            if (!IsZipBroken)
            {
                // Zip is not stuck, open the door as normal
                base.OpenDoor();
            }
            else
            {
                // The zip is stuck!!!
                Console.WriteLine("The zip is broken so the tent door is stuck open");
            }
        }

        /// <summary>
        /// A tent overrides how a the door is closed. If the zip is broken the tent
        /// door is stuck open. Otherwise, the door opens as normal (via functionality
        /// found on the 'base' class).
        /// </summary>
        public override void CloseDoor()
        {
            if (!IsZipBroken)
            {
                // Zip is not stuck, close the door as normal
                base.CloseDoor();
            }
            else
            {
                // The zip is stuck!!!
                Console.WriteLine("The zip is broken so the tent door is stuck open");
            }
        }

        /// <summary>
        /// This represents what happens when the heating is turned on in a one man
        /// tent (remember, this was marked as abstract on the base class so this class
        /// has no choice but to offer up some kind of implementation). Hot water bottles
        /// are the only choice here!
        /// </summary>
        public override void TurnOnHeating()
        {
            Console.WriteLine("Urm...using the hotwater bottle for extra heat!");
        }

        #endregion Public Overriden Methods
    }
}
/// <summary>
/// Further fun and games with homes!
/// </summary>
private static void PlayWithHomes()
{
	// A House, Bungalow and OneManTent are 'Homes', therefore share some of the blueprint information (as they are derived classes). Let's use them, and explore the differences

	// Configure instances, with floor and room numbers, as available to us
	House myHouse = new House(2, 8);
	Bungalow myBungalow = new Bungalow(7);
	OneManTent myTent = new OneManTent(2);

	// 1) The House...
	Console.WriteLine("Details about myHouse..." + Environment.NewLine);
	Console.WriteLine($"The house has { myHouse.NumberOfRooms } rooms.");
	Console.WriteLine($"The house has { myHouse.NumberOfFloors } floors.");
	Console.WriteLine($"The house door is { (myHouse.IsDoorOpen ? "open" : "closed") }.");

	// Open the door and check the door state
	myHouse.OpenDoor();
	Console.WriteLine($"The house door is { (myHouse.IsDoorOpen ? "open" : "closed") }.");

	// Turn on the heating in the house
	myHouse.TurnOnHeating();

	// Add an extension (house specific)
	myHouse.AddAnExtension();
	Console.WriteLine($"The house has { myHouse.NumberOfRooms } rooms (after adding an extension)." + Environment.NewLine);

	// ---------------------------------------------------------------------------------------------------

	// 2) The Bungalow...
	Console.WriteLine("Details about myBungalow..." + Environment.NewLine);
	Console.WriteLine($"The bungalow has { myBungalow.NumberOfRooms } rooms.");
	Console.WriteLine($"The bungalow has { myBungalow.NumberOfFloors } floor.");
	Console.WriteLine($"The bungalow door is { (myBungalow.IsDoorOpen ? "open" : "closed") }.");

	// Open the door and check the door state
	myBungalow.OpenDoor();
	Console.WriteLine($"The bungalow door is { (myBungalow.IsDoorOpen ? "open" : "closed") }.");

	// And close it this time, for good measure
	myBungalow.CloseDoor();
	Console.WriteLine($"The bungalow door is { (myBungalow.IsDoorOpen ? "open" : "closed") }.");

	// Turn on the heating in the bungalow
	myBungalow.TurnOnHeating();

	Console.WriteLine();

	// ---------------------------------------------------------------------------------------------------

	// 3) The One Man Tent...
	Console.WriteLine("Details about myTent..." + Environment.NewLine);
	Console.WriteLine($"The tent has { myTent.NumberOfRooms } rooms.");
	Console.WriteLine($"The tent has { myTent.NumberOfFloors } floor.");
	Console.WriteLine($"The tent door is { (myTent.IsDoorOpen ? "open" : "closed") }.");

	// Let's break the zip!
	myTent.IsZipBroken = true;

	// Open the door and check the door state (it should be stuck open)
	myTent.OpenDoor();
	Console.WriteLine($"The tent door is { (myTent.IsDoorOpen ? "open" : "closed") }.");

	// And close it this time, for good measure
	myTent.CloseDoor();
	Console.WriteLine($"The tent door is { (myTent.IsDoorOpen ? "open" : "closed") }.");

	// Fix the zip and try to re-open and close the door
	myTent.IsZipBroken = false;

	myTent.OpenDoor();
	Console.WriteLine($"The tent door is { (myTent.IsDoorOpen ? "open" : "closed") }.");

	myTent.CloseDoor();
	Console.WriteLine($"The tent door is { (myTent.IsDoorOpen ? "open" : "closed") }.");

	// Turn on the heating in the tent
	myTent.TurnOnHeating();

	// Stop the application so we can read the output
	Console.Read();
}

Finally, the following diagram shows that tents, bungalows and houses ‘are’ homes; they share the common facets of a home whilst providing their own functionality and overridden logic, that’s essentially it!

Home class diagram.

Home class diagram.

Home instances output.

Home instances output.

I’ll do a more in depth OOP principle post in the future so watch this space.

Happy Easter!!!

LINQ Joins – Multiple Match Conditions

Hi all,

Just a very quick post on an interesting piece of LINQ knowledge demonstrating how to find common matches between two C# lists, whereby you want to include multiple matching conditions.

To start, here is a typical LINQ statement (and setup) dealing with a single join condition, using the extension method approach (which I prefer), joining two lists; one defining humans and one defining aliens (just because!):

// Setup
public interface ILivingBeing
{
    string Name { get; set; }
    int Age { get; set; }
    void WriteInfo();
}

...

public abstract class LivingBeing : ILivingBeing
{
    public int Age { get; set; }
    public string Name { get; set; }
    public abstract void WriteInfo();
}

...

public class Human : LivingBeing
{
    public override void WriteInfo()
    {
        Console.WriteLine("I am a human who is {0} years old and my name is {1}", Age, Name);
    }
}

...

public class Alien : LivingBeing
{
    public override void WriteInfo()
    {
        Console.WriteLine("I am an alien who is {0} years old and my name is {1}", Age, Name);
    }
}

...

// And finally, the List setup and LINQ itself:
List<Human> humans = new List<Human>
{
    new Human { Name = "Steve", Age = 31 },
    new Human { Name = "Dave", Age = 34 },
    new Human { Name = "Alexa", Age = 56 }
};

List<Alien> aliens = new List<Alien>
{
    new Alien { Name = "Steve", Age = 31 },
    new Alien { Name = "Dave", Age = 76 },
    new Alien { Name = "Poppy", Age = 56 }
};

var matches = humans.Join(aliens, human => human.Name, alien => alien.Name, (human, alien) => human);

Here is an illustration showing the first set of results being used:

LINQ Single Join Condition.

LINQ Single Join Condition.

In the case above the results returned by ‘matches’ include both ‘Steve’ and ‘Dave’ (humans), as a match on the ‘Name’ property can be established across both lists. Everything is fine up until the point we want matches based on, say, the Name and Age properties (on the assumption we want to stick with the extension method approach). This is surprisingly easy to achieve, using anonymous types in our LINQ statement join condition, as follows:

// Return just a list containing the human 'Steve' (as he is the only one who directly matches an alien based on 'Name' and 'Age'
var matches = humans.Join(aliens, human => new { human.Name, human.Age }, alien => new { alien.Name, alien.Age }, (human, alien) => human);

The inner and outer key selectors simply use anonymous types, constructed using the new keyword, to bring the related properties into scope that we wish to match on. Then, hey presto, you can easily handle multiple matching conditions. In this instance, only the human ‘Steve’ is returned, based on a direct match with an alien of the same name/age:

LINQ Multiple Join Condition.

LINQ Multiple Join Condition.

I hope this comes in handy at some point. I’ve been kept fairly busy on the work front recently, hence my blog and twitter feed haven’t exactly been a hive of activity…I’ll work to change that as best I can in the coming weeks and months and carve out more time to publish content.

Cheers, until the next time!

Gordon Ramsay Home-made Fish Fingers with a Chip Butty Application…Just because

There was no need for this post to exist or for these four hundred and thirty-eight (with brackets and other nonsense) random lines of C# code to even see the light of day. For some unknown reason, I woke up over the weekend and spent an hour watching Gordon Ramsay YouTube videos when I came across this amazing looking take on fish fingers with a chip butty:

The video somehow leads me to write the following code. I wasn’t trying to write anything particularly good (i.e. a gaming engine or anything reusable, per say), it’s just an hour or two of me sitting on my tod mindlessly coding to no exacting standards (i.e. it’s all in one file, doesn’t conform to my usual commenting/ordering standards, is horrendously formed and pretty damn awful). Essentially, just enjoy the ‘random factor’ of all of this; you’ll notice I throw in a tonne of exceptions because, in my head, Gordon Ramsay wouldn’t use simple boolean logic to denote if your choice was correct or not, he would throw massive exceptions all over the place (pretty sure this is how he would write his code).

Here it is, very much un-sanitised and untested:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Media;

namespace GordonRamseyCookingSimulator
{
    class Program
    {
        private static List<string> gordonSoundBites = new List<string>()
        {
            "gordon-clip-one.wav",
            "gordon-clip-two.wav",
            "gordon-clip-three.wav"
        };

        private const string WELL_DONE_SOUND_BITE = "well-done.wav";

        private static SoundPlayer player = new SoundPlayer();

        private const string GOOD_JOB_TEXT = "Good job, moving on...";
        private const string GORDON_DEMANDS_TEXT = "Gordon demands that you choose a {0}";

        private static Dictionary<string, string> complexOptions = new Dictionary<string, string>();

        static void Main(string[] args)
        {
            Console.WindowHeight = 60;
            Console.WindowWidth = 185;
            Console.ForegroundColor = ConsoleColor.Green;
            bool playFailSound = false, continuePlaying = true;

            WriteToConsole($"Let's make Home-made Fish Fingers and a Chip Butty with Gordon Ramsay{ Environment.NewLine }================================================================================");

            do
            {
                try
                {
                    MakeFishFingersWithAChipButty();
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;

                    Console.WriteLine();
                    playFailSound = true;
                    Console.WriteLine("Gordon Ramsay looks upon you with shame. You have failed because: ({0}) - {1}", ex.GetType().Name, ex.Message);
                }

                if (playFailSound)
                {
                    player.SoundLocation = gordonSoundBites[new Random().Next(0, 3)];
                    player.Play();

                    playFailSound = false;
                }

                WriteToConsole("Play again (Y/N followed by enter)");

                continuePlaying = RetrieveTerifiedCooksChoice().Equals("y", StringComparison.InvariantCultureIgnoreCase);

            } while (continuePlaying);
        }

        private static void MakeFishFingersWithAChipButty()
        {
            Console.WriteLine();

            // Cook with the big man
            NavigateThroughThePotatoSelectionForTheButtyTest();
            NavigateThroughThePeelingInstrumentTest();                  // This one should be easy ahem (peeling...peeler!)
            NavigateThroughTheChipPreparationTest();
            NavigateThroughTheChipCookingChoiceTest();
            NavigateThroughTheFishSelectionTest();
            NavigateThroughTheFishPreparationTest();
            NavigateThroughTheHerbSelectionTest();
            NavigateThroughTheCookingInstrumentForFishFingersTest();
            NavigateThroughTheFishFinalCookingStepTest();
            NavigateThroughTheBreadSelectionTest();

            // Freebie from Gordon - He takes pity, and dishes out an awesome nugget of knowledge
            Console.ForegroundColor = ConsoleColor.DarkMagenta;
            WriteToConsole("HEY IDIOT! This one is for free; remember to rub the bread in the chip oil on the tray (do a 'mop' up) and get all of those awesome flavours!");
            Console.ForegroundColor = ConsoleColor.Green;

            // The FINAL TEST!
            NavigateThroughTheFinalServingTest();

            // SUCCESS (woop woop)!!!
            GodJob();

            Console.ForegroundColor = ConsoleColor.DarkMagenta;
            WriteToConsole("You have survived the Home-made Fish Fingers and a Chip Butty challenge! Well done! Press enter to exit...");
            Console.ForegroundColor = ConsoleColor.Green;
        }

        private static void NavigateThroughTheFinalServingTest()
        {
            // Final choice, how does this thing get served?
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "sauce to serve this with and decide if you should share this with others or not:"));
            complexOptions = CreateDictionaryWithOptions("Serve with ketchup and devour everything yourself.",
                "Serve with brown sauce and share (there is enough for two).", "Serve with ketchup and hand the whole dish over to a loved one, you've already eaten.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "A");
        }

        private static void NavigateThroughTheBreadSelectionTest()
        {
            // What type of bread should you use!!!
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "bread type for the chip butty (buttered, of course!):"));
            WriteEnumValuesToConsole<BreadType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<BreadType>(RetrieveTerifiedCooksChoice(), "thickcrustybread");

            // SUCCESS (so far anyway, you're nearly there)!!!
            GodJob();
        }

        private static void NavigateThroughTheFishFinalCookingStepTest()
        {
            // How long should they be cooked and what do you add near the end (when the heat is turned up)?
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "time for how long the fish fingers should be cooked on each side and what secret ingredient should be added near the end of this process:"));
            complexOptions = CreateDictionaryWithOptions("Cook for 3 minutes on each side adding some butter near the end.",
                "Cook for 2 minutes on each side adding some margarine near the end.", "cook for 5 minutes on each side adding some butter near the end.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "A");

            // SUCCESS (so far anyway, getting there)!!!
            GodJob();
        }

        private static void NavigateThroughTheCookingInstrumentForFishFingersTest()
        {
            // What cooking instrument should be used to cook said fish fingers
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "cooking instrument with which to cook your fish:"));
            WriteEnumValuesToConsole<ChefToolsForTheJob>();
            GordonRamsayProcessesYourToolChoiceForFoolishMistakes(RetrieveTerifiedCooksChoice(), ChefToolsForTheJob.Pan);

            // SUCCESS (so far anyway, getting there)!!!
            GodJob();
        }

        private static void NavigateThroughTheHerbSelectionTest()
        {
            // What herb should be used with the fish???
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "herb to chop and use with your breadcrumb mixture for the fish:"));
            WriteEnumValuesToConsole<HerbType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<HerbType>(RetrieveTerifiedCooksChoice(), "dill");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheFishPreparationTest()
        {
            // Ok, how should the fish be prepared...
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "method for preparing the fish:"));
            complexOptions = CreateDictionaryWithOptions("Cut into squares, pepper and cover in a little chill powder before coating in seasoned flour, then use egg-wash.",
                "Cut into battons, lightly salt before coating in seasoned flour and rolling, then use egg-wash.", "Cut at an angle, lightly salt and don't flour (just use egg-wash).");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "B");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheFishSelectionTest()
        {
            // Choose the correct fish type
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "fish type for your fish fingers:"));
            WriteEnumValuesToConsole<FishType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<FishType>(RetrieveTerifiedCooksChoice(), "pollock");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheChipCookingChoiceTest()
        {
            // Ok, what do we 'bang' the chips into in order to cook them
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "method for cooking the chips:"));
            complexOptions = CreateDictionaryWithOptions("Chuck the chips in a roasting hot pan with oil and cook for 10 minutes.",
                "Chuck the chips in the deep fat fryer until done.", "Chuck the chips on an oiled baking tray and cook in a pre-heated oven.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "C");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheChipPreparationTest()
        {
            // How do you choose to make the chips???
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "method for making preparing the chips for cooking:"));
            complexOptions = CreateDictionaryWithOptions("Chop, add oil, don't season and add chilli. Then shake.",
                "Chop, blanch, season and add paprika. Then shake.", "Chop, add oil, don't season and add pesto. Then shake.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "B");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughThePeelingInstrumentTest()
        {
            // Choose the correct peeling method, or you'll become a cropper
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "peeling instrument for your potatoes:"));
            WriteEnumValuesToConsole<ChefToolsForTheJob>();
            GordonRamsayProcessesYourToolChoiceForFoolishMistakes(RetrieveTerifiedCooksChoice(), ChefToolsForTheJob.Peeler);

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughThePotatoSelectionForTheButtyTest()
        {
            player.SoundLocation = WELL_DONE_SOUND_BITE;

            // Choose the correct potato type for the chips
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "potato type for your chips:"));
            WriteEnumValuesToConsole<PotatoType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<PotatoType>(RetrieveTerifiedCooksChoice(), "desiree");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static Dictionary<string, string> CreateDictionaryWithOptions(params string[] items)
        {
            Dictionary<string, string> complexItems = new Dictionary<string, string>();

            if (items == null || items.Count() == 0)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            List<string> keys = new List<string>() { "A", "B", "C" };

            if (items.Count() > keys.Count())
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            for (int i = 0; i < items.Count(); i++)
            {
                complexItems.Add(keys[i], items[i]);
            }

            return complexItems;
        }

        private static void GodJob()
        {
            player.Play();
            WriteToConsole(Environment.NewLine + GOOD_JOB_TEXT);
        }

        private static void WriteDictionaryValuesToConsole(Dictionary<string, string> complexOptions)
        {
            if (complexOptions == null || complexOptions.Count == 0)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            complexOptions.ToList().ForEach(item =>
            {
                WriteToConsole($"{ item.Value }: type = { item.Key }", false);
            });

            Console.WriteLine();
        }

        private static void GordonRamsayInspectsYourComplexChoice(string choiceString, string gordonsDesiredOutcome)
        {
            if (complexOptions == null || complexOptions.Count == 0)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            if (!choiceString.Trim().Equals(gordonsDesiredOutcome, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new GordonRamsayPoorChefSkillException("GOD DAMN IT, WHY DID YOU DO IT LIKE THAT. USELESS, SIMPLY USELESS - EPIC FAIL!");
            }
        }

        private static void GordonRamsayProcessesYourToolChoiceForFoolishMistakes(string choiceString, ChefToolsForTheJob gordonsDesiredOutcome)
        {
            if ((ChefToolsForTheJob)CheckForAStupidChoiceInGeneral(choiceString) != gordonsDesiredOutcome)
            {
                throw new GordonRamsayPoorChefSkillException("GOD DAMN IT, WHY DID YOU CHOOSE THAT BLOOMING (imagine swearing here) TOOL FOR THE JOB - EPIC FAIL!");
            }
        }

        private static void GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<T>(string choiceString, string gordonsDesiredOutcome) where T : struct, IConvertible
        {
            if (!typeof(T).IsEnum)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            string choiceFromEnum = Enum.GetName(typeof(T), CheckForAStupidChoiceInGeneral(choiceString));
            if (string.IsNullOrWhiteSpace(choiceFromEnum) || !choiceFromEnum.Equals(gordonsDesiredOutcome, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new GordonRamsayWrongIngredientException("WRONG, WRONG, WRONG INGREDIENT!!! - EPIC FAIL");
            }
        }

        private static int CheckForAStupidChoiceInGeneral(string choiceString)
        {
            int choiceValue;
            if (!int.TryParse(choiceString, out choiceValue))
            {
                throw new GordonRamsayInvalidChoiceException("YOU DIDN'T GIVE ME A BLOOMING (imagine swearing here) NUMBER - EPIC FAIL!");
            }

            return choiceValue;
        }

        private static string RetrieveTerifiedCooksChoice()
        {
            Console.ForegroundColor = ConsoleColor.Blue;

            Console.Write("MAKE YOUR CHOICE: ");
            string choice = Console.ReadLine();
    
            Console.ForegroundColor = ConsoleColor.Green;

            return choice;
        }

        private static void WriteToConsole(string text, bool addAdditionalNewLine = true)
        {
            Console.WriteLine(text + (addAdditionalNewLine ? Environment.NewLine : string.Empty));
        }

        private static void WriteEnumValuesToConsole<T>() where T : struct, IConvertible 
        {
            Type typePassedIn = typeof(T);

            if (typePassedIn.IsEnum)
            {
                Enum.GetValues(typePassedIn).Cast<T>().ToList().ForEach(item =>
                {
                    Enum enumValue = Enum.Parse(typeof(T), item.ToString()) as Enum;
                    WriteToConsole($"{ item }: type = { Convert.ToUInt32(enumValue) }", false);
                });
            }

            Console.WriteLine();
        }
    }

    public enum BreadType
    {
        MediumSlicedGranary = 0,
        HovisHalfAndHalf = 1,
        Baguette = 2,
        ThickCrustyBread = 3
    }

    public enum HerbType
    {
        Basil = 0,
        Chives = 1,
        Dill = 3,
        Sage = 4,
        Tarragon = 5
    }

    public enum FishType
    {
        Haddock = 0,
        Pollock = 1,
        Tuna = 2,
        Salmon = 3
    }

    public enum PotatoType
    {
        Desiree = 0,
        KingEdward = 1,
        Laura = 2,
        MelodyPotato = 3
    }

    public enum ChefToolsForTheJob
    {
        RustySpoon = 0,
        Peeler = 1,
        Screwdriver = 2,
        Pan = 3,
        Griddle = 4
    }

    public class GordonRamsayInvalidChoiceException : Exception
    {
        public GordonRamsayInvalidChoiceException(string message)
            : base(message)
        {

        }
    }

    public class GordonRamsayWrongIngredientException : Exception
    {
        public GordonRamsayWrongIngredientException(string message)
            : base(message)
        {

        }
    }
    
    public class GordonRamsayPoorChefSkillException : Exception
    {
        public GordonRamsayPoorChefSkillException(string message)
            : base(message)
        {

        }
    }

    public class GordonRamsayEnragedAtTheDeveloperException : Exception
    {
        public GordonRamsayEnragedAtTheDeveloperException()
            : base("OMG, YOU ARE SO INCOMPETENT. THIS SOFTWARE DEVELOPER DOESN'T KNOW HOW HIS OWN CODE SHOULD WORK. SHOCKING!")
        {

        }
    }
}

It takes the form of a beautiful console application; here it is in full swing:

Gordon Ramsay Cooking Challenge.

Gordon Ramsay Cooking Challenge.

For extra kicks, I forced my wife to play it to…adding annoying sound bites (for success and failure, in each case, to cover all of my bases):

Claire Epic Failing.

Claire Epic Failing.

Claire Nailing It.

Claire Nailing It.

It felt pretty darn good to just rattle out a piece of random, not that well thought out code. I think I just needed to write ‘something’, so this is what we have ended up with for better or for worse. I urge you to write something awful (just for yourself, not for your day job, unless you quite like the idea of being tossed out onto the street that is!) and get liberated, just for once! There is quite a bit of pressure sometimes to conform to standards, practice patterns, etc. This is all good and well, but just remember that you can always just hammer out something ill-thought for yourself and enjoy doing so; sometimes it can be just the ticket! πŸ˜‰

I seem to be, as it was pointed out to me today, late to the Gordon Ramsay game-based party anyway:

Gordon Ramsay Dash

There will be more golden, crispy, fish finger like coding posts coming soon; in the meantime have a great morning/afternoon (or evening/night) depending on when you read this, bye for now!