My Event Aggregator looks like this:
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.
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.
Sending a message is as simple as calling the IEventAggregator.SendMessage() method:
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:
The Event Aggregator itself just opens up a constructor argument for SynchronizationContext like so:
Finally, when the EventAggregator class is publishing events to subscribers it uses its SynchronizationContext to marshal the callback:
Unit testing interactions with the Event Aggregator is very simple because you only need to assert calls to SendMessage():
As I said before, all subscribers need to implement this interface for each message type they need to receive:
Which results in methods like this:
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:
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:
The “CallOnEach<T>(Action<T>)” method is a small extension method I’ve started using across projects:
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:
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:
“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:
Inside StoryTeller is a Presenter class called QueuePresenter that listens for the 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):
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:
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.