Skip to content
  1. We help small teams achieve big things.
  2. Our Technical Fellow just became a Microsoft MVP!
  3. We run the Azure Weekly newsletter.
  4. We just won 2 awards.
  5. We just published Programming C# 8.0 book.
  6. We run the Power BI Weekly newsletter.
  7. Our NDC London 2020 talk is now available online!
  8. We are school STEM ambassadors.
Matthew Adams By Matthew Adams Co-Founder

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 YAGNI 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.

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:

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:

or like this:

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

However, as we've said in previous instalments, 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.

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.

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.

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.

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.

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.

And here's the Windsor service container.

The source code for all of this is available here.

[1] You Aint Gonna Need It

[2] There's a good rehearsal of the arguments against this kind of "service locator" pattern here. The argument is sound, but not universal. We're going to make use of the service locator pattern in explicitly loosely-coupled scenarios where we expect that we might fail at runtime.

[3] Take a look at MefContrib to get a feel for the kind of work you'd need to do – specifically the stuff in ConventionCatalog.

Matthew Adams on Twitter

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.