public interface ISender
{
void SendMessage(string recipient, string message);
}
and we have a couple of implementations:
public class EmailSender : ISender
{
public void SendMessage(string recipient, string message)
{
// ... send email message ...
}
}
public class InstantMessageSender : ISender
{
public void SendMessage(string recipient, string message)
{
// ... send instant message ...
}
public bool IsOnline(string recipient)
{
// ... check is user is online ...
return false;
}
}
Suppose we have a component that requires a set a
ISender
services:public class AlertSystem
{
private ISender[] _senders;
public AlertSystem(ISender[] senders)
{
_senders = senders;
}
public ISender[] Senders
{
get { return _senders; }
}
/// ... implementation ...
}
OK, so far so good. Wiring this up with Windsor is a piece of cake:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="instant.message.sender"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="email.sender"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="alert.system"
type="Eg.AlertSystem, Eg">
<parameters>
<senders>
<array type="Eg.ISender, Eg">
<item>${instant.message.sender}</item>
<item>${email.sender}</item>
</array>
</senders>
</parameters>
</component>
</components>
</configuration>
But what if I have another component that requires a specific implementation of
ISender
like this:public class InstantMessageComponent
{
private InstantMessageSender _sender;
public InstantMessageComponent(InstantMessageSender sender)
{
_sender = sender;
}
public InstantMessageSender Sender
{
get { return _sender; }
}
// ... implementation ...
}
How can we get this wired up? Well, you might think just added the new component would work:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="instant.message.sender"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="email.sender"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="alert.system"
type="Eg.AlertSystem, Eg">
<parameters>
<senders>
<array type="Eg.ISender, Eg">
<item>${instant.message.sender}</item>
<item>${email.sender}</item>
</array>
</senders>
</parameters>
</component>
<component id="instant.message.component"
type="Eg.InstantMessageComponent, Eg"/>
</components>
</configuration>
However, there is a slight problem with this. MicroKernel will not auto-wire components that have a dependency of
InstantMessageSender
because it is instead registered in the config for the service it provides, namely ISender
. In this case, I can force the kernel to wire it up using the following configuration:<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="instant.message.sender"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="email.sender"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="alert.system"
type="Eg.AlertSystem, Eg">
<parameters>
<senders>
<array type="Eg.ISender, Eg">
<item>${instant.message.sender}</item>
<item>${email.sender}</item>
</array>
</senders>
</parameters>
</component>
<component id="instant.message.component"
type="Eg.InstantMessageComponent, Eg">
<parameters>
<sender>${instant.message.sender}</sender>
</parameters>
</component>
</components>
</configuration>
This works, but it's not autowired. If you have a lot of this going on in your system, you'll find yourself taking on more and more of the responsibility of wiring your components, even though you originally wanted to leverage MicroKernel to handle much of this for you. So let's say we want our configuration to look like the first configuration I presented. Well, we're going to have to change the way MicroKernel registers services. This concern is specifically handled via the NamingSubSystem. We will do the job by extending MicroKernel's default
INamingSubSystem
implementation, DefaultNamingSubSystem
:public class CustomNamingSubSystem : DefaultNamingSubSystem
{
public override void Register(string key, IHandler handler)
{
Type implementation = handler.ComponentModel.Implementation;
if (!service2Handler.Contains(implementation))
{
this[implementation] = handler;
}
base.Register(key, handler);
}
}
In this method, all we're doing is additionally adding the implementation to resolvable services if it isn't already. The
DefaultNamingSubsystem
only adds the service and not the implementation except in cases where you don't specify a service. Also note that this method should also take care of mapping the component id to the proper component. Here's the default implementation:public virtual void Register(String key, IHandler handler)
{
Type service = handler.ComponentModel.Service;
if (key2Handler.Contains(key))
{
throw new ComponentRegistrationException(
String.Format("There is a component already registered for the given key {0}", key));
}
if (!service2Handler.Contains(service))
{
this[service] = handler;
}
this[key] = handler;
}
And here's how I got Windsor to use my new
DefaultNamingSubSystem
:public class ApplicationContainer : WindsorContainer
{
public ApplicationContainer(string xmlFile) : base(xmlFile)
{
}
protected override void RunInstaller()
{
Kernel.AddSubSystem(SubSystemConstants.NamingKey, new CustomNamingSubSystem());
base.RunInstaller();
}
}
By the way, we could have gotten this all wired up if we registered the implementations twice: once for the service it provides and another time for the implementation, like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="instant.message.sender.service"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="email.sender.service"
service="Eg.ISender, Eg"
type="Eg.InstantMessageSender, Eg"/>
<component id="alert.system"
type="Eg.AlertSystem, Eg">
<parameters>
<senders>
<array type="Eg.ISender, Eg">
<item>${instant.message.sender}</item>
<item>${email.sender}</item>
</array>
</senders>
</parameters>
</component>
<component id="instant.message.component"
type="Eg.InstantMessageComponent, Eg"/>
<component id="instant.message.sender"
type="Eg.InstantMessageSender, Eg"/>
<component id="email.sender"
type="Eg.InstantMessageSender, Eg"/>
</components>
</configuration>
I wouldn't recommend this approach though.