I think there's 3 levels of skill involved with the
Castle IoC implementation... first off you get a handle of the XML
configuration, registering components, using existing facilities...
pretty much getting away largely with cut 'n paste coding.
Then you get to the intermediate level.. writing basic facilities,
tweaks to the component model, writing your own sub dependency
resolvers or component activator, having a go with binsor configuration.
I think the third level is reserved for the Castle team alone... ;o)
This post is going to be straddling the beginner to intermediate kinda
level... which is generally all I ever reach with Castle's IoC... it's
not often you have to dig deeper day-to-day... though it's always good
to know there is a lot of untapped potential there.
So.. for today, say you have a component, like the
Base4Host, which has some explicit constructors:
public Base4Host(string appName, int port)
{
if (string.IsNullOrEmpty(appName)) throw new ArgumentNullException("appName");
if (port <= 1024) throw new ArgumentOutOfRangeException("port", "port should be greater then 1024");
_appName = appName;
_port = port;
}
public Base4Host(string appName, int port, string root)
: this(appName, port)
{
if (string.IsNullOrEmpty(root)) throw new ArgumentNullException("root");
_root = root;
}
You can register it the container easy enough, and provide values for
them in XML configuration, but what if you want to do the same programatically... generally
your first stop would be to examine the
IWindsorContainer for a
suitable overload... alas it doesn't get us far, so we dig in to the
underlying
IKernel itself... the kernel exposes some possible candidates:
void AddComponentWithExtendedProperties(String key, Type classType, IDictionary extendedProperties);
void AddComponentWithExtendedProperties(String key, Type serviceType, Type classType, IDictionary extendedProperties);
So you experiment with them, but supplying the dictionary of extended
properties does nothing... hmm... time to file a bug report? well no...
extended properties having nothing to do with satisfying parameter or
property dependencies on your component - not directly at least.
So why don't we just create the component ourselves.. and then add it to the container?
Well you can, via the
Kernel.AddComponentInstance method but
you're going to miss out on some things... for instance the startable
facility won't be "concerned" with your component, and as such if it
implements
IStartable it won't get started and stopped... Though I haven't confirmed this, I dont think the container will bother to dispose of any
IDisposable
components registered in this fashion either... the container doesn't
consider itself the owner of the component (and generally this is what
we want).
So we're going to have to get a little more intimate with the container
implementation ... so every time a component is registered in the
container a corresponding
ComponentModel is generated for the component, this basically keeps track of the:
- Components dependencies
- Constructor candidates
- Parameters (sounds like us...)
- Name, implementation type and service type.
- Lifecycle, Lifestyle...
- And some other stuff you can discover for yourself.
Now we could get heavy handed and jump into contributing to the
construction of the component model itself... but it's pretty
uncessary, we just want to tweak the end result... so we can use an
event handler on the Kernel -
ComponentModelCreated.
So here we have an implementation that solves our problems... This is
being implemented inside a facility, but you could do this anywhere...
wire it up in your custom container that's derived from
WindsorContainer maybe, obviously you want to remove the if statement for checking the Implemenation type is
Base4Host though. :)
private const string AdditionalParametersKey = "AdditionalParameters";
private void Kernel_ComponentModelCreated(Castle.Core.ComponentModel model)
{
if (model.Implementation == typeof(Base4Host))
{
if ((model.Configuration == null)
&& model.ExtendedProperties.Contains(AdditionalParametersKey))
{
Dictionary<string, object> additionalParameters = (Dictionary<string, object>)model.ExtendedProperties[AdditionalParametersKey];
foreach (string parameterName in additionalParameters.Keys)
{
model.Parameters.Add(parameterName, Convert.ToString(additionalParameters[parameterName]));
}
}
}
}
protected override void Init()
{
Kernel.ComponentModelCreated += new ComponentModelDelegate(Kernel_ComponentModelCreated);
}
Now I can register my component by doing something like this:
Dictionary<string, object> additionalParameters = new Dictionary<string, object>();
additionalParameters.Add("appName", _applicationName);
additionalParameters.Add("port", _port);
additionalParameters.Add("root", _baseDirectory);
Hashtable properties = new Hashtable();
properties.Add(AdditionalParametersKey, additionalParameters);
Kernel.AddComponentWithProperties("base4.defaultHost", typeof(Base4Host), properties);
Here we're passing all our additional parameters as a
Dictionary<string, object> inside
our
IDictionary of additional properties.... this isn't the most
elegant implementation, but this is all code internal to a single
facility so it's not really important to me... and it gets the job done just fine.