04 October 2016

Dependency injection in a modular architecture like Sitecore Helix

Registering all your types using your favorite IoC container in a modular architecture based solution (cfr Sitecore Helix) might become time consuming and cumbersome over time. Not to mention the times you've developed a new feature and published it to find out you've forgotten to register your new types! That means an extra site recycle...

To overcome this you can implement a system which registers your types automatically. No more forgetting, no more adding project references; just develop and play! The sample code uses Autofac, but you can use any other IoC container you like.

Setting up the basics

First thing to do, is to create an interface that will be used in every module and scanned for when registering the types.
using Autofac;

public interface IModuleInitializer
{
    ContainerBuilder Register(ContainerBuilder builder);
}

Having defined the interface, let's implement a default implementation for it. This one will do all of the registering work for every type in each module in your solution.

What it does:
  1. First it gets the current assembly.
  2. Then it scans the assembly for types where the names ends with a few pre-defined keywords. This is a convention that you must agree upon with your team.
  3. The types it finds will be registered as the interface it implements. Any other configuration is done as well, like single instance or auto-wiring properties.
  4. The last step is to register the controllers and API controllers.
using System;
using Autofac;
using Autofac.Integration.Mvc;
using Autofac.Integration.WebApi;

public class DefaultModuleInitializer : IModuleInitializer
{
    public virtual ContainerBuilder Register(ContainerBuilder builder)
    {
        var assembly = GetType().Assembly;

        builder.RegisterAssemblyTypes(assembly)
            .Where(
                x =>
                x.Name.EndsWith("Repository", StringComparison.OrdinalIgnoreCase) ||
                x.Name.EndsWith("Factory", StringComparison.OrdinalIgnoreCase) ||
                x.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase))
            .AsImplementedInterfaces()
            .SingleInstance()
            .PropertiesAutowired();

        builder.RegisterControllers(assembly).PropertiesAutowired();
        builder.RegisterApiControllers(assembly).PropertiesAutowired();

        return builder;
    }
}

You may already have noticed that the method is marked as virtual. This comes in handy if you need to add types that don't match the predicate, or in case you need to overwrite the default implementation.

Add it to every module

In order to have it register your module's types, you'll need to create a class which inherits the DefaultModuleInitializer in each module. You can also implement IModuleInitializer instead if your module is way off your default implementation. Remember to have one implementation in each module!

In most cases your class will look like this:
public class ModuleInitializer : DefaultModuleInitializer
{
}

However, if you need extra registrations, then it might look like this:
using Autofac;

public class ModuleInitializer : DefaultModuleInitializer
{
    public override ContainerBuilder Register(ContainerBuilder builder)
    {
        builder = base.OnContainerInitializing(builder);
        builder.RegisterType().AsSelf().PropertiesAutowired();

        return builder;
    }
}

Let your IoC container know!

Now it's time to actually register your types. The GetModuleInitializers() method searches for classes implementing IModuleInitializer by scanning all bin files starting with your custom namespace. Each class found is then instantiated and returned. The Register() method will loop all found IModuleInitializers and call its Register() method. Et voila, you're done!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Autofac.Integration.Web;
using Autofac.Integration.WebApi;
using Sitecore.IO;

public static class AutofacConfiguration
{
    public static IContainerProvider Register()
    {
        var builder = new ContainerBuilder();
        var modules = GetModuleInitializers().ToList();

        foreach (var moduleInitializer in modules)
        {
            moduleInitializer.Register(builder);
        }

        var container = builder.Build();

        GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

        return new ContainerProvider(container);
    }

    public static IEnumerable<IModuleInitializer> GetModuleInitializers()
    {
        var assemblyFilenames = FileUtil.GetFiles(FileUtil.MapPath("/bin"), "YourNamespace.*.dll", false);
        var moduleInitializerType = typeof(IModuleInitializer);

        foreach (var assemblyFileName in assemblyFilenames)
        {
            var assembly = Assembly.LoadFrom(assemblyFileName);
            var initializerTypes = assembly.GetTypes().Where(x => moduleInitializerType.IsAssignableFrom(x) && !x.IsInterface);

            foreach (var initializerType in initializerTypes)
            {
                yield return Activator.CreateInstance(initializerType) as IModuleInitializer;
            }
        }
    }
}

No comments :

Post a Comment