Event Aggregator
The EventAggregator service is primarily a container for events that allow decoupling of publishers and subscribers so they can evolve independently. This decoupling is useful in modularized applications because new modules can be added that respond to events defined by the shell or, more likely, other modules.
In the Composite Application Library, EventAggregator allows subscribers or publishers to locate a specific EventBase. The event aggregator also allows for multiple publishers and multiple subscribers, as shown in Figure 1.
Figure 1
Event aggregator
IEventAggregator
The EventAggregator class is offered as a service in the container and can be retrieved through the IEventAggregator interface. The event aggregator is responsible for locating or building events and for keeping a collection of the events in the system.
public interface IEventAggregator
{
TEventType GetEvent<TEventType>() where TEventType : EventBase;
}
The EventAggregator constructs the event on its first access if it has not already been constructed. This relieves the publisher or subscriber from needing to determine whether the event is available.
CompositePresentationEvent
The real work of connecting publishers and subscribers is done by the CompositePresentationEvent class. This is the only implementation of the EventBase class that is included in the Composite Application Library. This class maintains the list of subscribers and handles event dispatching to the subscribers.
The CompositePresentationEvent class is a generic class that requires the payload type to be defined as the generic type. This helps enforce, at compile time, that publishers and subscribers provide the correct methods for successful event connection. The following code shows a partial definition of the CompositePresentationEvent class.
public class CompositePresentationEvent<TPayload> : EventBase
{
…
public SubscriptionToken Subscribe(Action<TPayload> action);
public SubscriptionToken Subscribe(Action<TPayload> action,
ThreadOption threadOption);
public SubscriptionToken Subscribe(Action<TPayload> action, bool keepSubscriberReferenceAlive)
public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive);
public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter);
public virtual void Publish(TPayload payload);
public virtual void Unsubscribe(Action<TPayload> subscriber);
public virtual bool Contains(Action<TPayload> subscriber)
…
}
The CompositePresentationEvent is intended to be the base class for an application's or module's specific events. For example, the following code shows the TickerSymbolSelectedEvent in the Stock Trader Reference Implementation (Stock Trader RI). Notice how the implementation for this class is empty.
public class TickerSymbolSelectedEvent : CompositePresentationEvent<string>{}
Note
In a composite application, the events are frequently shared between multiple modules, so they are defined in a common place. In the Stock Trader RI, this is done in the StockTraderRI.Infrastructure project.
Subscribing to an Event
Subscribers can enlist with an event using one of the Subscribe method overloads available on the CompositePresentationEvent class. There are a number of options for subscribing to CompositePresentationEvents. Use the following criteria to help determine which option best suits your needs:
- If you need to be able to update user-interface elements when an event is received, subscribe to receive the event on the user interface thread.
- If you need to filter an event, provide a filter delegate when subscribing.
- If you have noticed performance concerns with your events, consider using strongly referenced delegates when subscribing and manually unsubscribe from the CompositePresentationEvent.
- If none of the preceding is applicable, use a default subscription.
The following sections describe these options.
Default Subscriptions
For a minimal or default subscription, the subscriber must provide a callback method with the appropriate signature that receives the event notification. For example, the handler for the TickerSymbolSelectedEvent requires the method take a string parameter, as shown here.
public void Run()
{
...
eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews);
}
public void ShowNews(string companySymbol)
{
articlePresentationModel.SetTickerSymbol(companySymbol);
}
Subscribing on the User Interface Thread
Frequently, subscribers will need to update user interface elements in response to events. In Windows Presentation Foundation (WPF), only a user interface (UI) thread can update user interface elements. By default, the subscriber receives the event on the publisher's thread, so if the publisher sends the event from the UI thread, the subscriber can update the user interface.
However, if the publisher's thread is a background thread, the subscriber may be unable to directly update user interface elements. Instead, it would need to schedule the updates on the UI thread using the Dispatcher class included with the .NET Framework 3.x. The CompositePresentationEvent provided with the Composite Application Library can assist by allowing the subscriber to automatically receive the event on the UI thread. The subscriber must indicate this during subscription, as shown in the following code.
public void Run()
{
...
eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews,
ThreadOption.UIThread);
);
}
public void ShowNews(string companySymbol)
{
articlePresentationModel.SetTickerSymbol(companySymbol);
}
The following options are available for ThreadOption:
- PublisherThread. Use this setting to receive the event on the publishers' thread. This is the default setting.
- BackgroundThread. Use this setting to asynchronously receive the event on a .NET Framework thread-pool thread.
- UIThread. Use this setting to receive the event on the user interface thread.
Subscription Filtering
Subscribers may not need to handle every instance of a published event. In these cases, the subscriber can subscribe and supply a delegate that filters the event before the registered handler is called. Frequently, this filter is supplied as a lambda expression, as shown in the following code.
FundAddedEvent fundAddedEvent = eventAggregator.GetEvent<FundAddedEvent>();
fundAddedEvent.Subscribe(FundAddedEventHandler,
ThreadOption.UIThread, false,
fundOrder => fundOrder.CustomerId == _customerId);
Note
Silverlight does not support weak references to lambda expressions or anonymous delegates.
For Silverlight, you need to call a separate public method, as shown in the following code.
public bool FundOrderFilter(FundOrder fundOrder)
{
return fundOrder.CustomerId == _customerId;
}
...
FundAddedEvent fundAddedEvent = eventAggregator.GetEvent<FundAddedEvent>();
subscriptionToken = fundAddedEvent.Subscribe(FundAddedEventHandler, ThreadOption.UIThread, false, FundOrderFilter);
Subscribing Using Strong References
If you are raising multiple events in a short period of time and have noticed performance concerns with them, you may need to subscribe with strong delegate references—and therefore manually unsubscribe from the event when disposing the subscriber—instead of the default weak delegate references maintained by CompositePresentationEvent.
By default, CompositePresentationEvent maintains a weak delegate reference to the subscriber's handler and filter on subscription. This means the reference that CompositePresentationEvent holds to the subscriber will not prevent garbage collection of the subscriber. Using a weak delegate reference relieves the subscriber from the need to unsubscribe to enable proper garbage collection. However, maintaining this weak delegate reference is slower than a corresponding strong delegate reference. For most applications, this performance will not be noticeable, but if your application publishes a large number of events in a short period of time, you may need to use strong delegate references with CompositePresentationEvent to achieve reasonable performance. If you do use strong delegate references, your subscriber should unsubscribe to enable proper garbage collection of your subscribing object when it is no longer used.
To subscribe with a strong reference, use the keepSubscriberReferenceAlive option on the Subscribe method, as shown in the following code.
FundAddedEvent fundAddedEvent = eventAggregator.GetEvent<FundAddedEvent>();
bool keepSubscriberReferenceAlive = true;
fundAddedEvent.Subscribe(FundAddedEventHandler,
ThreadOption.UIThread, keepSubscriberReferenceAlive,
fundOrder => fundOrder.CustomerId == _customerId);
Publishing an Event
Publishers raise an event by retrieving the event from the EventAggregator and calling the Publish method. For example, the following code demonstrates publishing the TickerSymbolSelectedEvent.
EventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(“STOCK0”);
Unsubscribing from an Event
If your subscriber no longer wants to receive events, you can unsubscribe by using your subscriber's handler or you can unsubscribe by using a subscription token. The following example shows how to directly unsubscribe to the handler.
compositePresentationEvent.Subscribe(
FundAddedEventHandler,
ThreadOption.PublisherThread);
compositePresentationEvent.Unsubscribe(FundAddedEventHandler);
To unsubscribe with a subscription token, the token supplied during the subscribe process can be supplied to the Unsubscribe method call, as shown here.
FundAddedEvent fundAddedEvent = eventAggregator.GetEvent<FundAddedEvent>();
subscriptionToken = fundAddedEvent.Subscribe(FundAddedEventHandler,
ThreadOption.UIThread, false,
fundOrder => fundOrder.CustomerId == _customerId);
fundAddedEvent.Unsubscribe(subscriptionToken);
More Information
For more information about events in the Composite Application Library, see the following resources:
- Event Aggregation QuickStart
- How to: Create and Publish Events
- How to: Subscribe and Unsubscribe to Events
- Event Aggregator on Martin Fowler's Web site
For more information about other Composite Application Guidance technical concepts, see the following topics: