Component discovery and composition Part 1c: Fundamentals – Castle.Windsor
Combining MEF With Castle.Windsor For Low-Ceremony Component Composition
Last time, we looked at MEF and what it brings the to party. (The answer was a very powerful composition solution.) This time, we're going to contrast that with the facilities offered by Castle.Windsor.
Part of the solution — Castle.Windsor
Windsor takes as its primary focus the component container problem. Its usage model, rather than import and export, is based on registration and resolution. Although similar in effect, they are quite different in approach.
To make a component available in the container we call Register(). Here's an example registering a component called RestStarterKitHttpClient for a service contract defined by the IHttpClient interface. In this case, we're explicitly stating that we want a new instance every time by specifying a transient lifetime. (We'll talk more about lifetimes and initialization in another post.)
In order to get hold of the component concerned, I can explicitly call Resolve() Here's an example of that.
Compare this to our non-IOC equivalent:
So you can think of Resolve() as the IOC equivalent of new. They both provide us with instances of an object, but there's an extra level of indirection which gives us more control over how we construct and return it. Rather than always allocating a brand new instance, it might be a singleton, shared amongst all clients, or something from a pool, for example. It might be a "real" instance (like the RestStarterKitHttpClient) or a mock for testing. The decision is taken at registration time, rather than instantiation time (hence the term Inversion of Control).
Just as with MEF composition, it also cascades component resolution through the dependencies of the resolved items. Where it differs is that, rather than requiring explicit importing attributes to indicate which dependencies should be resolved by the container, it does this by convention.
What does that mean in practice? If my RestStarterKitHttpClient needs some other component to do its job (an ICacheManager, for instance), then I can just extend my plain-old-CLR class like this.
When you Resolve() the IHttpClient, it will look at the RestStarterKitHttpClient component, discover this non-default constructor, look in the container for a component implementing the ICacheManager interface, find it (providing we registered it, of course!) and inject it into the constructor. A very similar pattern to MEF (compare our FireTruck with our RestStarterKitHttpClient), but it leaves our code much cleaner than the equivalent in our MEF sample.
The problem is that we've completely lost the dynamic component discovery that MEF gave us; we're explicitly registering a concrete component in the container. We'd like a better solution.
And that will be the subject of the next post.
What would happen if we hadn't registered the component? Because we have no default constructor it can fall back to, Windsor would throw a ComponentNotFoundException, and the call to Resolve() would fail.