Simple message-based Event Aggregator

This blog post is more about implementation than in-depth description or background information. It’s covering an implementation of a simple Event Aggregator that I’ve developed and used in a non-trivial Silverlight project and which I found quite useful. A word of warning: It’s not fully fledged and not suited for all scenarios… and it’s not intended to be.

(Little) background

Event broker linkage, Copyright: Matthias JauernigThe first time I’ve seen an implementation of the event aggregator pattern was in the Prism framework. I like the idea behind this pattern. It’s decoupling event publishers and subscribers. It’s representing a kind of Hub. Each time a publisher publishs an event it gets into the hub and then it’s redirected to the subscribers. The publishers/subscribers don’t have to know each other, they just have to know the concrete event aggregator which is a mediator and mediates between both parties. Thus the event aggregator is realizing a useful indirection mechanism.

The event aggregator can be used in many scenarios and situations. I’ve mainly used it in UI-related scenarios, but it’s not limited to that. In the UI situation it helped me out e.g. at view synchronization and indirect communication: between multiple user controls/views, views and view models (both directions) or views and controllers. They don’t have to know each other and thus can be loosely coupled.

While I came across with the Prism event aggregator at first, after some investigation I didn’t like the implementation very much in view of the usage from a client’s perspective. You first have to get an event from the event aggregator and then you can subscribe to this event or publish the event with some event arguments. This is kind of duplicated work. When I know the type of the event arguments why do I have to know the event anymore (under the assumption that there’s a 1:1 relationship between both)? Others came across with this issue as well. A better approach in my opinion is a solely message-based event aggregator. A system that doesn’t distinguish between events and event arguments, but is based on messages people can subscribe to and publish.

Implementation

Let’s come to the bits’n’bytes of my implementatoin. My message-based event aggregator should be able to handle messages of type IMessage. This is just an empty interface for type-correctness in the other components. Additionally it makes the message type explicit which I like because a message should be a very specific type of your application domain:

public interface IMessage { }

Another important component is the ISubscription<TMessage> interface and its default implementation Subscription<TMessage>. A subscription is something the event aggregator stores internally when an action should be subscribed to a message. This subscription process results in an ISubscription<TMessage> object, which is returned to the caller. The caller will be able to unsubscribe from the event aggregator with this subscription object – there’s no need to reference the subscribed action, which is handy e.g. in situations where you want to use anonymous methods (via delegates or lambdas). Furthermore when the subscription disposes it’s unsubscribed from the event aggregator, which I found quite useful:

public interface ISubscription<TMessage> : IDisposable
    where TMessage : IMessage
{
    Action<TMessage> Action { get; }
    IEventAggregator EventAggregator { get; }
}

public class Subscription<TMessage> : ISubscription<TMessage>
    where TMessage : IMessage
{
    public Action<TMessage> Action { get; private set; }
    public IEventAggregator EventAggregator { get; private set; }

    public Subscription(IEventAggregator eventAggregator, Action<TMessage> action)
    {
        if(eventAggregator == null) throw new ArgumentNullException("eventAggregator");
        if(action == null) throw new ArgumentNullException("action");

        EventAggregator = eventAggregator;
        Action = action;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if(disposing)
            EventAggregator.UnSubscribe(this);
    }
}

Note the reference to EventAggregator in the interface and its use in the implementation. This is necessary due to the disposing functionality. Of course if you don’t want this behavior in your scenario you can adapt the implementation.

At the end the IEventAggregator interface and its default implementation EventAggregator handle the whole message publish/subscribe mechanism. Clients can Subscribe() to specific types of messages with custom actions that are stored in an ISubscription<TMessage> object. Those clients can UnSubscribe() if they’re owning the ISubscription<TMessage> object. Other clients can Publish() concrete messages, which gets the subscribers of the message notified:

public interface IEventAggregator
{
    void Publish<TMessage>(TMessage message)
        where TMessage : IMessage;

    ISubscription<TMessage> Subscribe<TMessage>(Action<TMessage> action)
        where TMessage : IMessage;

    void UnSubscribe<TMessage>(ISubscription<TMessage> subscription)
        where TMessage : IMessage;

    void ClearAllSubscriptions();
    void ClearAllSubscriptions(Type[] exceptMessages);
}

public class EventAggregator : IEventAggregator
{
    private readonly IDictionary<Type, IList> _subscriptions = new Dictionary<Type, IList>();

    public void Publish<TMessage>(TMessage message)
        where TMessage : IMessage
    {
        if(message == null) throw new ArgumentNullException("message");

        Type messageType = typeof(TMessage);
        if(_subscriptions.ContainsKey(messageType))
        {
            var subscriptionList = new List<ISubscription<TMessage>>(
                _subscriptions[messageType].Cast<ISubscription<TMessage>>());
            foreach(var subscription in subscriptionList)
                subscription.Action(message);
        }
    }

    public ISubscription<TMessage> Subscribe<TMessage>(Action<TMessage> action)
        where TMessage : IMessage
    {
        Type messageType = typeof(TMessage);
        var subscription = new Subscription<TMessage>(this, action);

        if(_subscriptions.ContainsKey(messageType))
            _subscriptions[messageType].Add(subscription);
        else
            _subscriptions.Add(messageType, new List<ISubscription<TMessage>>{subscription});

        return subscription;
    }

    public void UnSubscribe<TMessage>(ISubscription<TMessage> subscription)
        where TMessage : IMessage
    {
        Type messageType = typeof(TMessage);
        if (_subscriptions.ContainsKey(messageType))
            _subscriptions[messageType].Remove(subscription);
    }

    public void ClearAllSubscriptions()
    {
        ClearAllSubscriptions(null);
    }

    public void ClearAllSubscriptions(Type[] exceptMessages)
    {
        foreach (var messageSubscriptions in new Dictionary<Type, IList>(_subscriptions))
        {
            bool canDelete = true;
            if (exceptMessages != null)
                canDelete = !exceptMessages.Contains(messageSubscriptions.Key);

            if (canDelete)
                _subscriptions.Remove(messageSubscriptions);
        }
    }
}

Usage

The usage of this event aggregator implementation is simple and straight forward. Clients can subscribe to messages they’re interested in:

// Option 1: Explicit action subscription
Action<MyMessage> someAction = message => { /*...*/ };
var subscription1 = eventAggregator.Subscribe(someAction);

// Option 2: Subscription via lambda
var subscription2 = eventAggregator.Subscribe<MyMessage>(message => { /*...*/ });

The clients get an ISubscription<TMessage> in return, from which they’re able to unsubscribe:

// Option 1: Unsubscribe by calling the event aggregator method
eventAggregator.UnSubscribe(subscription);

// Option 2: Unsubscribe by calling Dispose() on the subscription object
subscription.Dispose();

Other clients now are able to publish concrete messages and subscribers get informed about those messages:

eventAggregator.Publish(new MyMessage{ /*...*/ });

How to get an instance of IEventAggregator, you may ask? Well, that’s your decision! Implement a singleton for accessing an instance, use your favorite DI container, whatever…

Conclusion

That’s it. A simple message-based event aggregator implementation that can be used in a variety of situations. And which can be replaced by other implementations as well. Perhaps you want to persist or log messages, enable detached subscribers, allow async event processing or even further functionality like load balancing… It’s up to you to provide your own implementation. And feel free to connect the Latch Pattern 😉

While the presented EventAggregator perfectly fitted my needs, it’s not intended to be universally applicable. For example I know that Prism uses WeakReferences to simplify garbage collection. I say it again: feel free to do that in your own implementation. Besides there are many more syntactic ways to implement event aggregators/brokers. Paste your comments if you have further suggestions – you’re welcome!

kick it on DotNetKicks.com