Delen via


Communicating between loosely coupled components in a Windows Store business app using C#, XAML, and Prism

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

From: Developing a Windows Store business app using C#, XAML, and Prism for the Windows Runtime

Previous page | Next page

Learn about event aggregation, provided by Prism for the Windows Runtime, which allows for communication between loosely coupled components. Event aggregation can reduce dependencies between assemblies in a solution, while also allowing components to be independently developed and tested.

Download

After you download the code, see Getting started using Prism for the Windows Runtime for instructions on how to compile and run the reference implementation, as well as understand the Microsoft Visual Studio solution structure.

You will learn

  • How event aggregation enables communication between loosely coupled components in an app.
  • How to define a pub/sub event, publish it, and subscribe to it using a default subscription.
  • How to subscribe to an event on the UI thread, perform event subscription filtering, and subscribe to an event by using strong references.
  • How to manually unsubscribe from a pub/sub event when using a strong delegate reference.

Applies to

  • Windows Runtime for Windows 8.1
  • C#
  • Extensible Application Markup Language (XAML)

Making key decisions

Event aggregation allows communication between loosely coupled components in an app, removing the need for components to have a reference to each other. The following list summarizes the decisions to make when using event aggregation in your app:

  • When should I use event aggregation over Microsoft .NET events?
  • How should I subscribe to pub/sub events?
  • How can a subscriber update the UI if the event is published from a background thread?
  • Does the subscriber need to handle every instance of a published event?
  • Do I need to unsubscribe from subscribed events?

Events in .NET implement the publish-subscribe pattern. The publisher and subscriber lifetimes are coupled by object references to each other, and the subscriber type must have a reference to the publisher type.

Event aggregation is a design pattern that enables communication between classes that are inconvenient to link by object and type references. This mechanism allows publishers and subscribers to communicate without having a reference to each other. Therefore, .NET events should be used for communication between components that already have object reference relationships (such as a control and the page that contains it), with event aggregation being used for communication between loosely coupled components (such as two separate page view models in an app). For more info see Event aggregation.

There are several ways to subscribe to events when using event aggregation. The simplest is to register a delegate reference of the event handler method that will be called on the publisher's thread. For more info see Subscribing to events.

If you need to be able to update UI elements when an event is received, you can subscribe to receive the event on the UI thread.

When subscribing to a pub/sub event, you can request that notification of the event will occur in the UI thread. This is useful, for example, when you need to update the UI in response to the event. For more info see Subscribing on the UI thread.

Subscribers do not need to handle every instance of a published event, as they can specify a delegate that is executed when the event is published to determine if the payload of the published event matches a set of criteria required to have the subscriber callback invoked. For more info see Subscription filtering.

By default, event aggregation maintains a weak delegate reference to a subscriber's handler. This means that the reference will not prevent garbage collection of the subscriber, and it relieves the subscriber from the need to unsubscribe. If you have observed performance problems with events, you can use strongly referenced delegates when subscribing to an event, and then unsubscribe from the event when it's no longer required. For more info see Subscribing using strong references.

[Top]

Event aggregation in AdventureWorks Shopper

The AdventureWorks Shopper reference implementation uses the Microsoft.Practices.Prism.PubSubEvents library to communicate between loosely coupled components. This is a Portable Class Library that contains classes that implement event aggregation. For more info see Prism for the Windows Runtime reference.

The AdventureWorks Shopper reference implementation defines the ShoppingCartUpdatedEvent class and ShoppingCartItemUpdatedEvent class for use with event aggregation. You invoke the ShoppingCartUpdatedEvent singleton instance's Publish method when the signed in user has changed, to notify the ShoppingCartTabUserControl of the change. The ShoppingCartTabUserControl is included on the HubPage, GroupDetailPage, and ItemDetailPage views, with there being no type or object references between the ShoppingCartTabUserControl and its parent pages.

The ShoppingCartItemUpdated event is published whenever a product is added to the shopping cart, so that the ShoppingCartTabUserControlViewModel class can be updated. For more info see Event aggregation.

Pub/sub events in the AdventureWorks Shopper reference implementation are published on the UI thread, with the subscribers receiving the event on the same thread. Weak reference delegates are used for both events, and so the events do not need to be unsubscribed from. For more info see Subscribing to events.

Note  Lambda expressions that capture the this reference cannot be used as weak references. You should use instance methods as the Subscribe method's action and filter parameters if you want to take advantage of the PubSubEvent class's weak reference feature.

 

[Top]

Event aggregation

.NET events are the most simple and straightforward approach for a communication layer between components if loose coupling is not required. Event aggregation should be used for communication when it's inconvenient to link objects with type and object references.

Note  If you use .NET events, you have to consider memory management, especially if you have a short lived object that subscribes to an event of a static or long lived object. If you do not remove the event handler, the subscriber will be kept alive by the reference to it in the publisher, and this will prevent or delay the garbage collection of the subscriber.

 

The event aggregator provides multicast publish/subscribe functionality. This means that there can be multiple publishers that invoke the Publish method of a given PubSubEvent<TPayload> instance and there can be multiple subscribers listening to the same PubSubEvent<TPayload> instance. A subscriber can have more than one subscription to a single PubSubEvent<TPayload> instance. The following diagram shows this relationship.

The EventAggregator class is responsible for locating or building singleton instances of pub/sub event classes. The class implements the IEventAggregator interface, shown in the following code example.

Microsoft.Practices.Prism.PubSubEvents\IEventAggregator.cs

public interface IEventAggregator
{
   TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
}

In the AdventureWorks Shopper reference implementation, an instance of the EventAggregator class is created in the OnLaunched method in the App class. This instance is then passed as an argument to the constructors of view model classes that need it.

[Top]

Defining and publishing pub/sub events

In apps such as the AdventureWorks Shopper reference implementation that use event aggregation, event publishers and subscribers are connected by the PubSubEvent<TPayload> class, which is the base class for an app's specific events. TPayload is the type of the event's payload. The PubSubEvent<TPayload> class maintains the list of subscribers and handles event dispatching to the subscribers. The class contains Subscribe method overloads, and Publish, Unsubscribe, and Contains methods.

Defining an event

A pub/sub event can be defined by creating an empty class that derives from the PubSubEvent<TPayload> class. The events in the AdventureWorks Shopper reference implementation do not all pass a payload because in some circumstances the event handling only needs to know that the event occurred and then retrieve the updated state related to the event through a service. In such cases, they declare the TPayload type as an Object and pass a null reference when publishing. The following code example shows how the ShoppingCartUpdatedEvent from AdventureWorks Shopper is defined.

AdventureWorks.UILogic\Events\ShoppingCartUpdatedEvent.cs

public class ShoppingCartUpdatedEvent : PubSubEvent<object>
{
}

Publishing an event

Publishers notify subscribers of a pub/sub event by retrieving a singleton instance that represents the event from the EventAggregator class and calling the Publish method of that instance. The EventAggregator class constructs the instance on first access. The following code demonstrates publishing the ShoppingCartUpdatedEvent.

AdventureWorks.UILogic\Repositories\ShoppingCartRepository.cs

private void RaiseShoppingCartUpdated()
{
    // Documentation on loosely coupled communication is at https://go.microsoft.com/fwlink/?LinkID=288820&clcid=0x409
    _eventAggregator.GetEvent<ShoppingCartUpdatedEvent>().Publish(null);
}

[Top]

Subscribing to events

Subscribers can enlist with an event using one of the Subscribe method overloads available in the PubSubEvent<TPayload> class. There are several approaches to event subscription.

Default subscription

In the simplest case, the subscriber must provide a handler to be invoked whenever the pub/sub event is published. This is shown in the following code example.

AdventureWorks.UILogic\ViewModels\ShoppingCartPageViewModel.cs

public ShoppingCartPageViewModel(...)
{
   ...
   eventAggregator.GetEvent<ShoppingCartUpdatedEvent>().Subscribe(UpdateShoppingCartAsync);
   ...
}

public async void UpdateShoppingCartAsync(object notUsed)
{
   ...
}

In the code, the ShoppingCartPageViewModel class subscribes to the ShoppingCartUpdatedEvent using the UpdateShoppingCartAsync method as the handler.

Subscribing on the UI thread

A subscriber will sometimes need to update UI elements in response to events. In Windows Store apps, only the app's main thread can update UI elements.

By default, each subscribed handler action is invoked synchronously from the Publish method, in no defined order. If your handler action needs to be called from the UI thread, for example, in order to update UI elements, you can specify a ThreadOption when you subscribe. This is shown in the following code example.

EventAggregatorQuickstart\ViewModels\SubscriberViewModel.cs

public SubscriberViewModel(IEventAggregator eventAggregator)
{
   ...
   _eventAggregator.GetEvent<ShoppingCartChangedEvent>().Subscribe(HandleShoppingCartUpdate, ThreadOption.UIThread);
   ...
}

The ThreadOption enumeration allows three possible values:

  • PublisherThread. This value should be used to receive the event on the publishers' thread, and is the default setting. The invocation of the handler action is synchronous.
  • BackgroundThread. This value should be used to asynchronously receive the event on a thread-pool thread. The handler action is queued using a new task.
  • UIThread. This value should be used to receive the event on the UI thread. The handler action is posted to the synchronization context that was used to instantiate the event aggregator.

Note  For UI thread dispatching to work, the EventAggregator class must be created on the UI thread. This allows it to capture and store the SynchronizationContext that is used to dispatch to the UI thread for subscribers that use the ThreadOption.UIThread value.

 

In addition, it is not recommended that you modify the payload object from within a callback delegate because several threads could be accessing the payload object simultaneously. In this scenario you should have the payload be immutable to avoid concurrency errors.

Subscription filtering

A subscriber may not need to handle every instance of a published event. In this case, the subscriber can use a Subscribe method overload that accepts a filter parameter. The filter parameter is of type System.Predicate<TPayload> and is executed when the event is published. If the payload does satisfy the predicate, the subscriber callback is not executed. The filter parameter is shown in the following code example.

EventAggregatorQuickstart\ViewModels\SubscriberViewModel.cs

public SubscriberViewModel(IEventAggregator eventAggregator)
{
   ...
   _eventAggregator.GetEvent<ShoppingCartChangedEvent>().Subscribe(HandleShoppingCartUpdateFiltered, ThreadOption.UIThread, false, IsCartCountPossiblyTooHigh);
   ...
}

The Subscribe method returns a subscription token of type Microsoft.Practices.Prism.PubSubEvents.SubscriptionToken that can later be used to remove a subscription to the event. This token is useful if you are using anonymous delegates as the callback delegate or when you are subscribing to the same event handler with different filters.

Note  The filter action is executed synchronously from the context of the Publish method regardless of the ThreadOption value of the current subscription.

 

Subscribing using strong references

The PubSubEvent<TPayload> class, by default, maintains a weak delegate reference to the subscriber's handler and any filter, on subscription. This means that the reference that the PubSubEvent<TPayload> class holds onto will not prevent garbage collection of the subscriber. Therefore, using a weak delegate reference relieves the subscriber from the need to unsubscribe from the event, and allows for garbage collection.

Maintaining a weak delegate reference has a slightly higher performance impact than using a corresponding strong delegate reference. If your app publishes many events in a very short period of time, you may notice a performance cost when using weak delegate references. However, for most apps the performance will not be noticeable. In the event of noticing a performance cost, you may need to subscribe to events by using strong delegate references instead. If you do use strong delegate references, your subscriber will need to unsubscribe from events when the subscription is no longer needed.

To subscribe with a strong delegate reference, use an overload of the Subscribe method that has the keepSubscriberReferenceAlive parameter, as shown in the following code example.

public SubscriberViewModel(IEventAggregator eventAggregator)
{
   ...
   bool keepSubscriberReferenceAlive = true;
   _eventAggregator.GetEvent<ShoppingCartChangedEvent>().Subscribe(HandleShoppingCartUpdateFiltered, ThreadOption.UIThread, keepSubscriberReferenceAlive);
   ...
}

The keepSubscriberReferenceAlive parameter is of type bool. When set to true, the event instance keeps a strong reference to the subscriber instance, thereby not allowing it to be garbage collected. For info about how to unsubscribe see Unsubscribing from pub/sub events. When set to false, which is the default value when the parameter is omitted, the event maintains a weak reference to the subscriber instance, thereby allowing the garbage collector to dispose the subscriber instance when there are no other references to it. When the subscriber instance is garbage collected, the event is automatically unsubscribed.

[Top]

Unsubscribing from pub/sub events

If your subscriber no longer want to receive events, you can unsubscribe by using your subscriber's handler or by using a subscription token. The following code example shows how to unsubscribe by using your subscriber's handler.

ShoppingCartChangedEvent shoppingCartChangedEvent = _eventAggregator.GetEvent<ShoppingCartChangedEvent>();
shoppingCartChangedEvent.Subscribe(HandleShoppingCartUpdate, ThreadOption.PublisherThread);
...
shoppingCartChangedEvent.Unsubscribe(HandleShoppingCartUpdate);

The following code example shows how to unsubscribe by using a subscription token. The token is supplied as a return value from the Subscribe method.

ShoppingCartChangedEvent shoppingCartChangedEvent =  _eventAggregator.GetEvent<ShoppingCartChangedEvent>();
subscriptionToken = shoppingCartChangedEvent.Subscribe(HandleShoppingCartUpdate, ThreadOption.UIThread, false, IsCartCountPossiblyTooHigh);
...
shoppingCartChangedEvent.Unsubscribe(subscriptionToken);

[Top]