Loosely Coupled and Testable Application Enabled by Dependency Injection

How to set up a loosely coupled and testable Umbraco Application Using Dependency Injection

Umbraco unit testing TDD Tuesday, May 20, 2014

A key part in any loosely coupled application is to program against abstractions and the way to do that here is through dependency injection. I setup an IoC container (Castle Windsor in this case).

public class ContainerBootstrapper : IContainerAccessor, IDisposable
{
    readonly IWindsorContainer container;

    ContainerBootstrapper(IWindsorContainer container)
    {
        this.container = container;
    }

    public IWindsorContainer Container
    {
        get { return container; }
    }
    public static ContainerBootstrapper Bootstrap()
    {
        // Looks for all windsor installers in the application assembly and installs them.
        var container = new WindsorContainer().Install(FromAssembly.This());

// Adding this controller factory to the factories resolver allows Umbraco to decide if it should or should not         // use it because it implements the CanHandle method of the IFilteredControllerFactory interface.         FilteredControllerFactoriesResolver.Current.InsertType<FilteredWindsorControllerFactory>(0);         return new ContainerBootstrapper(container);     }     public void Dispose()     {         Container.Dispose();     } }

The FilteredWindsorControllerFactory inserted above checks if the controller type if registered in the IoC container (not the case of RenderMvcController). The factory is only used if the controller type is registered. To this at play, you may download the Umbraco solution I prepared in attachment or check this stackoverflow answer about Umbraco MVC with Castle Windsor.


public class FilteredWindsorControllerFactory : DefaultControllerFactory, IFilteredControllerFactory
{
  public bool CanHandle(RequestContext request)
  {
    Type controllerType = GetControllerType(request, request.RouteData.Values["controller"].ToString());
    return WindsorActivator.Container.Kernel.HasComponent(controllerType);
  }
  
  // Rest omitted of simplicity.
}

Register the umbraco context so it can be injected in the controllers (we need to set it up as a dependency in the constructor so that controllers become unit testable).


public class UmbracoContextInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
                        Component.For<UmbracoContext>()
                        .UsingFactoryMethod(() => UmbracoContext.Current)
                        .LifestylePerWebRequest());
    }
}

Register the controllers.


public class ControllersInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        // Normally, it would be: container.Register(Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient()
        // but I installed MvcMailer in my application (used to create email templates)
        // and it used IController classes which I don't want to use here. Use the version
        // you need.
        Type mailerType = typeof(IMailer), controllerType = typeof(IController);

        container.Register(
                        Classes.FromThisAssembly()
                        .Where(t => controllerType.IsAssignableFrom(t) && !mailerType.IsAssignableFrom(t))
                        .LifestyleTransient());
    }
}

Register the business services.


public class BusinessLayerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
                        Classes.FromAssemblyContaining<PostService>()
                        .Where(t => t.Name.EndsWith("Service"))
                        .WithServiceDefaultInterfaces()
                        .LifestylePerWebRequest());
    }
}

And, finally, the umbraco data context (a class I created that gets me the homepage of my site and methods to query content nodes and media), unit of work and the repositories. The IUnitOfWork is an abstraction I created so that the business layer would be abstracted from the data source, in this case of my application, Umbraco but I could add another repository accessing another source (e.g. email repository). Anyway, the layers being decoupled, the business layer doesn't need to know. Recognise the Liskov Substitution principle here. Then the IRepositoryFactory is an abstract factory injected in the unit of work to inject the repositories and the UmbracoDataContext is just a wrapper for that keeps things in context for all repositories.

Please feel free to inspect the solution attached. You may find that you wouldn't need these layers decoupled as such because you don't ever want you application to change. You could make a case against and I won't stop you from not doing it. In fact, it may not even be worthy to go such length. You just need to ask yourself if you need your layers to be unit testable.

BTW, since this is a monologue, let me play advocate for other alternatives that may be perfectly valid. There's an alternative to having a unit of work with repositories and you may still keep your business layer testable. Create an IUmbracoService to wrap your stuff and you're done. Simpler, not as flexible but may be just what you need.

public class DataAccessLayerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
                    Classes.FromAssemblyContaining<UmbracoUnitOfWork>()
                        .Where(t => t.Name.EndsWith("UnitOfWork") || t.Name.EndsWith("Repository"))
                        .WithServiceDefaultInterfaces()
                        .LifestylePerWebRequest());

        container.Register(
                    Component.For<IRepositoryFactory>()
                    .ImplementedBy<UmbracoRepositoryFactory>()
                    .LifestylePerWebRequest());

        container.Register(
                    Component.For<UmbracoDataContext>()
                    .UsingFactoryMethod(() =>
                        new UmbracoDataContext(
                            HttpContext.Current.Request.Url.DnsSafeHost,
                            UmbracoContext.Current))
                    .LifestylePerWebRequest());
    }
} 

Now that we registered our dependencies, we need to stub the Umbraco ones.