Monday, 5 August 2013

Event aggregation using Prism for the Windows Runtime

Previously I’ve blogged about how to perform validation using Prism for the Windows Runtime. In this blog post I’ll demonstrate how to use Prism for the Windows Runtime to perform event aggregation. Event aggregation allows communication between loosely coupled components in an app, removing the need for components to have a reference to each other. For more info about event aggregation see Communicating between loosely coupled components in AdventureWorks Shopper.

My hypothetical scenario for the accompanying sample app is a stock control system for an organisation that sells goods. When a user buys a good the number of items in stock in the warehouse decreases, whereas in the accounts department the number of items paid for increases. The sample app uses the Microsoft.Practices.Prism.PubSubEvents library, which is a PCL that implements event aggregation.

Implementation

In the sample app the lifetimes of event publishers and subscribers are independent because the objects are not connected by object references. There are also no type dependencies between publishers and subscribers – the publisher and subscriber classes can be packaged in unrelated assemblies.

The EventAggregator class is responsible for locating and building events. An instance of the EventAggregator class is created in the App class, and must be created on the UI thread in order for UI thread dispatching to work. The instance is then registered with the Unity dependency injection container so that it can be passed into view model classes through constructor injection.

private readonly IEventAggregator _eventAggregator = new EventAggregator();
 
protected override void OnInitialize(IActivatedEventArgs args)
{
    ...
    _container.RegisterInstance(_eventAggregator);
    ...
}
Defining the event

The PubSubEvent<TPayload> class connects event publishers and subscribers, and is the base class for an app’s specific events. TPayload is the type of the event’s payload, and is the argument that will be passed to subscribers when an event is publlished. The sample app defines the OrderPaidForEvent, which will be published whenever the user purchases an item.

public class OrderPaidForEvent : PubSubEvent<object>
{
}
This event is used to communicate between the loosely coupled MainPageViewModel, WarehouseUserControlViewModel and AccountsUserControlViewModel classes.
Publishing the event

When the user purchases an item the MainPageViewModel class calls the OrderPaidForEvent’s Publish method in order to publish the event.

private void PurchaseItem()
{
    _eventAggregator.GetEvent<OrderPaidForEvent>().Publish(null);
}

The EventAggregator’s GetEvent method constructs the event if it has not already been constructed, and publishing can occur from any thread.

Subscribing to the event

Loosely coupled classes can be notified when the OrderPaidForEvent is published by subscribing to it, using one of the PubSubEvent<TPayload> Subscribe method overloads.

public class WarehouseUserControlViewModel : ViewModel, IWarehouseUserControlViewModel
{
    private readonly IEventAggregator _eventAggregator;
    private int _itemsInStock = 437;
 
    public int ItemsInStock 
    { 
        get { return _itemsInStock; } 
        private set { SetProperty(ref _itemsInStock, value); }
    }
 
    public WarehouseUserControlViewModel(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
        _eventAggregator.GetEvent<OrderPaidForEvent>().Subscribe(HandleOrderPaidForEvent);
    }
 
    private void HandleOrderPaidForEvent(object payload)
    {
        ItemsInStock--;
    }
}

Subscribers must provide an action with a signature that matches the payload of the event. For example, the HandleOrderPaidForEvent takes an object parameter. The method decrements that number of items that are in stock in the warehouse. This action will be executed by the subscriber when the event is published.

By default, the PubSubEvent<TPayload> class maintains a weak delegate reference to the subscriber’s registered action. This means that the reference that the PubSubEvent<TPayload> class holds onto will not prevent garbage collection of the subscriber. This approach relieves the subscriber from the need to unsubscribe from the event. The garbage collector will then dispose the subscriber instance when there are no references to it.

The overall effect is that when a user purchases an item the OrderPaidForEvent is published by the MainPageViewModel class. Both the WarehouseUserControlViewModel and AccountsUserControlViewModel subscribe to the event and respond to its publication by executing actions specific to each view model. All event publishing and subscribing occurs on the UI thread.

Summary

In this blog post I’ve demonstrated how to use Prism for the Windows Runtime to perform event aggregation between loosely coupled classes. When a publisher invokes the PubSubEvent<TPayload> class’s Publish method the system will run all actions that have been registered by the PubSubEvent<TPayload> class’s Subscribe method. Subscribers can control how the actions run. Subscriptions use weak references by default. Therefore registering a subscription action does not add a reference to the subscriber.

There’s a lot more to event aggregation than covered in this blog post. For more info see Communicating between loosely coupled components in AdventureWorks Shopper.

The sample app can be downloaded here.

1 comment:

  1. Dave - Thanks very much for the work you put into this. I just had a nasty surprise trying to use use CompositePresentationEvent (like, it's not there anymore), and your post got me publishing and subscribing in no time.

    ReplyDelete