A Suck Less Event Aggregator for Prism?
I heard on twitter the other day (Yes, I now tweet occasionally. If you'd like to waste literally seconds of your day you can follow me at joshtwist) that some folks at an Alt.Net UK event where giving P&P and Prism a rough ride. Specifically, they had some issues with the EventAggregator - citing Jeremy Miller's criticisms in this post Braindump on the Event Aggregator Pattern.
I need to say up front that I really like the EventAggregator in Prism - it's one of my favourite bits. However, I have to agree with some of the feedback. I've always found it odd that I have to go to the EventAggregator and ask for an 'event' object. Especially because the thing I get back isn't an event, but the 'bus' through which events travel.
CustomerSelectedEvent cse = eventAggregator.GetEvent<CustomerSelectedEvent>();
cse.Subscribe(c => CustomerHasBeenSelected(c));
// meanwhile, elsewhere...
CustomerSelectedEvent cse = eventAggregator.GetEvent<CustomerSelectedEvent>();
cse.Publish(customer);
However, once I've learned the API I've never found it obstructive. Nonetheless - I wondered, how hard would it be to create a wrapper that makes the Event Aggregator look something like you might expect it to look?
public interface ISuckLessEventAggregator
{
void SendMessage<T>(T message);
void Subscribe<T>(Action<T> action);
void Subscribe<T>(Action<T> action, bool keepSubscriberReferenceAlive);
void Subscribe<T>(Action<T> action, Microsoft.Practices.Composite.Presentation.Events.ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<T> where);
void Unsubscribe<T>(Action<T> action);
}
The interface would look something like that I guess. And it would be used like so.
eventAggregator.Subscribe<Customer>(c => CustomerHasBeenSelected(c));
// meanwhile, elsewhere...
eventAggregator.Send(customer);
That certainly feels a bit better. However, there's no 'topic' described so this feels like a poor usage of the new interface as I can't distinguish Customer *selection* from other events involving the Customer type. Maybe this is better:
public class CustomerSelectedEvent
{
public Customer Customer { get; set; }
}
// elsewhere
eventAggregator.Subscribe<CustomerSelectedEvent>(c => CustomerHasBeenSelected(c.Customer));
// meanwhile, elsewhere...
eventAggregator.Send(new CustomerSelectedEvent { Customer = customer} );
Now I prefer this over the existing pattern for a number of reasons
- It's just easier to read
- The 'event' class, CustomerSelectedEvent, is a POCO - no dependencies on the Prism pieces.
And how might this implementation look? Here's a first shot:
using System;
using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Presentation.Events;
public class SuckLessEventAggregator : ISuckLessEventAggregator
{
private readonly IEventAggregator _eventAggregator;
public SuckLessEventAggregator(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
private CompositePresentationEvent<T> GetEventBus<T>()
{
var bus = _eventAggregator.GetEvent<CompositePresentationEvent<T>>();
return bus;
}
public void SendMessage<T>(T message)
{
var bus = GetEventBus<T>();
bus.Publish(message);
}
public void Subscribe<T>(Action<T> action)
{
var bus = GetEventBus<T>();
bus.Subscribe(action);
}
public void Subscribe<T>(Action<T> action, bool keepSubscriberReferenceAlive)
{
var bus = GetEventBus<T>();
bus.Subscribe(action, keepSubscriberReferenceAlive);
}
public void Subscribe<T>(Action<T> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<T> where)
{
var bus = GetEventBus<T>();
bus.Subscribe(action, threadOption, keepSubscriberReferenceAlive, where);
}
public void Unusbscribe<T>(Action<T> action)
{
var bus = GetEventBus<T>();
bus.Unsubscribe(action);
}
// You'd probably have more overloads but I'll wait for C# 4's Optional keyword :)
}
Does this feel better?
I realise that it doesn't address all of Jeremy's concerns (it's just the result of a short train journey's work so far). However, some of them I just don't agree with.
For example:
"2. The listeners should have little or preferably NO/ZILCH/NADA coupling to the event aggregator."
I'd much prefer an explicit dependency on the EventAggregator than a relatively implicit one to an IoC behavior. In either case, this is more of a religious argument than a technical one.
I also realise that most of this could have been implemented in Extension Methods but I think a separate, uncluttered API would be more intuitive.
Thoughts? Any holes in my quick implementation?
Originally posted by Josh Twist on 4th of August 2009 here.