Sunday, January 17, 2010

10 Advanced Windsor Tricks – 6. The Event Wiring Facility

Here’s part six of (at least) 10 Advanced Windsor Tricks.

C# has had events from version 1.0. Events effectively implement the observer pattern (another classic from the Gang of Four book) by maintaining the publisher’s collection of subscribed observers for you. The Windsor event wiring facility lets you register subscribers for an event using Windsor configuration. It means you can leave the actual wiring up of subscriber delegates to publisher events, to the facility.

Let’s look at an example. First here’s a simple event publisher:

public class MessagePublisher : IStartable
{
    public event Action<string> MessagePublished;

    public void PublishMessage(string message)
    {
        if (MessagePublished != null)
        {
            MessagePublished(message);
        }
    }

    public void Start(){}
    public void Stop(){}
}

It has a single event ‘MessagePublished’ that simply publishes a string. It also implements IStartable which means it will be started as soon as possible during registration time. Implementing IStartable also means that the ‘Start’ method will be called, which is an excellent opportunity for a more complex publisher to do any initialisation. If you haven’t read my pervious trick ‘The Startable Facility’ I’ll wait now while you go and have a look :)

Any class that wishes to subscribe to ‘MessagePublished’ simply needs to supply a method that takes a single string argument and returns void. Here’s an example:

public class MessageListener
{
    public void OnMessagePublished(string message)
    {
        Console.WriteLine("MessageListener got message: '{0}'", message);
    }
}

If we weren’t interested in using an IoC container we would wire up the listener to the publisher by creating instances of each and using the += operator to do the subscription:

var publisher = new MessagePublisher();
var subscriber = new MessageListener();

publisher.MessagePublished += subscriber.OnMessagePublished;
publisher.MessagePublished += message => Console.WriteLine("Me too {0}", message);

publisher.PublishMessage("Hello World!");

I’ve also subscribed a simple lambda expression too, just to show that you can add as many subscribers as you wish. This example will print out:

MessageListener got message: 'Hello World!'
Me too Hello World!

Now let’s see how we would do the same thing with Windsor and the event wiring facility:

var container = new WindsorContainer()
    .AddFacility<EventWiringFacility>()
    .Register(
        Component.For<MessagePublisher>()
            .Configuration(
                Child.ForName("subscribers").Eq(
                    Child.ForName("subscriber").Eq(
                        Attrib.ForName("id").Eq("messageListener"),
                        Attrib.ForName("event").Eq("MessagePublished"),
                        Attrib.ForName("handler").Eq("OnMessagePublished")
                        )
                    )
                ),
        Component.For<MessageListener>().Named("messageListener")
    );


var publisher = container.Resolve<MessagePublisher>();
publisher.PublishMessage("Hello World!");

First we add the EventWiringFacility itself, then we register the publisher and subscriber. The core thing to get right is the configuration of the publisher. The EventWiringFacility looks for any component with a child configuration element of ‘subscribers’ and then adds it to its list of publishers. When the publisher instance is created the facility loops through its subscribers collection, resolves each in turn and wires up the configured events to the configured handlers. The ultimate effect of this code is exactly like the non-container code above (excepting the lambda registration of course), with the MessageListener’s OnMessagePublished method subscribed to MessagePublisher’s MessagePublished event.

Note that making a component a subscriber of a publishing component is effectively the same as making it a dependency. A subscriber instance will be resolved (and created if it’s not an already existing singleton) when the publisher is created and will live as long as the publisher. Remember that in the example above, the publisher implements IStartable which means that was created before it was resolved and we are simply grabbing the existing instance so that we can call PublishMessage on it.

OK, so we can all agree the configuration is pretty ugly and could do with some fluent registration love. Colin Ramsay has an interesting blog post where he shows a fluent interpretation of the configuration. You’d probably want to do something similar if you are planning to be a heavy user of the EventWiringFacility.

For myself I’d like to see something a little more convention based. It would be easy for a facility to record any component with a public event and also record the event delegate type. Then any subscribers could simply announce that they wanted to subscribe to event type x with method y. It could include an optional event name to disambiguate all those EventHandler(object o, EventArgs e) events.

1 comment:

  1. I like the convention based approach. Plus I agree that current way of working with facility is far from ideal, I already created a suggestion here: http://castle.uservoice.com/forums/16605-official-castle-project-feedback-forum/suggestions/450208-ioc-expose-high-level-fluent-api-for-event-wirin?ref=title

    ReplyDelete

Note: only a member of this blog may post a comment.