Skip to content
Matthew Adams By Matthew Adams Co-Founder
Component Discovery and Composition II - Abstracting the container implementation

In the previous instalment, we built out a model for component composition by convention, and showed how this could be implemented over MEF and/or Castle Windsor.

Other IOC containers exist, though, and it would be nice if we could abstract over the container, and plug in different implementations depending on our needs and preferences.

Seems like a good idea; but abstraction for the sake of it is a very popular anti-pattern, so what's our real justification?

In our case, we're doing this because our clients often make an internal bet on a particular container implementation, and we need to fit in with those requirements when we deliver to them. Being able to switch container is, therefore a 'must have' feature for us.

A weaker, but still persuasive answer is that it also gives us some future proofing (for instance, we may want to make a bet on Castle Windsor now, but suffer minimal pain if we switch to MEF in .NET vFuture, or Unity or something else).

Your mileage may vary.

OK, I'm sold. We're going to abstract the container. What should the API look like?

Inevitably, when you build a façade over something, two things happen.

  1. Your façade looks most like the first thing you implement, and subsequent implementations are bent a bit to fit the established interface.

However hard you try (even if you implement over two implementations in the first pass), this will tend to be true. You're not trying to implement the unknown, but the known; get too far from the known abstractions and you're probably going wrong.

  1. Your façade is a simpler subset of the functionality offered by your underlying implementation.

This is a good thing, because you're applying the YAGNI1 principle. It makes your façade simpler for your own developers (including yourself). It also means that your façade will tend to grow over time: as you need more functionality, you'll add it (and add across all implementations, so you're more likely evolve a more suitable abstraction, if you're careful about refactoring).

What does that mean for us?

Well, we're implementing over Castle Windsor as our first candidate. That means we're probably going to pick words like 'Resolve' rather than 'Import' or 'Satisfy' in our API. Fair enough.

The second point tells us to look for the minimum functionality we need. For an IOC container, that guides us towards the 3-calls principle. At the very least, our API needs to be able to bootstrap the container, resolve something from it (and all its dependencies) and shut the container down again.

The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

Let's look at the meat-and-drink of resolving components from the container first, and then look at bootstrapping and shutting down.

Resolving components from the container

The most common pattern is where we resolve a component of a known type (and all its dependencies) from the container at start-up with a call something like this:

var rootObject = myContainer.Resolve<IRootComponent>(); 

rootObject.GoGoGo();

That's our standard "resolve" call in the 3-calls pattern; but we also need to consider scenarios where we are doing some sort of indirected lookup in the container. For example, we might need to look up some component from the container by name, to match a text string in some serialized form, for instance; or bootstrap from some runtime-determined type information.

Perhaps like this:

var rootObject = myContainer.Resolve("MyName");

or like this:

Type runtimeDetermineType = DetermineTypeSomehow(); 

var rootObject = myContainer.Resolve(someType);

So, there are three different Resolve() overloads we might need already.

However, as we've said in previous installments, calling Resolve() is something we want to do as infrequently as possible.

It is generally not a good thing to take an explicit dependency on a particular component, and fail to acknowledge this fact until you hit a runtime failure; it is often better to hit those sorts of problems at compile-time or, failing that, start-up, if we can possibly help it.

Calling Resolve() also adds to our testing friction. We need to mock out our container, rather than just mocking out the dependency.

To minimize the need to call Resolve(), we want the container to satisfy these child dependencies for us, automatically. Typically we've done that by using constructor injection.

But not all constructor initialization is equal. Windsor performs 'conventional' constructor injection and MEF 'attributed' constructor injection.

We prefer convention over explicit configuration, so we're making a decision to support conventional constructor initialization in our API.

Let's take a moment to think about that. Even without writing any code, we're making a decision that has a profound impact on the work needed to support different types of container implementation. Our path for Windsor will be straightforward; but our path to a MEF implementation, a little less so. As long as we understand that, that's OK. We're prepared to take that potential future hit for the sake of the kind of API we'd like to use.

So, here's a first stab at an interface for our container.

 /// <summary>
 /// An interface for a service container
 /// </summary>
 public interface IServiceContainer
 {
     /// <summary>
     /// Resolve a component from the container
     /// </summary>
     /// <typeparam name="T">The type of the component to resolve</typeparam>
     /// <returns>The resolved component, or null if no component is found</returns>
     T Resolve<T>();
     /// <summary>
     /// Resolve all components of type T from the container
     /// </summary>
     /// <typeparam name="T">The type of the component to resolve</typeparam>
     /// <returns>The resolved components, or null if no component is found</returns>
     IEnumerable<T> ResolveAll<T>();
     /// <summary>
     /// Resolve a tagged component from the container
     /// </summary>
     /// <typeparam name="T">
     /// The type of the component to resolve
     /// </typeparam>
     /// <param name="tag">
     /// The tag for the component
     /// </param>
     /// <returns>
     /// The object corresponding to the tag, or null if no tag exists
     /// </returns>
     T Resolve<T>(string tag); 

     /// <summary>
     /// Resolve a component from the container
     /// </summary>
     /// <typeparam name="T">The return type for the component to resolve</typeparam>
     /// <param name="type">The type of the component to resolve</param>
     /// <returns>The resolved component, or null if no component is found</returns>
     T Resolve<T>(Type type);
}

Shutting down the container

That deals with resolving components. How about shutting down?

Well, the standard .NET approach for manual lifetime management is the "dispose" pattern. So let's follow that through and implement IDisposable on our container.

public interface IServiceContainer : IDisposable
{
    // ...
}

OK, that was simple. What about bootstrapping?

Bootstrapping the container

Bootstrapping is a slightly more complex process that breaks down into two steps:

  1. Find appropriate configuration information for the container
  2. Initialize the container from that configuration information

We've seen how this is done with MEF (we create component catalogs, aggregate them, and feed them in to the composition container), and with Windsor (we create one or more objects implementing IWindsorInstaller, and install them into the container).

The interesting thing about our Windsor approach is that we choose to use MEF to do the discovery and creation of the IWindsorInstaller instances. We also spoke of an alternate approach using Windsor itself to find those installers. We could also use a config file, or a call out to a web service, or whatever. Clearly, we can strategize over that configuration discovery step. In which case, our API needs to provide the flexibility to plug-in a configuration mechanism separately from the container initialization itself. Here's the interface we came up with for such a bootstrapping service.

/// <summary>
/// An interface for container bootstrappers
/// </summary>
public interface IContainerBootstrapper
{
    /// <summary>
    /// Get the configuration for the container
    /// </summary>
    /// <returns>The configuration parameters</returns>
    object[] GetConfiguration();
}

It has a single method, which gets some configuration for the container. Because we don't know exactly how this configuration is going to manifest itself, we use a return value that gives us considerable flexibility – an array of objects (rather like a params argument on a method).

It is then up to the container implementation to itself to deal with whatever it got back from the bootstrapper. Let's add a method to the container interface to do that.

/// <summary>
/// An interface for a service container
/// </summary>
public interface IServiceContainer : IDisposable
{
    /// <summary>
    /// Configure the container with a bootstrapper
    /// </summary>
    /// <param name="bootstrapper">An instance of a container bootstrapper</param>
    void Configure(IContainerBootstrapper bootstrapper);
    // ...
}

Finally, we'll create a static service locator class. This isn't just for when we do need to get at the container and resolve something dynamically, but to give us a simple façade over our startup and shutdown APIs.

/// <summary>
/// Provides an application-global service locator
/// </summary>
public static class ApplicationServiceLocator
{
    public static IServiceContainer Container { get; private set; }
    /// <summary>
    /// Configure the application service locator with a container
    /// </summary>
    /// <param name="container">The container</param>
    /// <param name="bootstrapper">The bootstrapper</param>
    /// <remarks>
    /// Call this during application startup
    /// </remarks>
    public static void Initialize(IServiceContainer container, IContainerBootstrapper bootstrapper)
    {
        if (Container != null)
        {
            throw new InvalidOperationException(ExceptionMessages.ApplicationServiceLocatorTheContainerHasAlreadyBeenInitialized);
        }
        Container = container;
        Container.Configure(bootstrapper);
    }
    /// <summary>
    /// Shutdown the application service locator and free its resources
    /// </summary>
    /// <remarks>
    /// Call this during application exit
    /// </remarks>
    public static void Shutdown()
    {
        if (Container == null)
        {
            throw new InvalidOperationException(
                ExceptionMessages.ApplicationServiceLocatorTheContainerHasNotBeenInitialized);
        }
        Container.Dispose();
        Container = null;
    }
}
Programming C# 10 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

You'll notice that we've also added some protection against multiply starting-up or shutting down the container.

We can now take the code from our previous articles, and implement these interfaces using those techniques.

Here's the MEF bootstrapper.

/// <summary>
/// A default MEF-based Windsor bootstrapper
/// </summary>
/// <remarks>
/// This bootstrapper finds all the <see cref="IWindsorInstaller"/> entities
/// exported from assemblies in the specified catalog. If you do not provide
/// a specific catalog, then it will bootstrap using the default catalog of
/// local dlls, dlls in the plugins folder and the main application assembly.
/// </remarks>
public class MefWindsorBootstrapper : IContainerBootstrapper
{
    private readonly ComposablePartCatalog catalog;
    /// <summary>
    /// Constructs a new instance of a <see cref="MefWindsorBootstrapper"/>.
    /// </summary>
    /// <remarks>
    /// This will bootstrap using the default catalog of local dlls, dlls in the
    /// Plugins folder and the main application assembly.
    /// </remarks>
    public MefWindsorBootstrapper()
    {
        this.catalog = CreateDefaultCatalog();
    }
    /// <summary>
    /// Constructs a new instance of a <see cref="MefWindsorBootstrapper"/>.
    /// </summary>
    /// <param name="catalog">The part catalog to use for composition</param>
    public MefWindsorBootstrapper(ComposablePartCatalog catalog)
    {
        if (catalog == null)
        {
            throw new ArgumentNullException("catalog");
        }
        this.catalog = catalog;
    }
    /// <summary>
    /// Get the configuration for a Windsor container using the
    /// MEF part catalog
    /// </summary>
    /// <returns>The configuration parameters for the container</returns>
    public object[] GetConfiguration()
    {
        using (var compositionContainer = new CompositionContainer(this.catalog))
        {
            var installers =
                (from export in compositionContainer.GetExports<IWindsorInstaller>() select export.Value).ToArray();
            return installers;
        }
    }
    private static AggregateCatalog CreateDefaultCatalog()
    {
#if !SILVERLIGHT
        var localAssemblies = new DirectoryCatalog(".", "*.dll");
#else
        var localAssemblies = new DeploymentCatalog();
#endif
        return new AggregateCatalog(localAssemblies);
    }
}

And here's the Windsor service container.

/// <summary>
/// A default MEF-based Windsor bootstrapper
/// </summary>
/// <remarks>
/// This bootstrapper finds all the <see cref="IWindsorInstaller"/> entities
/// exported from assemblies in the specified catalog. If you do not provide
/// a specific catalog, then it will bootstrap using the default catalog of
/// local dlls, dlls in the plugins folder and the main application assembly.
/// </remarks>
public class MefWindsorBootstrapper : IContainerBootstrapper
{
    private readonly ComposablePartCatalog catalog;
    /// <summary>
    /// Constructs a new instance of a <see cref="MefWindsorBootstrapper"/>.
    /// </summary>
    /// <remarks>
    /// This will bootstrap using the default catalog of local dlls, dlls in the
    /// Plugins folder and the main application assembly.
    /// </remarks>
    public MefWindsorBootstrapper()
    {
        this.catalog = CreateDefaultCatalog();
    }
    /// <summary>
    /// Constructs a new instance of a <see cref="MefWindsorBootstrapper"/>.
    /// </summary>
    /// <param name="catalog">The part catalog to use for composition</param>
    public MefWindsorBootstrapper(ComposablePartCatalog catalog)
    {
        if (catalog == null)
        {
            throw new ArgumentNullException("catalog");
        }
        this.catalog = catalog;
    }
    /// <summary>
    /// Get the configuration for a Windsor container using the
    /// MEF part catalog
    /// </summary>
    /// <returns>The configuration parameters for the container</returns>
    public object[] GetConfiguration()
    {
        using (var compositionContainer = new CompositionContainer(this.catalog))
        {
            var installers =
                (from export in compositionContainer.GetExports<IWindsorInstaller>() select export.Value).ToArray();
            return installers;
        }
    }
    private static AggregateCatalog CreateDefaultCatalog()
    {
#if !SILVERLIGHT
        var localAssemblies = new DirectoryCatalog(".", "*.dll");
#else
        var localAssemblies = new DeploymentCatalog();
#endif
        return new AggregateCatalog(localAssemblies);
    }
}
And here’s the Windsor service container.

/// <summary>
/// An implementation of the service container that uses the
/// windsor container as its underlying implementation.
/// </summary>
public sealed class WindsorServiceContainer : IServiceContainer
{
    private readonly WindsorContainer container = new WindsorContainer();
    /// <summary>
    /// Gets the internal windsor container
    /// </summary>
    /// <remarks>
    /// This is exposed for internal test purposes, and should not normally be used
    /// </remarks>
    public IWindsorContainer WindsorContainer
    {
        get { return this.container; }
    }
    /// <summary>
    /// Configure using a bootstrapper
    /// </summary>
    /// <param name="bootstrapper">The container bootstrapper</param>
    /// <remarks>
    /// The bootstrapper must provide us with an array of <see cref="IWindsorInstaller"/> to
    /// configure the container correctly.
    /// </remarks>
    public void Configure(IContainerBootstrapper bootstrapper)
    {
        if (bootstrapper == null)
        {
            throw new ArgumentNullException("bootstrapper");
        }
        this.Configure(bootstrapper.GetConfiguration());
    }
    /// <summary>
    /// Resolve a component from the container
    /// </summary>
    /// <typeparam name="T">The type of the component to resolve</typeparam>
    /// <returns>The resolved component, or null if no component is found</returns>
    public T Resolve<T>()
    {
        try
        {
            return this.container.Resolve<T>();
        }
        catch (ComponentNotFoundException e)
        {
            throw new Core.Exceptions.ComponentNotFoundException(e.Message, e);
        }
    }
    /// <summary>
    /// Resolve a collection of components from the container
    /// </summary>
    /// <typeparam name="T">The type of the component(s) to resolve</typeparam>
    /// <returns>An enumerable of the components found that are registered against the specified type</returns>
    public IEnumerable<T> ResolveAll<T>()
    {
        return this.container.ResolveAll<T>();
    }
    /// <summary>
    /// Resolve a tagged component from the container
    /// </summary>
    /// <param name="tag">The tag for the component</param>
    /// <typeparam name="T">The type of the component to resolve</typeparam>
    /// <returns>The object corresponding to the tag, or null if no tag exists</returns>
    public T Resolve<T>(string tag)
    {
        try
        {
            return this.container.Resolve<T>(tag);
        }
        catch (ComponentNotFoundException e)
        {
            throw new Core.Exceptions.ComponentNotFoundException(e.Message, e);
        }
    }
    /// <summary>
    /// Resolve a component from the container
    /// </summary>
    /// <typeparam name="T">The return type for the component to resolve</typeparam>
    /// <param name="type">The type of the component to resolve</param>
    /// <returns>The resolved component, or null if no component is found</returns>
    public T Resolve<T>(Type type)
    {
        try
        {
            return (T)this.container.Resolve(type);
        }
        catch (ComponentNotFoundException e)
        {
            throw new Core.Exceptions.ComponentNotFoundException(e.Message, e);
        }
    }
    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public void Dispose()
    {
        this.container.Dispose();
    }
    private void Configure(params object[] configuration)
    {
#if !CASTLE_2_0
        this.container.Kernel.Resolver.AddSubResolver(new CollectionResolver(this.container.Kernel, true));
#else
        this.container.Kernel.Resolver.AddSubResolver(new ArrayResolver(this.container.Kernel));
#endif
        this.container.Install(configuration.Cast<IWindsorInstaller>().ToArray());
    }
}

The source code for all of this is available here.

Matthew Adams on Twitter


  1. You Aint Gonna Need It

Matthew Adams

Co-Founder

Matthew Adams

Matthew was CTO of a venture-backed technology start-up in the UK & US for 10 years, and is now the co-founder of endjin, which provides technology strategy, experience and development services to its customers who are seeking to take advantage of Microsoft Azure and the Cloud.