This article illustrates the problems that occur when you use regular events for intercomponent communication in a composite application. It shows how to solve these problems by implementing the Mediator design pattern, using the Event Aggregator from the Prism 4.0 framework as an example.
Composite applications
A composite application draws its functionality from a set of as-loosely-as-possible-coupled functional modules. At runtime, these modules are loaded into an application shell that provides common non-functional services, such as security, screen layout, and localization. The Managed Extensibility Framework (MEF) simplifies the design and development of this type of applications by providing module discoverability and composition capabilities:
Discoverability
Types that want to be discovered by MEF indicate this by using attributes. The following code snippets from the attached Visual Studio project show how a publisher and a subscriber make themselves available to the outside world (i.c. a viewmodel that will hook them together):
[Export]
public class Subscriber : INotifyPropertyChanged
{
// ...
}
[Export(typeof(IPublisher))]
public class Publisher : IPublisher
{
// ...
}
Composition
At runtime, the application's object graph is constructed by the so-called composition. All the application needs to do, is decorating the required parts with attributes. This is how the viewmodels in the sample project request for a publisher and a subscriber:
[Import]
public IPublisher Publisher { get; set; }
[Import]
public Subscriber Subscriber { get; set; }
The exported parts are looked up by MEF in catalogs, placed in a container, and then bound to the corresponding imports:
AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
MVVM
Despite its strict modularity, a composite application is not really different from any other enterprise application that you would build in WPF, Silverlight, or WP7. For maintainability and testability purposes it definitely makes sense to develop the modules as well as the shell according to the MVVM-pattern. You'll end up with an application architecture that looks like a grid: functionality is layered vertically into modules, and technicality is layered horizontally into views, viewmodels, and models:
Both axes should maintain a level of independence between constituants: individual modules may make no assumptions about other modules, and within a module the models shouldn't know their viewmodels, viewmodels shouldn't know their views, and views should have no code beside.
The attached sample project has a simplified version of this architecture: it does everything in one project. It is however representative: all objects are injected through MEF, and the application is strictly MVVM. Here's a screenshot:
Communication
When the composite application is running, some components living in different architectural cells would want to talk to each other, e.g. viewmodels from different modules would want to play their role in an application-wide publisher-subscriber scenario. An implementation based on .NET events seems the obvious choice here.
Regular Events
Unfortunately, regular events require a reference from the subscriber to the publisher - at least while registering the event handler. After that, the subscriber doesn't need that reference to the publisher anymore. But even if you throw it away, the objects stay tightly connected: the publisher holds a reference to the subscriber in its delegate, as the 'target' of the method:
This prevents the subscriber from being garbage collected even if the rest of the application isn't referencing it anymore. You can verify this easily by running the sample project. After you 'kill' the regular event subscriber, it will remain in memory:
The only way to clean up memory is to unregister the event handler, e.g. in a Dispose action. Unfortunately this implies that the subscriber should hold a permanent reference to the publisher, now preventing the publisher to be cleaned up. In this scenario you should also exactly know when to dispose the subscriber. In a lot of cases -especially in composite applications- this is simply not possible.
EventAggregator
What you need in a composite application is a kind of central chat room, where all components anonymously appear, communicate, and disappear. This is exactly what the classic Gang-of-Four Mediator design pattern does: it describes how to moderate communication between components that aren't aware of each other's identity. The Prism 4.0 Event Aggregator is a robust implemenation of this design pattern. Prism is a framework and a set of guidelines on building composite applications in WPF, Silverlight, and WP7. You'll find more info here.
In our sample application, the publisher and subscriber need to get a reference to a central event aggregator. We'll use MEF to do this.
First the publisher and subscriber need to add the event aggregator to their list of required parts:
[Import]
public EventAggregator EventAggregator
{
get { return this.eventAggregator; }
set { this.eventAggregator = value; }
}
Then you have to make sure that the event aggregator becomes available, e.g. by explicitly adding it to the MEF container (Prism has other ways to do this):
AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
CompositionContainer container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddExportedValue<EventAggregator>(new EventAggregator());
container.Compose(batch);
container.ComposeParts(this);
Now it's time to define the event's payload type - a Prism requirement:
using Microsoft.Practices.Prism.Events;
public class PublishedEvent : CompositePresentationEvent<object>
{
}
The publisher publishes an event with the following two lines:
PublishedEvent pe = this.eventAggregator.GetEvent<PublishedEvent>();
pe.Publish(null);
In the subscriber, registering an event is also a two-liner:
PublishedEvent pe = this.eventAggregator.GetEvent<PublishedEvent>();
pe.Subscribe(o => this.Publisher_Published(o));
The event aggregator holds publishers and subscribers together via weak references, allowing participants to disappear without memory leaks. 'Killing' the subscriber in the sample application, will release its memory:
Extra's
I hope you're convinced of the need for a communication Mediator in some of your applications. If you go for Prism's event aggregator, you'll get a couple of extra's.
- In some cases it's necessary to 'upgrade' the default weak reference to a strong one. You can do this by setting the second parameter in the registration method to 'true':
pe.Subscribe(
o => this.Publisher_Published(o),
true);
- A subscriber can specify on which thread he wants to be notified. This is handy when the event handler needs the UI thread. In a viewmodel from a MVVM application this should never be the case: the bindings should take care of this.
- Lastly, you can apply a filter on the subscription, so that some events will be ignored. The following -I admit: silly- example ignores events raised during 'odd' minutes:
pe.Subscribe(
o => this.Publisher_Published(o),
ThreadOption.PublisherThread,
false,
o => DateTime.Now.Minute % 2 == 0);
Source code
Here's the source, the whole source, and nothing but the source: U2UConsult.Composite.Communication.Sample.zip (619,46 kb)
Enjoy!