Implementing best practice application design principles in Kentico 8

 

In this post Lead CMS Developer Chris Parkinson discusses how we use n-tier architecture and Ioc at Control F1 to allow us to write abstracted, testable custom code within Kentico, and how we then consume this custom code in the CMSApp and CMSApp_AppCode projects.

NB This post assumes a good working knowledge of Kentico 8 and understanding of SOLID principles, including n-tier and dependency injection / inversion of control. If the latter is new to you, here is a good place to start.

At Control F1 we like to apply good application design principles to any development project, be it Greenfield, mobile or CMS integration. These typically include the use of dependency injection, n-tier, and separation of concerns to allow custom code to be more easily tested.

Sometimes, however, particularly when working with an off the shelf product, we have to come up with workarounds. Kentico is a brilliant tool for quickly developing feature-rich websites, but as with any proprietary software it isn’t always the easiest to extend. We typically use Kentico web application projects at Control F1. This allows us to more easily integrate Kentico with our build processes using MSBuild (another article entirely).

One of the quirks of web application projects relates to the continued use of an App_Code  folder; which is a hangover from the older Website project. In website projects, code added and edited within App_Code is compiled on the fly, allowing server side code to be developed without the need to re-compile. This concept doesn’t really exist in web applications. Here is a good post from Microsoft explaining the differences between websites and web applications in .NET

Kentico has split out App_Code into its own VS project called CMSApp_AppCode, with any code/folders under it moved under a folder called Old_App_Code.

Kentico 8 pic

This causes two fundamental problems:

  1. Any custom objects or providers generated from custom page types or custom classes in Kentico are generated here.
  2. Any custom code that needs to inherit from CMSLoaderAttribute (custom events, extending the order provider etc) HAS to go here.

Remember these two points and we’ll quickly move on and discuss how we architecture our solution.

Historically, Control F1 Kentico solutions consisted of the following projects:

Kentico 8 2

  • Core (Utilities, helpers etc.)
  • Data (DAL layer)
    • Extensions (Extensions to CMS ‘entities’ – out of the box info, objects etc.)
    • Mappings (Classes that map ‘entities’ to ‘domain’ objects)
    • Repositories (Data repository classes to wrap up Kentico providers)
  • Domain
    • DTOs / DTO Extensions
  • Services
    • Service classes that typically wrap up the repository layer but return a ServiceResult<T> object, allowing consistent consumption from the client

The Service and Data projects typically contain both the interface and the implementation, meaning that to use a service you need to reference the Services project.

This is absolutely fine from the CMSApp project – you can happily reference Services and use them as you wish using your Ioc container of choice. But it’s time to revisit the two fundamental problems with Kentico caused by the CMSApp_AppCode project:

1. Any custom objects or providers generated from custom classes in Kentico are generated here.

Going back to our application architecture, we have a Data project which we use to wrap up out of the box Kentico info and provider objects. We also want to do this with any custom objects generated from custom classes in the CMS, meaning that we need to add a reference to App_Data from our Data project. This leads us nicely on to problem 2…

2. Any custom code that needs to inherit from CMSLoaderAttribute (custom events, extending the order provider etc) HAS to go here

Consider an eCommerce website where we might want to trigger custom code when the ‘Paid’ event is fired. We’d typically do this by creating a custom provider object that extends OrderInfoProvider, and by overriding the ProcessOrderIsPaidChangeInternal method to check for the IsPaid property.

public class MyCustomOrderProvider : OrderInfoProvider
{
    protected override void ProcessOrderIsPaidChangeInternal(OrderInfo orderInfo)
    {
        if (orderInfo.OrderIsPaid)
        {
            // TODO - our custom code here
        }
        base.ProcessOrderIsPaidChangeInternal(orderInfo);
    }
}

Now say we want to execute some custom code written in an IOrderService, we’d have to add a reference to the Services project.

Eek, fail!

Kentico 8 3

When we try and add a reference to our services project from CMSApp_AppCode, we get a circular reference error. This makes complete sense. CMSApp_AppCode is referenced by Data which is referenced by Services, so with our current architecture there’s no obvious way to use custom services in CMSApp_AppCode. This is obviously no good – we’ve already written custom code and tested so we don’t want to recreate this using Kentico’s provider objects – the whole point was abstracting this.

The solution is actually fairly simple, albeit requires a bit of refactoring and the use of reflection. Reflection, used with care, is a great tool that allows you to load an instance of an object at runtime – which is perfect for this scenario. Here is another good post on CodeProject explaining reflection in .NET.

Firstly, consider the initial good design principles we were discussing – particularly separation of concerns. If we’re using inversion of control, our consuming application doesn’t really need to know implementation details. All it needs to know is that we have an IOrderService which contains a method called DoStuff().  Therefore, with this in mind we can refactor our application to split out interfaces and implementations into separate projects. The solution now looks like:

kentico 8 4

  • Core
  • Data
  • Domain
  • Services
  • Interfaces
    • Services
    • Data

Our CMSApp_AppCode and CMSApp projects can now reference the interfaces project. However, we still need to create new implementations of these interfaces in order to use them. As we discussed earlier, CMSApp can happily reference the services directly as there are no dependencies on CMSApp from the n-tier layer. From CMSApp_AppCode we can’t reference services directly, but because we’ve abstracted interfaces from implementations we can load the implementations dynamically using reflection and map them to the correct interface.

Steps to achieve this are as follows:

  1. We need to load the assemblies – in this instance we need both Data and Services. We’re using dependency injection in our services and need to pass in the repositories from the data project. We’ve created a helper method to allow us to get the correct directory without specifying the full file path.
    var dataAssembly = Assembly.LoadFile(string.Concat(IoHelper.AssemblyDirectory, "\\Data.dll"));
     
    var _servicesAssembly = Assembly.LoadFile(string.Concat(IoHelper.AssemblyDirectory, "\\Services.dll"));

     

  2. Next we need to search the loaded assembly for an exported type that is assignable to our interface. In this case we’ve created an extension method that returns back an instance of the supplied type (providing a named parameter for when we have multiple instances of an interface).
    public static Type GetType<T>(this Assembly assembly, string name)
    {
        return (from type in assembly.GetExportedTypes()
                where typeof(T).IsAssignableFrom(type)
                && type.Name == name
                select type).
            Single();
    }
    var orderMapperType = dataAssembly.GetType<IMapper<Order, OrderInfo>>();
     
    var orderRepositoryType = dataAssembly.GetType<IOrderRepository>();
     
    var orderServiceType = servicesAssembly.GetType<IOrderService>();

     

  3. And finally we create new instances, passing in any dependencies.
    var orderMapper = (IMapper<Order, OrderInfo>)Activator.CreateInstance(orderMapperType);
     
    var orderRepository =
                   (IOrderRepository)Activator.CreateInstance(orderRepositoryType, orderMapper);
     
    var orderService = (IOrderService)Activator.CreateInstance(orderRepository)

It’s probably also worth mentioning Ioc in a bit more detail. Kentico 8 is a primarily WebForms application, meaning that whilst we could integrate an off the shelf library such as Ninject, Unity or StructureMap, unless you’re using an MVP pattern – which Kentico doesn’t – these libraries are quite fiddly to get to work. We want to use a well architected solution for our custom code that follows the good design principles we’ve previously discussed, but we don’t want to spend too much time fighting the way Kentico works.

In this instance we decided to create our own simple IServiceContainer. This is an interface that sits in the interfaces project under the Ioc namespace and contains public properties for the services we want to expose – repositories and mappings and private. These are used internally, but we don’t want the client to have access to them.

public interface IServiceContainer
{
    IOrderService OrderService { get; set; }
}

Our solution contains two implementations – one within CMSApp that creates new instances of implementations, and one within CMSApp_AppCode that uses reflection to create new instances as previously discussed. We’re also using lazy loading, meaning instances are only created when we actually need them. We typically then create ‘base’ abstract classes for either CMSWebParts / Modules / Loaders etc. that contain a protected property which in turn contains the IServiceContainer implementation. This allows us to call ServiceContainer.OrderService.DoStuff() etc.

CMSApp
public class CMSAppServiceContainer : IServiceContainer
{
    private IOrderService _orderService;
    public IOrderService OrderService
    {
        get
        {
            if (_orderService == null)
            {
                _orderService = new OrderService();
            }
            return _orderService;
        }
    }
}
CMSApp_AppCode
public class CMSAppAppCodeServiceContainer : IServiceContainer
{
    private IOrderService _orderService;
    public IOrderService OrderService
    {
        get
        {
            if (_orderService == null)
            {
                _orderService = (IOrderService)Activator.CreateInstance(_orderRepository)
            }
            return _orderService;
        }
    }
}

In summary, I hope you find this useful. I also hope that I’ve successfully demonstrated that with a bit of effort it’s fairly straightforward to write abstracted testable code within your Kentico solutions that can be used in the CMSApp and CMSApp_AppCode projects.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s