Issues

SOLID CMS: SOLID Principles in CMS Development

SOLID is an acronym used to describe five best practice principles for software development, with the aim to make software designs more flexible, simpler to understand, and easier to maintain. The principles primarily apply to object-oriented designs (OO). If you're a developer, you've probably been asked about them at interview at some point, and the room may have gone ominously quiet as you tried to recall and describe each one. Or maybe you explained to the interviewer what SOLID was better than they could.

For this article I will be visiting each of the five key SOLID guidelines, and observing how we might apply them to Content Management System (CMS) development. I'll cover a few real, Umbraco-flavoured code samples and zoom in on a couple of design patterns for developing Umbraco applications that comply with SOLID. I'll focus a lot on Umbraco, but these concepts can be applied to other CMS systems too. By the end of the article, ideally you'll have a clearer understanding of the SOLID principles and some food for thought of how to use them with a CMS such as Umbraco, naturally increasing code maintainability and clarity.

What are the SOLID Principles?

The SOLID acronym was originally coined by Michael Feathers, and includes five core subsets of principles promoted by Robert C. Martin:

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

Single responsibility principle

The Single Reponsibility Principle (SRP), whether applied to either a C# class or an Umbraco content node, is literally that:

An object should have only a single responsibility

This means that only changes to one part of the software's specification should affect the object. There should only be one reason to change it. Uno. Un. 00000001. If there are multiple reasons to change it, then it likely isn't following SRP.

How does this apply to CMS development?

In Umbraco, we have great power to build tidy, useful and well-organised data repositories, organised in neat content tree hierarchies. We can use them for websites, for apps, for a headless CMS - however we desire. There are hundreds of ways that users may access this data, and as such developers want, nay, need to make these applications maintainable when they are handed over, or returned to in a month to add hot new features.

We'd not ever want to switch away from Umbraco, right? Yet however much we <3 it, we should never forget our aim is always to abstract away any dependencies between our application and the CMS. We certainly want to use the data inside the CMS, but in a decoupled fashion. By writing SOLID code, we can start to reap the rewards of our code being easy to extend, less stressful to work on in future, painless to share with future developers or auditors... all with minimal effort.

I expect you want your applications to be as simple as possible for the content editor. You definitely don't want content editors to have to question the purpose of each section of the site they find themselves in, fearing that changing something in one place might have the unexpected effect of breaking something unknown buried deep within the system. I can confidently bet that you don't want to find that, by editing a content node, users accidentally cause a site-wide unhandled runtime exception and a dreaded Yellow Screen of Death (YSOD). The content audit trail may point to the user, but it wasn't their fault that the exception wasn't handled gracefully!

Yellow Screen of Death (YSOD)

Yellow Screen of Death (YSOD)

So what practices can we follow to make this scenario more likely to be the stuff of nightmares, than that of a lonely Friday evening Hotfix and chill?

Examples of Single Responsiblity in a CMS

Single Responsibility in a CMS might initially seem contradictory. Whilst we want to follow the principle, we also want to avoid duplication of editor effort. Removing the site's logo media item could certainly affect the published site in several places - the header, the footer, a content article with a watermark on it. This is because when we structure a CMS, we don't want content editors to have to upload the site logo multiple times in each instance they require it. Logically then, we decide to create a single media picker property (image only) called Site Logo on the content root node. We also add helpful text informing users that the property is used globally throughout the website, so that they know what happens if they change it:

Creating a specific property, in however many places it is referenced, follows SRP. The Site Logo property has a single responsibility to provide the image for the site's brand logo, and removing or changing this object will affect the site logo alone. A well-organised content tree is composed as part of a number of units, each with a dedicated and single responsibility for their own domain - be it a logo, social media link or the data repository for a website's events.

Another example of single responsibility in a CMS is to think in terms of separation of content concerns. If you have been following the "Global Manifesto" when organising your CMS site's content and data (hey Jonathan!!) you'll know to organise it into Content and Global sections. I talk about this in more detail in my 24 Days In Umbraco article "Feeding a Companion App with Umbraco", but essentially it looks a bit like this:

In this content tree:

  • The content nodes are split into two groups - "Site" and "Global"
  • The Site content node, and it's children, contain one-to-many instances of pages, created from a choice of templates. These nodes can be considered presentation content
  • The shared data used throughout the site is stored under a separate Global node

In this way, the content tree lends itself to following SRP - site nodes are used for presentation, whereas global nodes are utilised as data repositories.

Open/closed principle

Open to extension 
Closed to change 
And I'll be healthy, wealthy and strange 
         - T. Anon

Or something like that. More formally, the open/closed principle is derived from the classic Bertrand Meyer 1988 book "Object-Oriented Software Construction", in which Meyer writes that:

"software entities (classes, modules, functions, etc) should be open for extension, but closed for modification"

Essentially, an entity can allow its behaviour to be extended or added to, without modifying its source code. Who wants to be forced to make changes to the logic in a class every time you want to add some new functionality? Not me... I would rather extend the application's functionality.

You can achieve open/closed in several ways, but essentially you're usually adding something around, or instead of, the base class so that you don't have to modify it directly:

Interfaces

You'll have probably used, or been encouraged to use, interfaces at some point throughout your development career. When I first started developing in C# and using interfaces, I knew that I definitely should use them, and what happened to the classes that implemented them, but I didn't truly understand why. Now I understand that they are essentially contracts. Two separate classes that implement the same interface have something in common - they both have to respect a contract, or template, as defined in the interface.

I'll discuss this in more detail later, but using Dependency Injection (DI), we can bind the interface to a concreteimplementation. You can then in future replace the existing object with a new one, with the functionality you need. AutoFac is a great example of a DI Container, and can save a heck of a lot of code.

Composite Design Pattern

The Gang of Four Design Patterns cite the Composite Pattern as a structural pattern. It describes multiple, independent objects composed into a single unit of work. Use it when you want to treat multiple things the same way regardless of their type - i.e. treat composite objects uniformly. In Umbraco, the fact that we treat all content nodes the same could be seen as using the Composite Pattern - every node has the same underlying methods on it - create, publish, rollback etc. This avoids having to write duplicate code for all the different potential types of content in the system. Remember, composition > class inheritance.

Decorator Design Pattern

The Decorator Pattern adds functionality around the base component, allowing behaviour to be added to it without affecting the behavior of other objects. This means it follows SRP by allowing functionality to be split up between classes with unique areas of concern. You essentially forward all requests to the decorated component and can extend the functionality dynamically at run-time if required. These decorators can be stacked, adding new functionality each time. This could be used to add new behaviour to CMS functions without affecting the existing source code.

Class Extensions

Class Extensions add methods to an existing class by referencing it in a separate static class. The usual examples are extension methods on System.String and LINQ operators. Class extensions are also included in Umbraco core code via the PublishedContentExtensions static class, which provides extension methods for IPublishedContent. However, static classes and methods are often considered an anti-pattern of good object-oriented (OO) design, so this shouldn't be the first option you look at for following SRP.

A likely story...

Here is a quite possible real world scenario. Taking an events site that is in production and was made a year ago, we have the concept of an Event object used extensively in our existing code. As I discussed previously, one of Umbraco’s key strengths is the ability to carefully define and structure the data that you require by crafting document types. In this case, the Event document type in Umbraco has been generated using Umbraco’s built-in Models Builder tool into a strongly-typed partial class, called Event.generated.cs. These generated classes inherit Umbraco's PublishedContentModel class, which as the Umbraco.Core code comments tell you:

...represents strongly-typed published content...every strongly-typed published content class should inherit from PublishedContentModel (or inherit > from a class that inherits from... etc.) so they are picked by the factory

I've created a small sample site on GitHub using the Umbraco starter-kit. I started by adding a new Event doctype:

I installed Models Builder via nuget and set it up to be generated from a separate data project via the API. Using the auto-generated Event model class, we can now render an Event via a template as follows:

@inherits UmbracoTemplatePage<eg.skrift.data.Event>
<div id="event">
    <div class="container-fluid" id="event">
        @if (Model != null)
        {
            <div class="rating">
                <h1>@Model.Content.EventTitle</h1>
                <h2>@Model.Content.EventSummary</h2>
            </div>
        }
    </div>
</div>

Ok, so we're back chatting with the client about the backlog after a wildly successful go live (and an even wilder launch party). Say that the site is now into Phase Two, and after discussing the backlog and future requirements with the client, we now need to add more information to an Event.

The user story is:

As a logged in user of the event app,
I want to be able to rate an event that I have attended from 0 to 5 stars,
So that I can share my experience of that event with other users of the event app

As this user story captures, we need to enable an overall Event rating property to show the overall rating of the event as an integer representing stars from 0 (bit harsh!) to 5 (ahem Codegarden 2018). This rating will be added for each Event from an external source, and shown on the Event page. We know we need a Rating property, and that we will need to add functionality to enable users to rate an Event.

Following the open-closed principle, because this Event class is already referenced by existing code, we should not directly change it. This might feel frustrating.

How are we supposed to add a new feature, then?

Hmm, to add a new feature to the CMS as a property against a doctype without modifying the existing code...

We could decide to create a new custom partial class called Event with the Rating property on it, and as the class has the same name as the Models Builder generated Event.generated.cs partial class, all references to the Event class will include this Rating property. This is not exactly editing the existing class directly... but it still feels like we are, because now all references to the Event class have this new property.

Instead we decide to create a new class, EventDetail.cs, and to use the original Event class as a base class, inheriting all properties from it. This means that we will not break the existing implementation of the Event class. We have not altered the original Event class itself, so it shouldn't disturb anything already using it. We now have an object that we can use in our template to render the star rating without changing the existing Event document type:

using Umbraco.Core.Models;

namespace eg.skrift.data
{
    public class EventDetail : Event
    {
        public EventDetail(IPublishedContent content) : base(content)
        {
        }

        public int EventRating { get; set; }
    }
}

Now create a controller called EventController in order to auto-route the page that is doctype Event, and therefore to return an object of the new type EventDetail:

using System.Web.Mvc;
using Our.Umbraco.Ditto;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;

namespace eg.skrift.data.Controllers
{
    public class EventController : RenderMvcController
    {
        /// <summary>
        /// Renders the Event page
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public override ActionResult Index(RenderModel model)
        {
            var typedModel = model.As<EventDetail>();
            typedModel.EventRating = GetEventRating();
            return CurrentTemplate(typedModel);
        }

        /// <summary>
        /// Gets the rating for an event
        /// </summary>
        /// <returns></returns>
        private int GetEventRating()
        {
            //TODO: return real rating
            return 4;
        }
    }
}

Then our Razor file for an Event can render the Event Rating:

@inherits UmbracoTemplatePage<eg.skrift.data.EventDetail>
<div id="event">
    <div class="container-fluid" id="event">
        @if (Model != null)
        {
            <div class="rating">
                <h1>@Model.Content.EventTitle</h1>
                <h2>@Model.Content.EventSummary</h2>
                <b>Event Rating: @Model.Content.EventRating</b>
            </div>
        }
    </div>
</div>

How does this follow the open/closed principle?

Meyer's original proposal for extending classes without modifying them relied upon creating a new class that inherited from an existing class. However, this strategy of using concrete classes has since been surpassed. Our future implementations might change, or we might need multiple implementations. We'd rather follow the polymorphic open/closed principle, by using inheritance from abstracted interfaces. Polymorphic means "many forms". The existing interface is closed to modifications and new implementations must implement that interface. Implementations can be "polymorphically substituted for each other". That is a lot of Polly and a lot of Morph.

If you've never come across these concepts before it might sound intimidating, but real world examples help give context. Imagine that we want to change the way we do the actual rating, for instance, we switch from rendering the rating from an external event rating service, to storing it in Umbraco.

You might be thinking "if I need to add a new Umbraco doctype Rating property, I have no choice but to change the generated Event class, because the next Models Builder run will add the new Rating property to Event.generated.cs."

However, by adding a new Rating document type to the Event doctype via composition, we:

  • Extend the generated properties in the class
  • Do not modify the existing behaviour of the class

Although, it should be considered that if you want to delete or change the data type of an existing property on a document type instead, you could lose data and are perhaps breaching the open/closed principle.

Long-term maintenance and effects need to be considered if you're always extending and never deleting. We don't want the CMS to become cumbersome due to legacy properties. A balance needs to be had, and the last thing anyone needs is an over-architected solution.

Compositions

Now back to the Event Rating example. Instead of adding the new Rating property directly to the Event doctype, we could add a new property to the Umbraco document type via Umbraco's Compositions feature, rather than modifying an existing property. In Models Builder generated classes, any document type used in a composition (previously known as a "mixin") generates an interface for the class it is composed from, and implements the well-known IPublishedContent interface. The generated classes of any doctypes composed of another doctypes implement it's interface, and contain all of the members of that interface. This is programming by contract.

Create the new doctype for Event Rating and add it to the Event via composition. Don't forget to regenerate the models after saving the doctype:

The effect of creating a new docType called Rating, adding this to the Event doctype via composition, and running Models Builder is that:

  • The Event class now implements a new IEventRating interface that has been generated as a partial interface, which itself implements IPublishedContent
  • Event still inherits from PublishedContent and has its original properties, but now also implements the new IRatinginterface containing the new Rating property
  • Regenerating the models has created a concrete instance of the EventRating class that also implements the IRating interface

We can populate the Umbraco Rating field for the Event, save and publish. Next, our custom Event class now can be edited to use the new Umbraco property instead of the dummy hard-coded int:

using System;
using System.Globalization;
using Umbraco.Core.Models;

namespace eg.skrift.data
{
    public class EventDetail : Event, IEventRating
    {
        public EventDetail(IPublishedContent content) : base(content)
        {
        }

        /// <summary>
        /// Get the rating from Umbraco
        /// </summary>
        public int EventRating
        {
            get
            {
                try
                {
                    int.TryParse(Rating, out int rating);
                    return rating;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Issue when parsing the rating to an int", e);
                    throw;
                }
            }
        }
    }
}

Our controller can simply return the view with the typed Umbraco content (it uses Ditto to map the IPublishedContent to a concrete EventDetail class):

using System.Web.Mvc;
using Our.Umbraco.Ditto;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;

namespace eg.skrift.data.Controllers
{
    public class EventController : RenderMvcController
    {
        /// <summary>
        /// Renders the Event page
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public override ActionResult Index(RenderModel model)
        {
            var typedModel = model.As<EventDetail>();
            return CurrentTemplate(typedModel);
        }
    }
}

In future, if requirements change to using a third party service instead of Umbraco to store the ratings, we could do the following:

  • Undo the composition of the Rating doctype on the Event docType
  • Regenerate models
  • Recreate the IRating interface previously generated and make it into a custom interface (property can now be an int)
  • Revert the EventController to assign the rating value instead of taking it from Umbraco

The implementation can now use a third party service to store event ratings. Our existing EventDetail class will not change as it still inherits from Event, it just has to implement the IRating interface, so we don't need to alter the code inside the Event class. When EventDetail inherited from the Event with an Umbraco doctype Rating composition on it, it already implemented the IRating interface so we didn't need to specify that in code. Any unit tests using IRating interface should still be good to go. Bear in mind that since you will be removing a composition from the doctype, you are modifying data, so best to have a data migration strategy in place.

Liskov substitution principle

The Liskov Substitution Principle (LSP) was defined by American scientist and professor Barbara Liskov in “Data Abstraction and Hierarchy,” 1988, and while it seems complex initially, it can be summarised as:

"objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program"

...right, so that actually still sounds quite complex. I want to break it down more:

Similar objects (like those that derive from the same definition, or implement the same contract), can be replaced with ease and no YSOD, DEFCON 1, page-exploding support tickets coming through.

...even more:

You can swap things and they don't break.

Lets carry on in terms of our Event example above. Now that we have successfully got Ratings implemented and have some breathing space, we've looked at the backlog and one of the tasks is to sort Events into different Event types. There are actually several - Live EventsInterview Events and Prerecorded Events.

The first task is fairly standard - we need to create each required Event type as a new doctype in Umbraco - InterviewEventLiveEvent and PrerecordedEvent. We rename Event to EventBase and move it to the doctype Composition folder for organisation. We already took off the Rating composition due to the previous change, so the EventBase doctype can now be used as a composition itself.

We compose all new Event types from the Event Base doctype, and set them all to use the existing Event template. EventBase itself no longer needs a template, as we're not going to use it as an independent page in future.

NB: Don't forget to give permissions to the Home node to allow these docTypes as children, and for neatness, switch off permission to Event Base since it is not going to be independent anymore. Also, don't forget to regenerate the models afterwards!

Luckily, Umbraco lets us change, or substitute, one doctype for another with a nice mapping feature, so we can change the existing EventBase doctype to a LiveEvent:

We are also told by our wily Business Analyst that each Event Type has their own way of getting rated:

  • Interview events are rated by radio listeners
  • Live events are rated on a third party review site
  • Prerecorded Events are rated by manual data entry after a questionnaire is submitted post event

To handle this, we need to use our own classes rather than directly referencing the generated models builder classes. We decide to create an IEventDetail interface that extends the auto-generated IEventBase and IEventRating:

eg.skrift.data.Models
{
    public interface IEventDetail : IEventBase,  IEventRating
    {
    }
}

Then we create an EventDetail class that implements the auto-generated EventBase and the custom IEventDetail interface. It contains a property for the Event Rating and inherits from EventBase to gain those Umbraco-driven properties. IEventRatinginterface remains the same, and Event Title and Event Summary are not shown because they're already present (via inheritance from the EventBase class):

using Umbraco.Core.Models;

namespace eg.skrift.data.Models
{
    public class EventDetail : EventBase, IEventDetail
    {
        public EventDetail(IPublishedContent content) : base(content)
        {
        }

        /// <summary>
        /// Get the rating from Umbraco
        /// </summary>
        public int EventRating { get; set; }
    }
}

It is the responsibility of the sub-class to give a suitable definition appropriate to the Event type. So, we have to create our own implementations of a concrete event. These new Event types have already been generated by the ModelsBuilder tool as partial classes, but we set the EventRating properties on each one as follows (you can return real ratings later):

namespace eg.skrift.data.Models
{
    public partial class LiveEvent : IEventDetail
    {
        public int EventRating => GetEventRating();

        /// <summary>
        /// Gets the rating for an event.
        /// </summary>
        /// <returns></returns>
        private int GetEventRating() => 5;
    }
}

namespace eg.skrift.data.Models
{
    public partial class PrerecordedEvent: IEventDetail
    {
        public int EventRating => GetEventRating();

        /// <summary>
        /// Gets the rating for an event.
        /// </summary>
        /// <returns></returns>
        private int GetEventRating() => 3;
    }
}

namespace eg.skrift.data.Models
{
    public partial class InterviewEvent : IEventDetail
    {
        public int EventRating => GetEventRating();

        /// <summary>
        /// Gets the rating for an event.
        /// </summary>
        /// <returns></returns>
        private int GetEventRating() => 4;
    }
}

We have:

  • Created our own partial class implementations of each new Event Type
  • Made each new event type implement the IEventDetail class
  • Made each new event type return a required value for the EventRating int property

Our Event template only needs one change - it is still inheriting from the original EventDetail class via UmbracoTemplate. To enable this template to render the model for any type of Event we change it to inherit from IEventDetail:

@inherits UmbracoTemplatePage<eg.skrift.data.Models.IEventDetail>

These pages will still be hit without a constructor due to Umbraco rendering the page using the defined template. However, if we want to customise the behaviour further, we'll need a controller. The controller that was previously hit was EventController, but this needs to be renamed to the relevant {DocTypeName}Controller in order to follow the Umbraco auto-routing convention. The solution we'll choose for now is to create a controller for each docType and then inherit from a shared base controller called EventBaseController. The new Event Type controllers don't need to call the method GetEventRating as this is dealt with in the concrete implementations via the models. They simply inherit from the EventBaseController and return the view with the given Umbraco page values populated in it:

using eg.skrift.businesslogic.Factories;
using eg.skrift.data.CMS;

namespace eg.skrift.data.Controllers
{
    public class PrerecordedEventController : EventBaseController
    {
        public PrerecordedEventController()
        {
        }
    }
}

We can change the docType of an Event in Umbraco in future, e.g. a content editor may want to change a Live Event to a Prerecorded Event once the Event has passed, swapping it out with no ill effects and therefore following LSP. We can also easily add new Event types in future by simply creating the equivalent classes that inherit from EventDetail, only needing to alter how the Rating is accessed. All the doc types can share the same Event template if they wish - a new EventController will be created if the controller logic needs to be accessed, and the concrete implementation of the doctype subclass will be used.

The key takeaway here is that:

Changing the docType will not break our implementation when getting an Event Rating, because by using the IEventDetail interface, we can switch out the classes for each other

The LiveEvent doctype doesn't need to know about PrerecordedEvent. If it did, it would violate the Open-Closed principle, because both LiveEvent and PrerecordedEvent classes would need to be modified whenever a new Event doctype composed of the EventBase doctype was created in Umbraco.

Another example of LSP is the handy Umbraco Ditto Package. This enables us to easily convert any Umbraco doctype into a strongly-typed class that implements IPublishedContent, by simply doing:

var typedModel = model.As<ClassName>(); 

This implemention works for any sub class that implements IPublishedContent, and therefore I reckon it follows LSP. Swapping stuff out without breaking it.

Interface segregation principle

We've already covered the concept of interfaces in the Open/Closed principle earlier, but to recap:

Interfaces are essentially contracts, and any class that implements an interface must follow it's contract

Like abstract base classes, classes/structs that implement an interface must implement all the members, be it events, indexers, methods, or properties. Bearing this in mind, and how many members this could end up being in large systems, the Interface Segregation Principle, described in Robert Martin's "Design Principles and Design Patterns" (2000), states that:

"many client-specific interfaces are better than one general-purpose interface"

We have shown through our earlier examples that we naturally use many client-specific interfaces by using inheritance of doctypes in Umbraco. IEventBase is generated as soon as the EventBase doctype becomes part of a composition of another doctype. Ultimately, in Version 7 at least, every node in Umbraco implements IPublishedContent, which is likely a very familiar face if you are working with back-end code and using Umbraco. Every node, whatever doctype it is, has the same members as the IPublishedContent contract dictates. Here are a few of them you'll probably recognise:

int Id { get; }
int TemplateId { get; }
int SortOrder { get; }
string Name { get; }
string UrlName { get; }
string DocumentTypeAlias { get; }
int DocumentTypeId { get; }
string WriterName { get; }
string CreatorName { get; }
int WriterId { get; }
int CreatorId { get; }
string Path { get; }
DateTime CreateDate { get; }
DateTime UpdateDate { get; }
Guid Version { get; }
int Level { get; }
string Url { get; }
...

Taking one as an example from Umbraco, UpdateDate is found in the Umbraco backoffice:

Interfaces should always be focused

So, now we need to get Event Ratings for reals, rather than our temporary hard-coded solution. We decide to create an IRatingsService that allows the retrieval and updating of ratings. This can be logically split into two parts, neither of which need to know about each other:

  • Getting a rating (IHaveRatings)
  • Setting a rating (IStoreRatings)

We create an IRatingsService that extends both the IHaveRatings and the IStoreRatings interfaces, allowing us to only pass the narrow functionality required by the consumer.

Lets add these interfaces into the sample code:

public interface IRatingsService : IHaveRatings, IStoreRatings
{
}

public interface IHaveRatings
{
    /// <summary>
    /// Get a rating for a given event
    /// </summary>
    /// <param name="eventId"></param>
    int GetRating(int eventId);
}

public interface IStoreRatings
{
    /// <summary>
    /// Store a rating for a given event
    /// </summary>
    /// <param name="rating"></param>
    /// <param name="eventID"></param>
    void SetRating(int rating, int eventID);
}

So how should we access these services in code in a SOLID way? The concrete implementation of these service interfaces can be created using Dependency Injection, which brings me onto the last, but by no means least, of the five SOLID principles - Dependency Inversion.

Dependency Inversion principle

The Dependency Inversion principle is also discussed in "Design Principles and Design Patterns" by Robert C. Martin (get the feeling this is a good book to read?), and states that:

one should "depend upon abstractions, [not] concretions"

You'll most likely have come across Inversion of Control (IoC) principle and the Dependency Injection (DI) design pattern in any back-end software development environment. There are hundreds of articles and books on each topic alone, but essentially, by using a proper DI container, like AutoFac or Ninject, you can help implement DI in your application. So? Well, imagine never having to hard-code concrete objects again!

Disclaimer: After learning Dependency Inversion techniques, you may become severely phobic of hard-coding dependencies in code

Abstracting away from the CMS

As you have seen throughout this article, we're working with abstractions wherever possible. We've learned that the concept of Umbraco nodes is built upon an IPublishedContent interface - an abstraction of the content node itself, and a shift that has enabled Umbraco to separate concerns from concrete classes in code.

Umbraco itself is based on the MVC pattern, allowing a separation of concerns between the Views (usually Razor files), the Controllers (usually inheriting from relevant Umbraco base controllers), and Models (either custom or auto-generated via something like Models Builder, or a combination).

Like most CMS systems, Umbraco exposes a Client Context in the form of UmbracoContext, which encapsulates the Umbraco-related information of the HTTP request. When building an Umbraco site, whilst the website is naturally quite tightly coupledto Umbraco (the nuget package itself builds the website with all Umbraco dependencies required), the rest of the application doesn't need to be. Umbraco provides the UmbracoCms nuget package, along with an independent UmbracoCms.Core nuget package without the "web stuff" and containing the key components of Umbraco. This means you can increasingly work away from the website, and move the models into a separate data project and logic into a separate business logic project.

Dependency Inversion Container example using AutoFac

Ideally, we would not hard-code the dependencies to Umbraco and the access to UmbracoContext in our controllers. We would instead use a DI Container like AutoFac to define the dependencies in the object composition root of the application, an example being the Global class below that extends UmbracoApplication and will replace the default Global.asax reference to Umbraco.Web.UmbracoApplication.

First using Package Manager Console, we can install the required DI Container of choice:

> install-package Autofac.Integration -project eg.skrift.data
> install-package Autofac.Mvc5 -project eg.skrift.data
> install-package Autofac.WebApi2 -project eg.skrift.data

Next, setup the DI Container in a custom Global class that inherits from UmbracoApplication (more information in the Umbraco IoC docs:

using System;
using System.Reflection;
using System.Web.Http;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Autofac.Integration.WebApi;
using eg.skrift.businesslogic.Factories;
using eg.skrift.data.Factories;
using Umbraco.Core;
using Umbraco.Web;

namespace eg.skrift.data.CMS
{
    public class Global : UmbracoApplication
    {
        /// <summary>
        /// Registers our controllers and Umbraco controllers, plus the types we need to resolve, 
        /// and sets up MVC to use Autofac as a dependency resolver as our DI Container of choice
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void OnApplicationStarted(object sender, EventArgs e)
        {
            base.OnApplicationStarted(sender, e);
            var builder = new ContainerBuilder();
            builder.RegisterInstance(ApplicationContext.Current).AsSelf();
            //register all controllers found in your assembly
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
            builder.RegisterApiControllers(typeof(Global).Assembly);
            //register umbraco MVC + webapi controllers used by the admin site
            builder.RegisterControllers(typeof(UmbracoApplication).Assembly);
            builder.RegisterApiControllers(typeof(UmbracoApplication).Assembly);
            builder.RegisterType<UmbracoLoggerFactory>()
                .As<ILoggerFactory>();
            builder.RegisterType<UmbracoContentFetcherFactory>()
                .As<IContentFetcherFactory>();
            builder.RegisterType<UmbracoContentFetcher>()
                .As<IUmbracoContentFetcher>()
                .WithParameter((paramInfo, ctx) => paramInfo.Name == "umbracoHelper",
                    (paramInfo, ctx) => new UmbracoHelper(UmbracoContext.Current));
            builder.RegisterType<RatingsServiceFactory>()
                .As<IRatingsServiceFactory>();
            var container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
            GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
        }
    }
}

The updated Global.asax now references this Global.cs class:

<%@ Application Inherits="eg.skrift.data.CMS.Global" Language="C#" %>

The above code follows Dependency Inversion by binding a context interface to its concrete implementation. IUmbracoContentFetcher is bound to UmbracoContentFetcherIRatingsServiceFactory is bound to RatingsServiceFactory. Notice that we use a custom class called UmbracoContentFetcherFactory - we use this in order to spin up our own UmbracoContentFetcher. This is essentially a class that wraps the default UmbracoHelper class but it implements its own IContentFetcher interface, and adds some additional Umbraco content-related methods that use the helper, or their own Examine-driven content queries.

We also use an UmbracoLoggerFactory, which we inject into our controllers using DI - it implements an ILoggerFactory. Through Dependency Injection, our logger can log any separate errors that come from an Angular application in the same site to the same centralised Umbraco logs. This is despite the fact that our Angular API knows nothing about Umbraco - you can inject the ILoggerFactory into the relevant API Controller, and log away like you're a lumberjack. It also means that at any time we could choose to swap out this logger from using Umbraco logging to something different, by simply changing one line in the OnApplicationStarted method above.

Decoupling code from the CMS

At Rock Solid Knowledge (SOLID being very apt), we have built a lot of business logic that uses Umbraco, but is completely decoupled. That has the huge advantage of allowing us to:

  • Unit test without worrying about UmbracoContext
  • Migrate aspects of functionality away, or into, the CMS as we desire
  • Use Mocks for unit testing

Having the interfaces injected in also means that if we want to replace Umbraco with another CMS system (again, promise we won't!), we can just modify the bindings. Theoretically, and I know this is idealistic because there are a lot of moving parts to consider, there would be no further changes to the MVC application and controllers required.

Back to the Ratings Service. We have created the IRatingsService.csIHaveRatings.cs and ISetRatings.cs interfaces in a new Business Logic project eg.skrift.businesslogic. We also have followed the Factory Pattern and created an IRatingsServiceFactoryand a RatingsServiceFactory, the sole purpose being to create a RatingsService. We decided via the AutoFac DI Container that the concrete factory for IRatingsServiceFactory will be the concrete RatingServiceFactory, which will create a concrete RatingService for us when we inject it and call Create() on it.

We have built up a suite of unit tests that create the required functionality for the IRatingsService by following the red, green, refactor unit testing principles. As a reminder, these are:

  1. Create a failing unit test (RED)
  2. Write code to make that test pass (GREEN)
  3. Make it pretty (REFACTOR)

The example code uses Nunit but it is personal preference. We haven't started to use Mocks here yet - this is just a starting point for now:

using System;
using eg.skrift.businesslogic.Services;
using NUnit.Framework;

namespace eg.skrift.businesslogic.tests
{
    [TestFixture]
    public class RatingsServiceTests
    {
        private const int testEventId = 4047;
        private const int rating = 5;

        [SetUp]
        public void SetUp()
        {
            //TODO: setup code here
        }

        [TearDown]
        public void TearDown()
        {
        }

        public RatingsService CreateSut()
        {
            return new RatingsService();
        }

        [Test]
        public void GetRating_WhenCalledWithEventId_ReturnsCorrectRating()
        {
            var sut = CreateSut();

            var result = sut.GetRating(testEventId);
            Assert.That(result, Is.Not.Null);
            Assert.That(result, Is.EqualTo(rating));
        }

        [Test]
        public void SetRating_WhenCalledWithRatingAndEventId_ShouldNotThrowException()
        {
            var sut = CreateSut();

            try
            {
                sut.SetRating(rating, testEventId);
            }
            catch (Exception ex)
            {
                Assert.Fail("Expected no exception, but got: " + ex.Message);
            }
        }
    }
}

These tests have led to the interface having the methods:

namespace eg.skrift.businesslogic.Services
{
    /// <summary>
    /// Service to get and set ratings for an event
    /// </summary>
    public class RatingsService : IRatingsService
    {
        /// <summary>
        /// Get an event rating
        /// </summary>
        /// <param name="eventId"></param>
        /// <returns></returns>
        public int GetRating(int eventId)
        {
            //TODO: implement functionality
            return 5;
        }

        /// <summary>
        /// Set an event rating
        /// </summary>
        /// <param name="rating"></param>
        /// <param name="eventID"></param>
        public void SetRating(int rating, int eventID)
        {
            //TODO: implement functionality                        
        }
    }
}

As you saw earlier, we've already created a concrete RatingsService that implements IRatingsService, and we've registered the factory that creates it, IRatingsServiceFactory, via our DI Container. Now we're ready to inject the factory into our controller in order to create an instance of the RatingsService and use it. We'll also inject the ILoggerFactory and create an instance of our own logger (NB: you don't have to do it this way in Umbraco - you can use the built-in logging functionality):

using System;
using System.Reflection;
using System.Web.Mvc;
using eg.skrift.businesslogic.Factories;
using eg.skrift.businesslogic.Services;
using eg.skrift.data.CMS;
using eg.skrift.data.Models;
using Our.Umbraco.Ditto;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;

namespace eg.skrift.data.Controllers
{
    public class EventBaseController : RenderMvcController
    {
        internal new ILogger Logger;
        private IRatingsService ratingsService;

        public EventBaseController(ILoggerFactory loggerFactory, IRatingsServiceFactory ratingsServiceFactory)
        {
            if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory));
            InitaliseLogger(loggerFactory);
            if (ratingsServiceFactory == null) throw new ArgumentNullException(nameof(loggerFactory));
            InitaliseRatingsService(ratingsServiceFactory);
        }

        private void InitaliseRatingsService(IRatingsServiceFactory ratingsServiceFactory)
        {
            ratingsService = ratingsServiceFactory.Create();
        }

        private void InitaliseLogger(ILoggerFactory loggerFactory)
        {
            Logger = loggerFactory.Create(MethodBase.GetCurrentMethod().DeclaringType);
        }

        /// <summary>
        /// Renders the Event page
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public override ActionResult Index(RenderModel model)
        {
            var typedModel = model.As<EventDetail>();
            if (ratingsService != null) typedModel.EventRating = ratingsService.GetRating(typedModel.Id);
            return CurrentTemplate(typedModel);
        }
    }
}

Note that in the sample code, the eg.skrift.web project references the eg.skrift.data project, the eg.skrift.data project references the eg.skrift.businesslogic, and the eg.skrift.businesslogic project has no knowledge of either project, nor does it have knowledge of Umbraco at all. You can compile and run the code, and the Event view will be able render the Event Rating from the service we injected using DI, which has no knowledge of the CMS at all. 😺

DI for the absolute win

Summary

We've covered each of the five SOLID principles, and considered some ways that they could be applied to CMS Development. We've built up our code to try and follow SOLID principles using realistic scenarios, and we've thought about ways in which it can become tricky, and how to try and get around that. I imagine that this topic could create a lot of debate and consideration, so I am keen as mustard to be told of other peoples implementations of SOLID in CMS development. There are many other ways of following SOLID and a lot of additional design patterns and subjects that I've not covered in here (I'll leave describing the many advantages of unit testing Umbraco to Lars-Erik Aabech!) - I'm sure that I'll be refactoring the sample code shown in future.

I'd love to chat about this topic in person, as it is definitely something that needs to be regularly revisited, whether via additional articles or talks in future - especially as Umbraco and other CMS products continually evolve and get optimised. Hopefully this article has got you thinking and talking about how to adhere to SOLID principles when developing with a CMS.

Just because the CMS is boxed up and ready to go doesn't mean that we should stop thinking outside the box and trying to follow SOLID development principles

References and Useful Reads

Shout Outs

Emma Garland

Emma is Senior Software Engineer at Rock Solid Knowledge, an Umbraco Gold Partner based in Bristol, UK. Emma leads the Umbraco CMS development team and has been working with Umbraco since 2008. She loves coding, comparing it to the world's best logic puzzle or being in an evolving open world computer game. Keen to contribute to the community, she can be found at various code camps, writing articles or delivering user group talks.

comments powered by Disqus