Skip Navigation LinksHome > View Post

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

  1. It's just easier to read
  2. 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?

 
Josh Post By Josh Twist
1:27 AM
04 Aug 2009

» Next Post: .NET Naming Conventions
« Previous Post: How to work with PropertyChanged's smelly name string

Comments are closed for this post.

Posted by FallenGameR @ 04 Aug 2009 3:22 AM
A suggestion. Implement it not with another class but with C# extensions. Prism uses them alot, e.g. IRegionManager.RegisterViewWithRegion:

public static IRegionManager RegisterViewWithRegion(this IRegionManager regionManager, string regionName, Func<object> getContentDelegate)
{
var regionViewRegistry = ServiceLocator.Current.GetInstance<IRegionViewRegistry>();
regionViewRegistry.RegisterViewWithRegion(regionName, getContentDelegate);
return regionManager;
}

Posted by Josh @ 04 Aug 2009 3:35 AM
Hi FallenGameR,

I actually mention this at the bottom of the article:

"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"

Ta

Josh

Posted by Laurent Bugnion @ 27 Sep 2009 2:37 AM
The syntax you propose is quite exactly what I have in my Messenger class (part of the MVVM Light Toolkit) V2, which I will publish these coming days. It was developed in collaboration with Glenn Block, mostly based on feedback from the Prism EventAggregator.

I will try to push a beta out today, it is implemented and tested, just need to write a smal blog post about it. I still have a little work in the installer and should be ready with V2 final very soon.

Cheers,
Laurent

Posted by Laurent Bugnion @ 27 Sep 2009 6:26 AM
Done, the MVVM Light Toolkit's Messenger V2 beta is posted at http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx.

Cheers,
Laurent

Posted by Mohamoud @ 21 Jan 2010 8:56 AM
LoooL ... love the choice of Interface name "Suckless" ... and thanks for this post.

Posted by Shimmy @ 11 May 2011 8:58 PM
I would want even less sucky, something like below where you don't have to pass an argument at all, like Subscribe<UserLoggedOutEvent>() where the event type is just used as a body class for generic distinguishing.

public interface ISuckEvenLessEventAggregator
{
void Publish<TEvent>();
void Publish<TEvent>(TEvent @event);
SubscriptionToken Subscribe<TEvent>(Action action, ThreadOption threadOption = ThreadOption.PublisherThread, bool keepSubscriberReferenceAlive = false);
SubscriptionToken Subscribe<TEvent>(Action<TEvent> action, ThreadOption threadOption = ThreadOption.PublisherThread, bool keepSubscriberReferenceAlive = false, Predicate<TEvent> filter = null);
void Unsubscribe(SubscriptionToken subscriptionToken);
void Unsubscribe<TEvent>(SubscriptionToken subscriptionToken);
void Unsubscribe<TEvent>(Action action);
void Unsubscribe<TEvent>(Action<TEvent> action);
}

© 2005 - 2014 Josh Twist - All Rights Reserved.