Tuesday, May 26, 2009

Event Aggregator in Prism Explorer

Now that we’ve unveiled the Prism Explorer (PE) which is our demonstration of Prism and DevForce working together, I’m on the hook to write about it. I should be telling the story from the beginning and moving deliberately forward. Instead I’m jumping into the middle because someone asked me about a particular feature. Bear with me.

I first showed PE (in a slightly different version) at Tech Ed 2009 in a talk I gave on migrating an application to Prism; you can see the video of my talk here.

EventAggregator (EA) is one of the Prism components I discussed; it’s an implementation of the Event Aggregator pattern.

Event Aggregator Pattern


The EA pattern helps us decouple publishers and subscribers. You’ll want it whenever you find yourself in this pickle: your object needs to know when something has happened … but it can not know who will tell it when that thing happens. In other words, the subscriber does not know the publisher of an event it cares about. It doesn’t know if a publisher exists; there might be multiple publishers.

In everyday .NET eventing, the publisher never knows who is going to subscribe. But subscribers at least know the publisher. That’s why we can write a line such as “publisher.SomeEvent += subscriberSomeEventHandler;” No such luck in our scenario.

EA pattern addresses the problem with a little indirection. The pattern refers to an EventAggregator “service” that acts as a broker between publishers and subscribers. Both publishers and subscribers know the EA even if they don’t know about each other. A subscriber registers its interest in a particular event with the EA by handing the EA an appropriate event handler. The EA keeps track of the subscriber and its event. When a publisher wants to raise that event, it publishes to the EA. The EA accepts the published information (a parameter of the published event) and calls all of the subscribing handlers.

There is one significant detail I neglected to mention. The publishers and subscribers must jointly agree on the event itself. They may not know each other but they must have joint knowledge of the same event.

There are plenty of ways to go about this. They could agree on a string name for the event; that’s how it was done in the Composite Application UI Block.
Prism adopts a different approach. In Prism, an event is an instance of a class that derives from CompositePresentationEvent<T> where T is the type of the “event payload”. The “event payload” is the parameter the publisher included when raising the event and that the EA forwards to the subscriber event handlers. The payload type can be any type you like and is typically a custom type.

In Prism, you define the event class and its payload class in an assembly referenced by both publishers and subscribers. Prism applications tend to keep shared information of this nature in an “infrastructure” assembly. Although modules do not reference each other, they do reference this infrastructure assembly.

One last thought. When you follow the CLR eventing convention, the publisher is one of the parameters passed to the event handler. That’s why your handlers look like “void SomeHandler(object sender, EventArgs e);”.

It makes sense that the handler should know who raised the event – who the sender is; after all, the subscriber attached the handler to the publisher. The handler can cast the sender to a known type and, potentially, call back on to the publisher through its public API.

But in our decoupled world, the subscriber shouldn’t know the publisher. In principle, the subscriber would not know how to cast the sender. Accordingly, event handlers used in the EA pattern do not take a sender parameter. They only accept a payload.

And there is a corollary thought; events of this kind should be “Fire and Forget”. The publisher, in raising the event, does not know if anyone is listening and should not expect a response. A subscriber shouldn’t attempt to respond.

No one can stop you from burying the sender (the publisher) in the payload and making it available to the subscriber through an interface defined in the infrastructure assembly. I would strongly discourage such practice. It violates our expectations for the pattern and likely leads to hours of aggravation as some poor sod tries to figure out which subscriber did (or didn’t) make a call back into the publisher (which publisher?). Add multi-threading to compound the confusion. Stay away. If you must report back, consider raising another “completed” event that the publisher can subscribe to. You have been warned!

That’s your informal, breezy description of EA within the Prism world. You can learn more about the pattern by searching for “Event Aggregator”. You can go to Prism for details of the Prism implementation. The balance of this post describes how we use EA in Prism Explorer so that one module can respond to activity in another module.

Event Aggregator in Action


After launch, PE displays two tabs, both populated with an instance of the “Model Explorer View” (MEV). These views were added to the shell by the MEV module. With either MEV instance I can choose a query from a list and see the results displayed in a DataGrid. There are other features here but for present purposes we are interested in the ability to choose a query and see results in a grid.

Here is a snapshot of PE after running an Employee query



This view looks much the same as long as I run a query returning anything other than a Customer query. When I run my first Customer query, a new tab appears, “Customer Orders”



When I click on the new “Customer Orders Tab” I see a master/detail display of the first customer in the grid. Because I selected “All Customers”, the first displayed is “Alfreds …”. The “Customer Orders” view displays “Alfreds …” and its orders.



If I return to either of the “Model Explorer View” tabs, select a different customer, and then return to the “Customer Orders” tab, the newly selected customer is displayed with its orders.

What Happened


At launch, the PE Bootstrapper class loads and initializes two modules, MevModule and CustomerOrdersModule.

The MevModule displays the “Model Explorer View” in the two tabs. When the user picks a query, the query results arrive and the ModelExplorerViewModel publishes an EntityNotificationEvent to report that a new entity has become the “current entity”. As the user moves up and down among the grid rows, the ViewModel publishes an EntityNotificationEvent to the EventAggregator indicating the newly selected “current entity”.

Note that there is no query when the application launches … and therefore no entities to talk about. The ViewModel will only publish when the user selects a query and that query’s execution returns entities.

The ViewModel publishes a notification about whatever entity is newly selected. That could be a customer; it could be an employee, or order, or something else.

Now rewind to application launch. The application Bootstrapper also instantiated the CustomerOrdersModule and initialized it. We don’t know whether it did so before or after creating the MevModule … and we don’t care.

Prism called CustomerOrdersModule.Initialize() which, after some configuration, instantiates a CustomerOrdersCoordinator.

The CustomerOrdersCoordinator starts listening for notifications about Customers by subscribing (via the EA) to the EntityNotificationEvent.

The CustomerOrdersCoordinator treats every EntityNotificationEvent as an opportunity to display a Customer and its Orders. It knows which customer to display by virtue of the EntityNotificationSpecification payload that describes the entity’s type and id.

Of course not every notification will be about customers. The coordinator checks the type of entity mentioned in the event and ignores non-customers.

When the coordinator sees a customer-related event, it checks first to see if it has created a “Customer Orders View”.

If you followed my earlier post on the Coordinator “pattern”, you know that a coordinator’s primary job is to create Model-View-ViewModel (MVVM) triads.

In this application, we didn’t want to show any view … not even the tab to hold a view … unless and until the user queried for customers. At this point in our story, the coordinator hasn’t created a view yet.

Having seen its first customer notification event, it creates the “Customer Orders View” MVVM triad and (using Prism regions) adds the view to the main region of the application shell. It then tells the CustomerOrdersViewModel which customer to display. The ViewModel takes it from there.

Henceforth, whenever the coordinator hears a customer notification, it tells the ViewModel to update itself (and its view) accordingly.

Should the CustomerOrdersViewModel be a subscriber?


That is certainly an option. The coordinator could have turned the listening duties over to the ViewModel, unsubscribed, and disappeared.

I chose a different path for these reasons. If the ViewModel were to subscribe and respond, it would duplicate much of the subscription code that the coordinator is using to bootstrap the MVVM triad. I don’t like to duplicate code and this would have lead me to factor the duplication into some helper class which is just another moving part.

Perhaps as important, I like that the ViewModel doesn’t know how to acquire its model. This frees the ViewModel to concentrate exclusively on supporting its view. These more limited responsibilities make it easier to understand and easier to test.

In sum, I’m happy to keep the coordinator around and let it “coordinate” the ViewModel as well as instantiate it.

Code Fragments


Now that we have the outlines of the story, we can show the fragments of code that are most likely to interest you. The entire code can be obtained from here.

EntityNotificationEvent and EntityNotificationSpecification


namespace PrismExplorer.Infrastructure {
public class EntityNotificationEvent : CompositePresentationEvent<EntityNotificationSpecification> {}

public class EntityNotificationSpecification {

public EntityNotificationSpecification(Type entityType, object entityId) {
EntityType = entityType;
EntityId = entityId;
}

public object EntityId { get; private set; }

public Type EntityType { get; private set; }

public bool IsType<T>() { return IsType(typeof(T)); }

public bool IsType(Type entityType) { return EntityType == entityType; }

}

}

We see that these classes are defined in the application’s infrastructure assembly which is referenced by both the MevModule and the CustomerOrdersModule.

Event Publication


The following two methods appear inside ModelExplorerViewModel, a class of the MevModule:

private void PublishEntitySelected() {
var spec = GetEntityNotificationSpecification();

if (null == spec) return;
PublishEntitySelected(spec);
}

private void PublishEntitySelected(EntityNotificationSpecification spec) {
_eventAggregator.GetEvent<EntityNotificationEvent>().Publish(spec);
}

GetEntityNotificationSpecification is not interesting; suffice it to say that it determines if there is an entity to talk about and, if there is, builds a “specification” of that entity so we’ll know what entity the event is talking about.

The EA (held in the “_eventAggregator” field) was injected into the ViewModel during its construction without developer assistance. It just arrives. Isn’t Dependency Injection marvelous!

We use the EA’s strongly-typed GetEvent() method to retrieve the desired Event object and call that Event object’s publish method, passing along the specification object.

Event Subscription


Here is the CustomerOrdersViewModel subscription method:

protected void Subscribe() {

// Simple subscription - pass in the fn that implements the response
_eventAggregator.GetEvent<CacheClearEvent>().Subscribe(ClearView);

// Full-blown subscription
_eventAggregator.GetEvent<EntityNotificationEvent>().Subscribe(
NotifyView, // the function that implements the response
ThreadOption.UIThread, // invoke subscription on the UI Thread
true, // a true KeepAlive means ensure strong fn references
IsNotificationRelevant // Fn to filter events so only hear pertinent events
);

}

public void ClearView(object dummy) {
if (null == _moduleViewModel) return;
_moduleViewModel.Clear();
}

public void NotifyView(EntityNotificationSpecification spec) {
EnsureViewExists();
_moduleViewModel.RequestCustomer(spec.EntityId as string);
}

Evidently there were some detail I omitted from my story. Time to fill in the gaps.

First, there were two events to consider. The CustomerOrders view needs to know when either the customer has changed or the client cache has been purged.

If you don’t know this application, you don’t know that it maintains a cache of all retrieved entities: all customers, employees, orders, … everything. The application happens to share this cache across the Mev and CustomerOrders modules.

Such sharing is a design choice. It isn’t mandated by anything; it isn’t even a recommendation. It’s just an artifact of this particular design. But it has a consequence. If something clears the cache and the CustomerOrders view happens to be displaying a customer that was in the cache, that customer entity object is no longer “valid for display”. Cache-clearing is a MevModule capability so we must watch out for this.

The ModelExplorerViewModel in MevModule actually raises two events: the EntityNotificationEvent and the CacheClearEvent.

The coordinator subscribes to the CacheClearEvent first. That event is simple. It needs no payload. We can use the lightest Subscribe overload of the Event object which takes a single argument, the handler for the event. ClearView() is the handler in this case; it accepts an object (all handlers must accept some kind of payload, even a dummy) but discards it.

The second subscription reveals the full blown Subscribe() overload. It takes four arguments.

The first is the handler, “NotifyView” in our example. EnsureViewExists will create the MVVM triad and display it in the shell if it hasn’t done so already. It then extracts the customer Id from the specification (the event payload) and hands it to the ViewModel which should know what to do.

The last argument, “IsNotificationRelevant”, refers to another method of the coordinator. This is a “predicate” (a function taking one argument and returning true or false) that determines if the EntityNotificationEvent concerns a customer. If it concerns a customer, the subscriber cares and the EA should invoke the “NotifyView” handler. If the event concerns any other kind of entity, the subscriber does not care and the EA should move on to other subscribers.

The second argument, “ThreadOption.UIThread” tells the EA that it should only call the handler on the UI thread. Prism contemplates the possibility that you will raise the event on a background thread. By default, Prism calls handlers on the publisher’s thread. Were we to raise the EntityNotificationEvent on a background thread and not specified the ThreadOption, our application would explode in pain: our handler updates the UI and that must be done on the UI thread.

To be safe, I should have specified the UIThread in the earlier CacheClearEvent subscription. I happen to know for a dead-on certainty that the CacheClearEvent will never, never, never, never, never be raised on any thread other than the UIThread. I’ll never have a problem. We continue.

I saved the third argument for last.

The Prism EA can hold strong or weak references to your handler and filter functions (“NotifyView” and “IsNotificationRelevant”).

The references are weak by default … usually a wonderful convenience because it relieves the developer of concern about a memory leak.

Suppose the references were strong and the coordinator went out of scope by which I mean that there were no other living objects that held a reference to it. I would hope that the garbage collector would come along and remove it from memory.

Unfortunately, the EA would keep my coordinator alive because it would have a strong reference to the coordinator handler. My coordinator would live as long as the EA itself … which is as long as the application session itself. The coordinator, which is doing nothing for my application, is holding down a spot in memory. That’s the leak. I’ll have to take special steps (implement IDisposable, unsubscribe in the Disposing() method, remember to call Dispose) if I want to ensure that the coordinator gets garbage collected. I probably won’t bother.

Suppose the EA’s handler references are weak. Now the EA’s references to the coordinator “don’t count” and the garbage collector can dispose of it as soon as no other object holds a reference to it. I won’t have to write a lick of code. Pretty sweet.

So why did I force the EA to hold strong references by setting “KeepAlive” to true?

Trust me, I tried the weak reference first and there was a problem. A big problem. The coordinator was garbage collected BEFORE it had a chance to hear the first Customer notification event. In fact, it was collected almost immediately after it subscribed to the event. I never saw the “Customer Orders” tab let alone the customer/orders master/detail view.

Why? Because the coordinator was never referred to by another object with sufficient lifetime. I thought that the CustomerOrdersModule would keep it around but, oops, that instance was GC’d too! Apparently decoupling really works. It was a tough bug to track down too; watch for it.

Wrap Up


This is long post … with many a winding turn.

That worries me because it suggests that Event Aggregator is hard to use. It is not. The code to consume it, as we’ve seen, amounts to about 30 neatly spaced lines. So blame me for being long winded.

Try it yourself. Use it wisely … and sparingly.

Event Aggregator is appropriate when

  • the subscriber shouldn’t know the publisher

  • subscriptions can precede publications

  • the Event is Fire-and-Forget


Do not use it everywhere. It is not a replacement for standard .NET eventing.

Update 16 June: The "Prism Explorer" code has changed. The "Coordinator" is now a "Screen Factory", it unsubscribes, and the "ViewModel" is now subscribing once it has been created.

The gist of this post remains true and relevant. There is still a GC threat to the "Screen Factory" which must subscribe with a strong reference. The changes were prompted by design interests unrelated to EA.

And ... yeah ... I guess you could say I changed my mind about whether the VM should subscribe. There was very little duplication, the Factory got simpler, less indirection ... hey, what more can I say?

2 comments:

Anonymous said...

Hi Ward,

We are building a Silverlight LOB application using prism and plan on using your devforce product (which we are really excited about). Your prism explorer application is proving particularly useful in understanding both prism and the MVVM architecture so thanks. In terms of implementing MVVM, and considering this post, I was wondering if you are aware of the memory leak in Prism Explorer, which is associated with the FormView and FormViewModel. Basically, neither the FormView nor FormViewModel is GC'd when the view is closed.
I also wonder if I’m completely missing something as we have encountered a number of similar memory leaks in our application. Basically, we are using something similar to your coordinator to load a view into a region and pass the viewmodel as the datacontext. When we remove the view from the region the view is GC’d as expected however the viewmodel isn’t. The interesting thing is that if I pass the viewmodel to the view but not set it as the datacontext, both the view and viewmodel gets GC’d. I can’t think why this would be.

Anyway, I’m glad to see you are blogging more frequently.

Cheers
Ian

Ward Bell said...

You’ve got a very interesting case.

I don’t know if you’ve passed it along to the SL team. I have an idea why this might be happening … and it would be so fundamental to WPF / SL that you’d think there would be literature on it.

My speculation (it is only that) is that because DataContext is a DependencyProperty, unlike Tag, there is some other class somewhere … not the View Class … that is holding the ViewModel … and doing so on the behalf of the View. The View doesn’t actually have a reference to the VM; that “other” class has the reference; as with all DPs, that other class is (under the covers) maintaining an association between your view class and the VM. Now, according to this theory, that class should also prevent your View from being GC’d. But maybe it holds a weak reference to your view.

Again … I’m just guessing. This is definitely a good question for the SL and WPF boards.

Please let me know if you get anywhere with this.