Wednesday, May 09, 2007

Exploring Castle Windsor/MicroKernel Auto-wiring

Castle Windsor/MicroKernel is a great tool for dependency injection. For most applications, the auto-wiring features of MicroKernel will perfectly for the your situation out-of-the-box. However, imagine a scenario where the dependencies of some components are services defined by an interface, while the dependencies of other components depend not on the interface, but rather on the specific implementation. Let's explore how we can get MicroKernel to help us out. So let's say we have this interface:

    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.

No comments: