Delen via


Event aggregation Quickstart for Windows Store apps 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 how to perform event aggregation using Prism for the Windows Runtime. Event aggregation allows communication between loosely coupled components in an app, removing the need for components to have a reference to each other.

Download

You will learn

  • How event aggregation enables communication between loosely coupled components in a Windows Store app.
  • How to define a pub/sub event.
  • How to notify subscribers by retrieving a pub/sub event from the event aggregator.
  • How to register to receive notifications for a pub/sub event.

Applies to

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

The Quickstart contains a publisher and several subscribers that communicate using an instance of the Microsoft.Practices.Prism.PubSubEvents library's PubSubEvent<TPayload> class. This instance is managed by an EventAggregator object.

In this Quickstart, the lifetimes of publishers and subscribers are independent because the objects are not connected by object references. There are also no type dependencies between publishers and subscribers—publisher and subscriber classes can be packaged in unrelated assemblies. Nonetheless, when the 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. The Quickstart shows the following options:

  • The action is invoked synchronously in the same thread as the Publish thread.
  • The action is scheduled to run in the background on a thread-pool thread.
  • The action is dispatched to the app's UI thread.

Subscriptions in this Quickstart use weak references. Registering a subscription action does not add a reference to the subscriber.

Building and running the Quickstart

Build the Quickstart as you would a standard project:

  1. On the Microsoft Visual Studio menu bar, choose Build > Build Solution.
  2. After you build the project, you must deploy it. On the menu bar, choose Build > Deploy Solution. Visual Studio also deploys the project when you run the app from the debugger.
  3. After you deploy the project, pick the Quickstart tile to run the app. Alternatively, from Visual Studio, on the menu bar, choose Debug > Start Debugging.

When the app runs you will see a page similar to the one shown in the following diagram.

Panels represent the PublisherViewModel and SubscriberViewModel classes. In the left panel are two buttons that allow you to add items to a shopping cart, from the UI thread and from a background thread. Selecting either button causes the PublisherViewModel class to add an item to the shopping cart and invoke the Publish method of the ShoppingCartChangedEvent class that derives from the PubSubEvent<TPayload> class. The SubscriberViewModel class has two subscriptions to this event, in order to update the count of the number of items in the shopping cart, and to display a warning message once there are more than 10 items in the shopping cart.

On the right of the page there's a button for adding a background subscriber to the ShoppingCartChangedEvent. If this button is selected, a message dialog is shown from the background subscriber whenever the ShoppingCartChangedEvent is published. There's also a button that forces the background subscriber to be garbage collected. No special cleaned is required—the background subscriber did not need to call the ShoppingCartChangedEvent class's Unsubscribe method.

For more info about event aggregation, see Communicating between loosely coupled components.

[Top]

Solution structure

The EventAggregatorQuickstart Visual Studio solution contains three projects: EventAggregatorQuickstart, Microsoft.Practices.Prism.PubSubEvents, and Microsoft.Practices.Prism.StoreApps. The EventAggregatorQuickstart project uses Visual Studio solution folders to organize the source code into these logical categories:

  • The Assets folder contains the splash screen and logo images.
  • The Common folder contains the styles resource dictionary used in the app.
  • The Events folder contains the ShoppingCartChangedEvent class.
  • The Models folder contains the two model classes used in the app.
  • The ViewModels folder contains the view model classes that are exposed to the views.
  • The Views folder contains the views that make up the UI for the app's page.

The Microsoft.Practices.Prism.StoreApps library contains reusable classes used by this Quickstart. The Microsoft.Practices.Prism.PubSubEvents project is a Portable Class Library (PCL) that implements event aggregation. For more info about portal class libraries, see Cross-Platform Development with the .NET Framework. This project has no dependencies on any other projects, and can be added to your own Visual Studio solution without the Microsoft.Practices.Prism.StoreApps library. For more info about these libraries, see Prism for the Windows Runtime reference. With little or no modification, you can reuse many of the classes from this Quickstart in another app. You can also adapt the organization and ideas that this Quickstart provides.

[Top]

Key classes in the Quickstart

The EventAggregator class is responsible for locating or building events and for managing the collection of events in the system. In this Quickstart, an instance of the EventAggregator class is created in the OnLaunched method in the App class. The EventAggregator instance must be created on the UI thread in order for UI thread dispatching to work. This instance is then passed into the view model classes through constructor injection. This is shown in the following code examples.

EventAggregatorQuickstart\Bootstrapper.cs

public void Bootstrap(INavigationService navService)
{
    // Create the singleton EventAggregator so it can be dependency injected down to the view models who need it
    _eventAggregator  = new EventAggregator();
    ViewModelLocator.Register(typeof(MainPage).ToString(), () => new MainPageViewModel(_eventAggregator));
}

The app has a singleton instance of the EventAggregator class that is created on the UI thread.

EventAggregatorQuickstart\ViewModels\MainPageViewModel.cs

public MainPageViewModel(IEventAggregator eventAggregator)
{
    // Pass the injected event aggregator singleton down to children since there is no container to do the dependency injection
    SubscriberViewModel = new SubscriberViewModel(eventAggregator);
    PublisherViewModel = new PublisherViewModel(eventAggregator);
}

View models, such as the MainPageViewModel, take the event aggregator object as a constructor parameter and pass this object to any of their child objects that need to use event aggregation. In the code example, the MainPageViewModel passes the event aggregator to the SubscriberViewModel and PublisherViewModel instances that it contains.

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 published. Compile-time checking helps publishers and subscribers provide successful event connection.

The following diagram shows a conceptual view of how event aggregation is used in this Quickstart.

[Top]

Defining the ShoppingCartChangedEvent class

The ShoppingCartChangedEvent class's Publish method is invoked when the user adds an item to the shopping cart. This class, which derives from the PubSubEvent<TPayload> class, is used to communicate between the loosely coupled PublisherViewModel and SubscriberViewModel classes. The following code example shows how the ShoppingCartChangedEvent is defined, specifying ShoppingCart as the payload type.

EventAggregatorQuickstart\Events\ShoppingCartChangedEvent.cs

public class ShoppingCartChangedEvent : PubSubEvent<ShoppingCart> { }

[Top]

Notifying subscribers of the ShoppingCartChangedEvent

Users can add an item to the shopping cart from both the UI thread and from a background thread. When an item is added to the shopping cart the PublisherViewModel class calls the ShoppingCartChangedEvent's Publish method in order to alert subscribers of the change to the shopping cart. The following code example shows how the subscribers are notified.

EventAggregatorQuickstart\ViewModels\PublisherViewModel.cs

private void PublishOnUIThread()
{
    AddItemToCart();
    // Fire the event on the UI thread
    _eventAggregator.GetEvent<ShoppingCartChangedEvent>().Publish(_cart);
}

private void PublishOnBackgroundThread()
{
    AddItemToCart();
    Task.Factory.StartNew(() => 
        {
            // Fire the event on a background thread
            _eventAggregator.GetEvent<ShoppingCartChangedEvent>().Publish(_cart);
            Debug.WriteLine(String.Format("Publishing from thread: {0}", Environment.CurrentManagedThreadId));
        });
}

private void AddItemToCart()
{
    var item = new ShoppingCartItem("Widget", 19.99m);
    _cart.AddItem(item);
}

Publishing can occur from any thread. The EventAggregator and PubSubEvent<TPayload> classes are thread safe. The Quickstart shows this by notifying subscribers from both the UI thread and a background thread.

Note  If you access objects from more than one thread you must ensure that you appropriately serialize reads and writes. For example, the ShoppingCart class in this Quickstart is a thread safe class.

 

The PublishOnUIThread and PublishOnBackgroundThread methods add an item to the shopping cart by creating and initializing an instance of the ShoppingCartItem class. Then, the ShoppingCartChangedEvent is retrieved from the EventAggregator class and the Publish method is invoked on it. This supplies the ShoppingCart instance as the ShoppingCartChangedEvent event's parameter. The EventAggregator class's GetEvent method constructs the event if it has not already been constructed.

[Top]

Registering to receive notifications of the ShoppingCartChangedEvent

Subscribers can register actions with a PubSubEvent<TPayload> instance using one of its Subscribe method overloads. The SubscriberViewModel class subscribes to the ShoppingCartChangedEvent on the UI thread, regardless of which thread published the event. The subscriber indicates this during subscription by specifying a ThreadOption.UIThread value, as shown in the following code example.

EventAggregatorQuickstart\ViewModels\SubscriberViewModel.cs

// Subscribe indicating this handler should always be called on the UI Thread
_eventAggregator.GetEvent<ShoppingCartChangedEvent>().Subscribe(HandleShoppingCartUpdate, ThreadOption.UIThread);
// Subscribe indicating that this handler should always be called on UI thread, but only if more than 10 items in cart
_eventAggregator.GetEvent<ShoppingCartChangedEvent>().Subscribe(HandleShoppingCartUpdateFiltered, ThreadOption.UIThread, false, IsCartCountPossiblyTooHigh);

Subscribers provide an action with a signature that matches the payload of the pub/sub event. For example, the HandleShoppingCartUpdate method takes a ShoppingCart parameter. The method updates the number of items that are in the shopping cart.

A second subscription is made to the ShoppingCartChangedEvent using a filter expression. The filter expression defines a condition that the payload must meet for before the action will be invoked. In this case, the condition is satisfied if there are more than 10 items in the shopping cart. The HandleShoppingCartUpdateFiltered method shows a warning message to the user, indicating that they have more than 10 items in their shopping cart.

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. If you want to use dispatching on the UI thread, you must make sure that you instantiate the EventAggregator class in your app's UI thread.

 

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

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<TPayload> class's weak reference feature.

 

When the Add Background Subscriber button is selected the AddBackgroundSubscriber method is invoked. This method creates a background subscriber and holds onto the reference to the subscribing object in order to prevent it from being garbage collected. The method also subscribes using the HandleShoppingCartChanged method as the subscribed action. After the subscription is established, any call to the ShoppingCartChangedEvent's Publish method will synchronously invoke the HandleShoppingCartChanged method that displays a message dialog that informs the user that the shopping cart has been updated. The messages gives the numerical thread ID of the calling thread. You can use this to see that the expected thread was used for the action, depending on which button you used to add the shopping cart item.

EventAggregatorQuickstart\ViewModels\SubscriberViewModel.cs

private void AddBackgroundSubscriber()
{
    if (_subscriber != null) return;

    // Create subscriber and hold on to it so it does not get garbage collected
    _subscriber = new BackgroundSubscriber(Window.Current.Dispatcher);
    // Subscribe with defaults, pointing to subscriber method that pops a message box when the event fires
    _eventAggregator.GetEvent<ShoppingCartChangedEvent>().Subscribe(_subscriber.HandleShoppingCartChanged);
}

When the GC Background Subscriber button is selected the GCBackgroundSubscriber method is invoked. This method releases the reference to the background subscriber and forces the garbage collector to run. This garbage collects the background subscriber. The registered action will then no longer be invoked by the Publish method.

EventAggregatorQuickstart\ViewModels\SubscriberViewModel.cs

private void GCBackgroundSubscriber()
{
    // Release and GC, showing that we don't have to unsubscribe to keep the subscriber from being garbage collected
    _subscriber = null;
    GC.Collect();
}

[Top]