In my last post I did a braindump on the Event Aggregator pattern, and me being me, I was critical of the Prism approach for what I feel is needless complexity of usage. Just to even the score and allow you to make fun of my code, here is a brief synopsis of my approach to the Event Aggregator pattern in the StoryTeller codebase. I’m banging this out way fast, so feel free to ping me in the comments about anything that isn’t clear. I’m going to blog tomorrow night on a small Event Aggregator strategy for client side JavaScript and if anybody wants it, I’ll also talk about writing diagnostics for the StoryTeller Event Aggregator.
My Event Aggregator looks like this:
public interface IEventAggregator
{
// Sending messages
void SendMessage<T>(T message);
void SendMessage<T>() where T : new();
// This method sounded cool, but has been somewhat awkward
// in real usage
void SendMessage<T>(Action<T> action) where T : class;
// Explicit registration
void AddListener(object listener);
void RemoveListener(object listener);
// Filtered registration, experimental
If<T> If<T>(Func<T, bool> filter);
}
The key points about my design is that I:
- Use generic marker interfaces to call and register the subscribers
- There is no “Event/Message” pair like Prism, just little “Message” objects
- All wiring is done through an IoC container (StructureMap)
- The subscriber registration is done by convention by my IoC container
- At this point I depend on explicit calls to remove listeners instead of using WeakReferences in the Event Aggregator. I have not made up my mind how this is going to end up yet.
Publishers
Classes that need to publish messages simply need to get a reference to the IEventAggregator singleton. In my world order that’s done strictly through Constructor Injection.
public TestEngine(IEventAggregator events, IUserInterfaceTestObserver observer)
Sending a message is as simple as calling the IEventAggregator.SendMessage() method:
public virtual void Execute(Test test)
{
_events.SendMessage(new TestRunEvent(test, TestState.Executing));
_currentTest = test;
_engine.RunTest(test);
_currentTest = null;
_events.SendMessage(new TestRunEvent(test, TestState.NotQueued){Completed = true});
}
Thread Marshalling
Internally, I let the EventAggregator class itself handle all thread marshalling between the UI thread and background threads. Today, publishing to the EventAggregator happens on the same thread as the caller, but the Event Aggregator just assumes that messages come in on a background thread and uses SynchronizationContext to marshal the call back to the UI thread. I register SynchronizationContext with StructureMap like this inside a StructureMap Registry:
ForSingletonOf<SynchronizationContext>().TheDefault.Is.ConstructedBy(() =>
{
if (SynchronizationContext.Current == null)
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
}
return SynchronizationContext.Current;
});
The Event Aggregator itself just opens up a constructor argument for SynchronizationContext like so:
public EventAggregator(SynchronizationContext context)
{
_context = context;
}
Finally, when the EventAggregator class is publishing events to subscribers it uses its SynchronizationContext to marshal the callback:
public void SendMessage<T>(Action<T> action) where T : class
{
sendAction(() => all().Each(x => x.CallOn(action)));
}
public void SendMessage<T>(T message)
{
sendAction(() => all().CallOnEach<IListener<T>>(x => x.Handle(message)));
}
protected virtual void sendAction(Action action)
{
// Uses SynchronizationContext to marshal the call
// to the UI thread
_context.Send(state => { action(); }, null);
}
Unit testing interactions with the Event Aggregator is very simple because you only need to assert calls to SendMessage():
[Test]
public void send_cancellation_message_if_the_test_is_queued()
{
MockFor<IEventAggregator>().AssertWasCalled(
x => x.SendMessage(new TestRunEvent(theTest, TestState.NotQueued)));
}
Subscribers
As I said before, all subscribers need to implement this interface for each message type they need to receive:
public interface IListener<T>
{
void Handle(T message);
}
Which results in methods like this:
public class FixtureExplorer : IListener<FixtureLibraryLoaded>, IStartable
{
public void Handle(FixtureLibraryLoaded message)
{
TreeNode node = BuildTree(message.Library);
_view.ApplyFixtureNode(node);
_container.Inject(message.Library);
}
}
Internally, the EventAggregator just stores all of the subscribers in a single List (StoryTeller isn’t big enough that I’m worried about the performance hit from scanning all subscribers). When an event is published like this to the EventAggregator:
public void ReloadFixtureLibrary()
{
_library = _proxy.BuildFixtureLibrary();
_events.SendMessage(new FixtureLibraryLoaded(_library));
}
The EventAggregator receives a “FixtureLibraryLoaded” messsage, then it loops through all of its subscribers and tries to case each object to IHandler<FixtureLibraryLoaded>. If the object can be cast to that interface, EventAggregator will call the Handle(FixtureLibraryLoaded) method on the subscriber. That code looks like this below:
public void SendMessage<T>(T message)
{
// all() is just a readonly IEnumerable<object> snapshot
// of the list of subscribers
// sendAction(Action) is just making the Action run through
// SynchronizationContext
sendAction(() => all().CallOnEach<IListener<T>>(x => x.Handle(message)));
}
The “CallOnEach<T>(Action<T>)” method is a small extension method I’ve started using across projects:
public static void CallOn<T>(this object target, Action<T> action) where T : class
{
var subject = target as T;
if (subject != null)
{
action(subject);
}
}
public static void CallOnEach<T>(this IEnumerable enumerable, Action<T> action) where T : class
{
foreach (object o in enumerable)
{
o.CallOn(action);
}
}
Convention Based Registration
As I said earlier, all subscribers have to implement the IListener<T> interface to “plug” into the Event Aggregator. I’m very aggressive about using my IoC container (StructureMap of course) to “compose” the system. Every “service,” Presenter, or element of the user interface that would implement the IListener<T> interface is constructed/resolved by StructureMap. The event aggregator itself is a “managed singleton” in the container:
For<IEventAggregator>().AsSingletons().Use<EventAggregator>();
StructureMap “knows” about the Event Aggregator and creates all the objects in the system that would implement IListener<T>. Wouldn’t it be convenient if StructureMap could just automatically add the subscribers to the Event Aggregator as it creates the objects? That turns out to be relatively simple to do with the “TypeInterceptor” mechanism in StructureMap. I built a simple TypeInterceptor that automatically adds any object created by StructureMap to the Event Aggregator before the new object is returned to the caller. This EventAggregatorInterceptor class is shown below:
public class EventAggregatorInterceptor : TypeInterceptor
{
public object Process(object target, IContext context)
{
context.GetInstance<IEventAggregator>().AddListener(target);
return target;
}
public bool MatchesType(Type type)
{
return
type.ImplementsInterfaceTemplate(typeof (IListener<>)) ||
type.CanBeCastTo(typeof (ICloseable));
}
}
“TypeInterceptor” is a StructureMap interface, and “ImplementsInterfaceTemplate()” and “CanBeCastTo()” are extension methods on the Type class provided by StructureMap as convenience methods. Next, I need to add the EventAggregatorInterceptor to StructureMap with this little bit of code in the bootstrapper:
RegisterInterceptor(new EventAggregatorInterceptor());
Inside StoryTeller is a Presenter class called QueuePresenter that listens for the TestRunEvent:
public class QueuePresenter : IScreen, IListener<TestRunEvent>
When the user interface screen conducting code makes a call to “container.GetInstance<QueuePresenter>(), StructureMap will invoke the EventAggregatorInterceptor.Process() method and register the new instance of QueuePresenter with the Event Aggregator.
The value of the convention based registration approach is less code and fewer errors. The registration happens automatically, so I don’t get errors from forgetting to register an event. Classes that are strictly subscribers do not need to be coupled to the Event Aggregator at all. When I unit test the Queue Presenter class above I can simply walk right up to the Handle(TestRunEvent):
[TestFixture]
public class when_a_new_test_is_queued : QueuePresenterContext
{
private object theItem;
protected override void setUp()
{
MockFor<IExecutionQueue>().Expect(x => x.GetAllQueuedTests()).Return(DataMother.TestArray(3));
theItem = new object();
MockFor<IQueuedItemFactory>().Expect(x => x.Build(theTest)).Return(theItem);
ClassUnderTest.Handle(new TestRunEvent(theTest, TestState.Queued));
}
[Test]
public void hide_the_no_tests_label()
{
theView.AssertWasCalled(x => x.NoTestsAreQueued = false);
}
[Test]
public void the_test_should_be_added_to_the_view()
{
theView.AssertWasCalled(x => x.AddTestItem(theItem));
}
}
Note that there’s no reference to Event Aggregator in the unit testing code above.
What about Filtering?
So far I’ve only got one place where I need to worry about event filtering and I do it inside the subscriber. My “TestPresenter” class only cares about TestRunEvent messages that relate to a particular Test object. In this case I’m simply filtering inside TestPresenter.Handle(TestRunEvent) like this:
public void Handle(TestRunEvent message)
{
// Filter the message here and disregard messages not related to *my* Test
if (message.Test != _test) return;
// Continue on with processing
}
When you need event filtering, I’m finding it much easier to use coarse grained events rather than finer grained events.
That’s about it, there really isn’t that much to it. Fire away with questions. The entire code for this is in the StoryTeller codebase that’s available without a tigris login.