Click here to Skip to main content
15,867,832 members
Articles / Desktop Programming / WPF

Prism Event DashBoard

Rate me:
Please Sign up or sign in to vote.
4.77/5 (7 votes)
18 Mar 2014CPOL27 min read 31.7K   785   24   9
DashBoard to view all PRISM events in real-time
Prism Event DashBoard

Contents

Introduction

The Prism Event DashBoard came out of a need to better see inside of the Prism EventAggregator. If you are building a large Prism application or have recently started working on a new Prism application, it can take some time to understand the flow of events that take place. Questions like these are tough to answer normally, but are handled nicely by the dashboard:

  • Where are the events defined?
  • Where are they published from?
  • Where are the Event Handlers?
  • How often do they get executed?
  • Are they executed too often or not enough?

It is easy to miss the actual flow of events if your only recourse is to manually trace code.

The Prism Event DashBoard answers all of these questions by logging all of the Prism events in a dashboard interface and presenting summary information on how often each event handler gets executed along with detailed information on every Publish event that is raised, all the handlers that get executed for that publish, and detailed information on each handler. You are even able to examine the payload values that have been sent to each handler along with what assembly the handlers are in and their specific location within the assembly. Once you start collecting data, you may find events firing you didn't know were firing along with events that don't fire at all or too often. If your application is multi-threaded, that adds a whole other layer of complexity to the event firing sequence. This dashboard can help unravel that complexity.

The dashboard is packaged as a separate module that can be plugged into any application. After launching your application, you can instantiate the dashboard view. Once it is running, all events that Prism executes will be collected in real time as the application is running. You can see the events being added to the various list views along with tally's of the total number of subscriptions, published events, number of event handlers being executed and the number of UnSubscribe operations being performed. One event being published may result in many event handlers being executed, this dashboard will log all of them.

Quick Start

To get up and running and see what is happening right away, simply unzip the StockTrader RI.zip file into a local directory. Then just compile the solution file provided and run it to see the demo app and dashboard module in action! It contains all of the DLLs needed to run the dashboard without first having to download and install the full PRISM framework. All projects were compiled using Visual Studio 10 in .NET 4.0. I've also built it in Visual Studio 12 with .NET 4.5. But it does not use any 4.5 features yet. This is the demo application that comes with the PRISM framework and I thought this was the best one to use since everyone would get it with the framework download.

Background

The Prism framework is a WPF framework written by Microsoft and is available here. It is a comprehensive framework that can significantly increase productivity within your WPF development team. The main focus of this article will be on the event aggregator that is part of the framework. It is possible to use this event framework separately in your own application without using any of the other pieces of the Prism framework. I would recommend however, taking a look at the whole package and utilizing as much as possible. It is a wonderful piece of software. When using the event aggregator with its Publish/Subscribe pattern, you will wonder how you developed without it.

While most of this article is intermediate to advanced in content, I will not assume the reader is familiar with all of the patterns and concepts. Many Microsoft technologies are careers unto themselves and one can't know all of the technologies equally. So where deemed necessary, I will explain what may appear as basic information... just to be sure. :)

Design Decisions

In order to respond to all events that are happening inside of a Prism application, there is only one place that the notification of the application level events can be published. That is inside of the Prism framework itself. The Prism framework DLL (Microsoft.Practices.Prism) is referenced by all modules inside of your Prism application. All events that are subscribed to and published are handled by this core Prism DLL. So this is the gateway that needs to be modified in order to log these application level events.

I created four new events within Prism itself to track subscription, publishing, execution and unsubscribing of all application level events:

New Events

New Event Description
PrismSubscribeNotificationEvent Is published by Prism when your application code performs a subscribe:
C#
eventAggregator.GetEvent<MarketPricesUpdatedEvent>().
        Subscribe(MarketPricesUpdated, ThreadOption.UIThread));
PrismPublishNotificationEvent Is published by Prism when your application code performs a Publish:
C#
eventAggregator.GetEvent<TickerSymbolSelectedEvent>().
       Publish((CurrentPositionSummaryItem.TickerSymbol));
PrismSubscriberExecutionNotificationEvent Is published by Prism when it calls all of the subscribers to the event being Published.
PrismUnSubscribeNotificationEvent Is published by Prism when an event is unSubscribed in your application.

These new events are created in a new "PrismEvents" directory within the prism directory structure. By using these new events and the modifications in the core prism project, NO changes are necessary inside of your existing code base. All that is done is to drop in the Dashboard module into your application and wire it up to a menu or inject it into a region. When the dashboard is running, all it is doing is listening for these 4 new events that are being published by Prism itself. It keeps running stats on them and populates the summary and detailed list views with the event data as your events are firing.

The dashboard view itself for the purposes of this article is kept pretty generic. To make the dashboard view look like a dash board, it would be a good idea to add in controls like gauges, etc. to give it a more visual appeal, but to keep it easy to plug in and use, these kinds of things were left out for this article.

In order to utilize these new events, there were updates to a few prism classes but not very much was needed once it was fine tuned. The main class for events, "CompositePresentationEvent" was not subclassed to minimize changes within Prism. The more important goal was to minimize the changes to your applications. If this core class was subclassed, all of the references to it would have to be changed in your application when subscribing to all of your events. Since some Prism applications could contain 100s of events, I did not want to impose this burden on the developer. It was far easier to make the changes within Prism.

What's Included

What's included in the zip files for this article are the new Prism framework DLLs. These are Microsoft.Practices.Prism, Microsoft.Practices.Prism.Interactivity and Microsoft.Practices.Prism.MefExtensions. The only actual changes are in the Microsoft.Practices.Prism assembly, but all of the original DLLs that are in the Prism download from Microsoft are all strongly signed. If only one assembly is recompiled, it will not work with the other strongly signed assemblies. Microsoft does not provide the security certificate so they cannot be strongly signed again unless you get your own certificate. With the Prism DLLs provided, you'll be able to run the StockTrader demo right away. But of course what is also included are the new event classes and the changes to the existing Prism classes. I am not including a whole new copy of the Prism download. You need to download the Prism zip file from here, unzip it and then copy in the changes to the few Prism classes (discussed below). There are not many. You will get the modified Prism classes as well as the four (4) new Prism Events.

This is the DashBoard module. As you can see, it's a pretty small module. It is another project in the demo application. But it can also be kept in its own solution of course if you make it available to multiple applications. You just need to reference the one SoftwareLifeCycle.Modules.DashBoard.dll file to implement it.

DashBoard Module

There is minimal changes to the Stock Trader demo app. I just added the new Dashboard module and created a new region in the main shell.xaml file to house the new dashboard view. This is explained in detail below. It appears below the main region and is displayed when the app launches. This way, you can see all of the events from the beginning. In a real world LOB application, you may decide to put the dashboard on another tab or in a dockable window that can be docked to the side of the main window. I cleaned up the Stocktrader solution to make the download smaller. I deleted the test projects for each of the modules. I also excluded the ChartControls project from the solution and instead just reference the StockTraderRI.ChartControls.dll library where it is needed. I created a special "libs" folder under the StockTrader project directory to hold the ChartControls DLL and the modified PRISM libraries along with the Enterprise Libraries referenced in the main project. This would also be a good place to put any additional 3rd party libraries like the XCeed WPF control libraries that are mentioned at the end of the article. In short, this demo is self contained with all required DLLs and has been zipped after being run through the CleanProject utility mentioned at the end of the article. The "CleanProject" utility cleans out all of the bind\debug files and other files created during the build process.

Visual Studio Setup

The changes to the Prism framework outlined below all happen inside the \PrismFrameWork\PrismLibrary\Desktop\Prism\Events and the new directory at \PrismFrameWork\PrismLibrary\Desktop\Prism\PrismEvents. The latter directory needs to be added and the new PRISM events dropped in there. Then add them to the Prism-Desktop project file so they can be referenced by the changes to other existing Prism classes.

If any additional custom changes are made to the Prism library above what is shown here, you will need to recompile the PrismLibrary.Desktop project. The other Microsoft.Practices.Prism.Interactivity and Microsoft.Practices.Prism.MefExtensions would not have to be recompiled if you are using the ones supplied here.

In my own copy of the Prism Framework, I modified the three Prism projects that are needed for this Dashboard. I added these lines:

copy $(TargetPath) 
"C:\Users\Harold\Documents\VisualStudio\PrismFrameWork\Builds\$(TargetName).DLL"
copy $(TargetDir)$(TargetName).pdb 
"C:\Users\Harold\Documents\VisualStudio\PrismFrameWork\Builds\$(TargetName).pdb"

in the Post build event. This copies out each of the three DLLs to a central directory to hold the necessary framework libraries for the demo application. When these are used for your own projects, this approach is recommended. It makes it easier for your applications to reference the necessary DLLs in one location instead of them being spread out in various bin\debug directories. Just change the above path to reflect your central "build" directory location.

The Event Aggregator

The design philosophy behind an event aggregator is to loosely couple different modules of an application together. This makes it possible for one part of an application to simply raise an event and have the event aggregator lookup that event and publish it. There could be 1 or a 100 different event handling methods then called by the event aggregator to handle that event in different ways.

The Event Aggregator portion of the Prism framework is defined in the PrismLibrary\Desktop\Prism\Events\EventAggregator class. The aggregator is simply defined as follows:

C#
private readonly Dictionary<Type, EventBase><Type, EventBase> events = new Dictionary<Type, EventBase>();

This simply creates a dictionary that is keyed off of an Event Type and stores the definition of the event (EventBase) as the value for the key.

To start off the process of populating the event aggregator, a subscription to an event is made that associates an event with an event handler (method):

This code snippet is from StockTraderRI.Modules.Position.PositionSummary.ObservablePosition class. The first parameter of the "Subscribe" method, is the name of the method that will handle this event. Note that this is not a literal string, it is a reference to a method that has to be resolved in the editor! This is passed as a generic "Action" type. Since this reference is resolved by the compiler, it knows where the target event handler resides. The aggregator is really just a dictionary of these method references stored as delegates.

Subscription of an event

The following screenshot illustrates the kind of information the action delegate tracks. It knows everything it needs to find and execute a method when the delegates "invoke" method is called.

Image 4

Using the code snippet below, calling the GetEvent method of the aggregator first checks to see if the event (TickerSymbolSelectedEvent) is already in the event dictionary. If it is not, it is added and returned. If it already is in the dictionary, the event is returned from the dictionary using the Type of the event as the key.

When an event is published like this:

C#
eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(CurrentPositionSummaryItem.TickerSymbol);

This object, which is of type "CompositePresentationEvent" as shown below, then executes its "Publish" method and passes the payload, CurrentPositionSummaryItem.TickerSymbol, along as the payload. The "TickerSymbolSelectedEvent" is defined as follows:

C#
using Microsoft.Practices.Prism.Events;

namespace StockTraderRI.Infrastructure
{
    public class TickerSymbolSelectedEvent : CompositePresentationEvent<string>
    {
    }
}

This event is a very simple one. Its main purpose is to simply pass a string along to the event handler that has subscribed to this event being published. In this case, it is just passing a string, but that string parameter could just as easily be a more complex object with any number of properties defined.

If there are more than one subscriber to an event, there is a list of subscribers maintained for this event in the "EventBase" class.

C#
private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();

Then as subscriptions are made to an event, the "internalSubscribe" method in EventBase simply adds another entry for this subscription to the list. This is one of the beauties of the publish/subscribe approach. Multiple modules can subscribe to an event, and the event aggregator maintains a list of all of the locations by keeping the delegate reference in the above List<>. Then during publish, it simply iterates over the list of subscriptions and invokes the delegate. Since the delegate has all the information that .NET needs to locate the method being subscribed to, it can find it and execute it no matter where it is.

Image 5

Changes in Prism

There are very few changes made to the Prism source code for this new functionality. The changes are very easy to merge into the Prism 4.0 or 4.1 source code. There is very little difference between these 2 versions and by using WinMerge or comparable merge utility, it is a very easy process to create the updated code. The article download includes the new event classes and the modified Prism classes. It is recommended to unzip the supplied source code changes into a directory, download the original Prism source code, and do a file compare between the two. You will be comparing the \PrismLibrary\Desktop\Prism directory in the original source code with the updated source code provided in this article.

The following is a list of the files that have been added or modified in the Prism Framework to publish the new events. WinMerge (great free program) was used to do a file compare between the supplied modified files on the left and the original Prism files on the right.

File Differences.

Here is the directory structure for the new and updated files as supplied in this articles ZIP file. Under "NewUpdatedPrismFiles", there is a directory "PrismEvents" which are the new events shown above and there is an "Events" directory with the modified Prism classes. After you download the full PRISM framework, add in the new PrismEvents files and update the existing original Prism classes with the modified ones supplied. Very easy process!

New and Updated Prism files

Like all regular application level events, these new events are based on the Prism CompositePresentationEvent definition. As we'll see later, these events are subscribed to by the Event Dashboard viewmodel so they can update the statistics and event detail listings.

C#
using System;
using Microsoft.Practices.Prism.Events;

namespace Microsoft.Practices.Prism.PrismEvents
{
    /// <summary>
    /// Event used to notify Prism Event Visualizer when a subscription is performed.
    /// </summary>
    public class PrismSubscribeNotificationEvent : CompositePresentationEvent<PrismSubscribeNotificationEventArgs>
    {

    }

    /// <summary>
    /// Arguments for Prism Subscribe Event
    /// </summary>
    public class PrismSubscribeNotificationEventArgs : EventArgs
    {
        public object EventObj { get; set; }
        public object AppEventPayLoad { get; set; }
        public int SubscriptionToken { get; set; }
        public Delegate TargetReference { get; set; }

        /// <summary>
        ///
        /// </summary>
        /// <param name="eventObj">reference for application event being published</param>
        /// <param name="subscriptionToken">unique GUID token for this subscription</param>
        /// <param name="targetReference">The Target of the subscription.. what is going to be executed.</param>
        /// <param name="appEventPayLoad">Payload of the application level event.
        /// used to display payload values in dashboard.</param>
        public PrismSubscribeNotificationEventArgs(object eventObj,
            int subscriptionToken, Delegate targetReference = null, object appEventPayLoad = null)
        {
            EventObj = eventObj;
            AppEventPayLoad = appEventPayLoad;
            SubscriptionToken = subscriptionToken;
            TargetReference = targetReference;
        }
    }
}

using System;
using Microsoft.Practices.Prism.Events;

namespace Microsoft.Practices.Prism.PrismEvents
{
    /// <summary>
    ///   Event that will be used to Publish to PrismEventVisualizer when an application Publish occurs.
    /// </summary>
    public class PrismPublishNotificationEvent : CompositePresentationEvent<PrismPublishNotificationEventArgs>
    {

    }

    /// <summary>
    /// Custom Prism Event for Publish Notification
    /// </summary>
    public class PrismPublishNotificationEventArgs : EventArgs
    {
        public object EventObj { get; set; }
        public object AppEventPayLoad { get; set; }
        public string CallingClass { get; set; }
        public string CallingMethod { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="eventObj">reference for application event being published</param>
        /// <param name="callingClass">name of class where .Publish() was executed</param>
        /// <param name="callingMethod">name of Method where Publish() was executed</param>
        /// <param name="appEventPayLoad">Payload object from Application level event</param>
        public PrismPublishNotificationEventArgs(object eventObj,
            string callingClass = "", string callingMethod = "", object appEventPayLoad = null)
        {
            EventObj = eventObj;
            AppEventPayLoad = appEventPayLoad;
            CallingClass = callingClass;
            CallingMethod = callingMethod;
        }
    }
}
using System;
using Microsoft.Practices.Prism.Events;

namespace Microsoft.Practices.Prism.PrismEvents
{
    /// <summary>
    ///   Event that will be used to Publish to PrismEventVisualizer when an application Publish occurs.
    /// </summary>
    public class PrismSubscriberExecutionNotificationEvent :
        CompositePresentationEvent<PrismSubscriberExecutionNotificationEventArgs>
    {

    }


    /// <summary>
    /// Arguments for when Prism Executes an event handler for an event Publish
    /// </summary>
    public class PrismSubscriberExecutionNotificationEventArgs : EventArgs
    {
        public object EventObj { get; set; }
        public object AppEventPayLoad { get; set; }
        public int SubscriptionToken { get; set; }  // HashCode for event token
            // that published the event that caused execution of this method.
        public Delegate TargetReference { get; set; }
        public ThreadOption ThreadingType { get; set; }

        /// <summary>
        ///
        /// </summary>
        /// <param name="eventObj">reference for application event being published</param>
        /// <param name="subscriptionToken">unique GUID token for this subscription</param>
        /// <param name="targetReference">The Target of the subscription.. what is going to be executed.</param>
        /// <param name="appEventPayLoad">Payload of the application level event.
        /// used to display payload values in dashboard.</param>
        /// <param name="threadingType">Threading type used to run eventhandler.
        /// Displayed in dash board grid.</param>
        public PrismSubscriberExecutionNotificationEventArgs(object eventObj,
        int subscriptionToken, Delegate targetReference = null, object appEventPayLoad = null,
        ThreadOption threadingType = ThreadOption.PublisherThread)
        {
            EventObj = eventObj;
            AppEventPayLoad = appEventPayLoad;
            SubscriptionToken = subscriptionToken;
            TargetReference = targetReference;
            ThreadingType = threadingType;
        }
    }
}
using System;
using Microsoft.Practices.Prism.Events;

namespace Microsoft.Practices.Prism.PrismEvents
{
    /// <summary>
    /// Event used to notify Prism Event Visualizer when an event is unsubscribed.
    /// </summary>
    public class PrismUnSubscribeNotificationEvent :
    CompositePresentationEvent<PrismUnSubscribeNotificationEventArgs>
    {

    }

    /// <summary>
    /// Arguments for a prism UnSubscribe
    /// </summary>
    public class PrismUnSubscribeNotificationEventArgs : EventArgs
    {
        public object EventObj { get; set; }
        public string CallingClass { get; set; }
        public string CallingMethod { get; set; }

        /// <summary>
        ///
        /// </summary>
        /// <param name="eventObj">reference for application event being published</param>
        /// <param name="callingClass">Class that issues contains method that unsubscribes event</param>
        /// <param name="callingMethod">Method that unSubscribes event.</param>
        public PrismUnSubscribeNotificationEventArgs
        (object eventObj, string callingClass = "", string callingMethod = "")
        {
            EventObj = eventObj;
            CallingClass = callingClass;
            CallingMethod = callingMethod;
        }
    }
}

Moving forward, the file differences in the listings will show the new and modified code in bold red.

CompositePresentationEvent is the class that all application level events inherit from.

The changes from the Prism source are the following:

  1. Three new using directives have been added.
  2. An "eventAggregator" private field has been added to hold reference to the eventAggregator collection.
  3. A new constructor was added to obtain the reference to the eventAggregator for subsequent publishing of the new Prism Events described above.
  4. In the "Subscribe" method, a new block of code was added that publishes the new "PrismSubscribeNotificationEvent" that notifies the Event Dashboard that an application level Subscription has taken place.
  5. In the "Publish" method, a new block of code was added that publishes the new "PrismPublishNotificationEvent" that notifies the Event Dashboard that an application level Publish has taken place. As part of the event payload, it also determines the calling class and method of the application level publish. This is so you know exactly where it was called from. Also passed along with this payload is the application level payload that is passed into the Publish method from the application. This is used on the DashBoard to display the values of the application level payload values. This is very helpful in debugging events. Also a reference to the application level itself is passed along.'
  6. Finally in the "UnSubscribe" method, a new block of code has been added to publish to the dashboard when an application level un-subscribe has been executed on an event. The calling class and method are also passed along in the payload along with the application level event reference.
C#
//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows.Threading;
using Microsoft.Practices.Prism.PrismEvents;
using Microsoft.Practices.ServiceLocation;

namespace Microsoft.Practices.Prism.Events
{
    /// <summary>
    /// Defines a class that manages publication and subscription to events.
    /// </summary>
    /// <typeparam name="TPayload">The type of message
    /// that will be passed to the subscribers.</typeparam>
    public class CompositePresentationEvent<TPayload> : EventBase
    {
        private IDispatcherFacade uiDispatcher;
        private IEventAggregator eventAggregator;

        /// <summary>
        /// Gets the <see cref="Dispatcher"/> that is bound to the UI elements.
        /// </summary>
        /// <value>The <see cref="Dispatcher"/> to use when
        /// subscribing using <see cref="ThreadOption.UIThread"/>.</value>
        private IDispatcherFacade UIDispatcher
        {
            get
            {
                if (uiDispatcher == null)
                {
                    this.uiDispatcher = new DefaultDispatcher();
                }

                return uiDispatcher;
            }
        }

        /// <summary>
        /// Constructor used to get reference to eventaggregator.
        /// </summary>
        public CompositePresentationEvent()
        {
            // need to use servicelocator in Try/Catch instead of checking if ServiceLocator.Current is !null
            // because of this: http://commonservicelocator.codeplex.com/workitem/9676 known problem in PRISM.
            try
            {
                eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error occurred getting event aggregator: {0}", ex);
            }
        }

        /// <summary>
        /// Subscribes a delegate to an event that will be published on the
        /// <see cref="ThreadOption.PublisherThread"/>.
        /// <see cref="CompositePresentationEvent{TPayload}"/> will maintain a
        /// <seealso cref="WeakReference"/> to the target of the supplied <paramref name="action"/> delegate.
        /// </summary>
        /// <param name="action">The delegate that gets executed when the event is published.</param>
        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
        /// <remarks>
        /// The CompositePresentationEvent collection is thread-safe.
        /// </remarks>
        public SubscriptionToken Subscribe(Action<TPayload> action)
        {
            return Subscribe(action, ThreadOption.PublisherThread);
        }

        /// <summary>
        /// Subscribes a delegate to an event.
        /// CompositePresentationEvent will maintain a <seealso cref="WeakReference"/>
        /// to the Target of the supplied <paramref name="action"/> delegate.
        /// </summary>
        /// <param name="action">
        /// The delegate that gets executed when the event is raised.</param>
        /// <param name="threadOption">
        /// Specifies on which thread to receive the delegate callback.</param>
        /// <returns>A <see cref="SubscriptionToken"/> 
        /// that uniquely identifies the added subscription.</returns>
        /// <remarks>
        /// The CompositePresentationEvent collection is thread-safe.
        /// </remarks>
        public SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption)
        {
            return Subscribe(action, threadOption, false);
        }

        /// <summary>
        /// Subscribes a delegate to an event that will be published on the 
        /// <see cref="ThreadOption.PublisherThread"/>.
        /// </summary>
        /// <param name="action">The delegate that gets executed when the event is published.</param>
        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>,
        /// the <seealso cref="CompositePresentationEvent{TPayload}"/> keeps a reference
        /// to the subscriber so it does not get garbage collected.</param>
        /// <returns>A <see cref="SubscriptionToken"/> 
        /// that uniquely identifies the added subscription.</returns>
        /// <remarks>
        /// If <paramref name="keepSubscriberReferenceAlive"/> 
        /// is set to <see langword="false" />,
        /// <see cref="CompositePresentationEvent{TPayload}"/> 
        /// will maintain a <seealso cref="WeakReference"/>
        /// to the Target of the supplied <paramref name="action"/> delegate.
        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is
        /// <see langword="true" />), the user must explicitly call Unsubscribe for the event when
        /// disposing the subscriber in order to avoid memory leaks or unexpected behavior.
        /// <para/>
        /// The CompositePresentationEvent collection is thread-safe.
        /// </remarks>
        public SubscriptionToken Subscribe(Action<TPayload> action, bool keepSubscriberReferenceAlive)
        {
            return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
        }

        /// <summary>
        /// Subscribes a delegate to an event.
        /// </summary>
        /// <param name="action">The delegate that gets executed when the event is published.</param>
        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>,
        /// the <seealso cref="CompositePresentationEvent{TPayload}"/> keeps a reference
        /// to the subscriber so it does not get garbage collected.</param>
        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
        /// <remarks>
        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />,
        /// <see cref="CompositePresentationEvent{TPayload}"/> will maintain a
        /// <seealso cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is
        /// <see langword="true" />), the user must explicitly call Unsubscribe for the event
        /// when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
        /// <para/>
        /// The CompositePresentationEvent collection is thread-safe.
        /// </remarks>
        public SubscriptionToken Subscribe(Action<TPayload> action, 
            ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            return Subscribe(action, threadOption, keepSubscriberReferenceAlive, null);
        }

        /// <summary>
        /// Subscribes a delegate to an event.
        /// </summary>
        /// <param name="action">The delegate that gets executed when the event is published.</param>
        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>,
        /// the <seealso cref="CompositePresentationEvent{TPayload}"/> keeps a reference
        /// to the subscriber so it does not get garbage collected.</param>
        /// <param name="filter">Filter to evaluate 
        /// if the subscriber should receive the event.</param>
        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
        /// <remarks>
        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />,
        /// <see cref="CompositePresentationEvent{TPayload}"/> will maintain a
        /// <seealso cref="WeakReference"/> to the Target 
        /// of the supplied <paramref name="action"/> delegate.
        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/>
        /// is <see langword="true" />), the user must explicitly call Unsubscribe for the event
        /// when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
        ///
        /// The CompositePresentationEvent collection is thread-safe.
        /// </remarks>
        public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption,
            bool keepSubscriberReferenceAlive, Predicate<TPayload> filter)
        {

            IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);
            IDelegateReference filterReference;
            if (filter != null)
            {
                filterReference = new DelegateReference(filter, keepSubscriberReferenceAlive);
            }
            else
            {
                filterReference = new DelegateReference(new Predicate<TPayload>(delegate { return true; }), true);
            }
            EventSubscription<TPayload> subscription;

            <a id="#ThreadChoice">switch (threadOption)
            {
                case ThreadOption.PublisherThread:
                    subscription = new EventSubscription<TPayload>(actionReference, filterReference);
                    break;
                case ThreadOption.BackgroundThread:
                    subscription = new BackgroundEventSubscription<TPayload>(actionReference, filterReference);
                    break;
                case ThreadOption.UIThread:
                    subscription = new DispatcherEventSubscription<TPayload>(actionReference, filterReference, UIDispatcher);
                    break;
                default:
                    subscription = new EventSubscription<TPayload>(actionReference, filterReference);
                    break;
            }

            SubscriptionToken token = base.InternalSubscribe(subscription, threadOption);


            // don't notify about subscribing to the Prism events!
            if (eventAggregator != null && !(this is PrismUnSubscribeNotificationEvent)
            && !(this is PrismSubscribeNotificationEvent) && !(this is PrismPublishNotificationEvent)
            && !(this is PrismSubscriberExecutionNotificationEvent))
            {
                var internalSubscribeNotificationEvent = eventAggregator.GetEvent<PrismSubscribeNotificationEvent>();
                // call internalpublish so Prism Event Visualizer can handle it!
                if (internalSubscribeNotificationEvent != null)
                {
                    internalSubscribeNotificationEvent.InternalPublish(new PrismSubscribeNotificationEventArgs
                    (subscriptionToken: token.GetHashCode(), targetReference: subscription.Action, eventObj: this));
                }
            }

            return token;
        }

        /// <summary>
        /// Publishes the <see cref="CompositePresentationEvent{TPayload}"/>.
        /// </summary>
        /// <param name="payload">Message to pass to the subscribers.</param>
        public virtual void Publish(TPayload payload)
        {

            // publish original event and PrismSubscriberExecutionNotificationEvent for each subscriber...
            // filter out publishing notification of these internal logging events!
            try
            {
                if (eventAggregator != null && !(this is PrismUnSubscribeNotificationEvent)
                && !(this is PrismSubscribeNotificationEvent) && !(this is PrismPublishNotificationEvent)
                && !(this is PrismSubscriberExecutionNotificationEvent))
                {

                    // get calling method name. one level up from this...
                    StackTrace stackTrace = new StackTrace();
                    string callingClass = string.Empty;
                    string callingMethodName = string.Empty;

                    if (stackTrace != null && stackTrace.GetFrames().Count() >
                    2 && stackTrace.GetFrame(1).GetMethod().DeclaringType != null)
                    {
                        callingClass = stackTrace.GetFrame(1).GetMethod().DeclaringType.FullName;
                        callingMethodName = stackTrace.GetFrame(1).GetMethod().Name;
                    }

                    // Get Internal publish Event...
                    var internalPublishNotificationEvent = eventAggregator.GetEvent<PrismPublishNotificationEvent>();
                    // call internalpublish so Prism Event Visualizer can handle it!
                    // this publishes the notification that an application level event was published....
                    if (internalPublishNotificationEvent != null)
                    {
                        internalPublishNotificationEvent.InternalPublish(new PrismPublishNotificationEventArgs
                        (eventObj: this, callingClass: callingClass, callingMethod: callingMethodName, appEventPayLoad: payload));
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error occurred during publish notification: {0}", ex);
            }
            base.InternalPublish(payload);
        }

        /// <summary>
        /// Removes the first subscriber matching <seealso cref="Action{TPayload}"/> from the subscribers' list.
        /// </summary>
        /// <param name="subscriber">The <see cref="Action{TPayload}"/> 
        /// used when subscribing to the event.</param>
        public virtual void Unsubscribe(Action<TPayload> subscriber)
        {
            lock (Subscriptions)
            {
                IEventSubscription eventSubscription = Subscriptions.Cast<EventSubscription<TPayload>>().
                FirstOrDefault(evt => evt.Action == subscriber);
                if (eventSubscription != null)
                {
                    Subscriptions.Remove(eventSubscription);

                    if (eventAggregator != null && !(this is PrismUnSubscribeNotificationEvent) &&
                    !(this is PrismSubscribeNotificationEvent) && !(this is PrismPublishNotificationEvent) &&
                    !(this is PrismSubscriberExecutionNotificationEvent))
                    {
                        // get calling method name. one level up from this...
                        StackTrace stackTrace = new StackTrace();
                        string callingClass = stackTrace.GetFrame(1).GetMethod().DeclaringType.FullName;
                        string callingMethodName = stackTrace.GetFrame(1).GetMethod().Name;

                        // Get Internal publish Event...
                        var internalUnSubscribeNotificationEvent = eventAggregator.GetEvent<PrismUnSubscribeNotificationEvent>();

                        // call internalpublish so Prism Event Visualizer can handle it!
                        // this publishes the notification that an application level un-subscribe was published....
                        if (internalUnSubscribeNotificationEvent != null)
                        {
                            internalUnSubscribeNotificationEvent.InternalPublish
                            (new PrismUnSubscribeNotificationEventArgs(eventObj: this,
                            callingClass: callingClass, callingMethod: callingMethodName));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Returns <see langword="true"/> 
        /// if there is a subscriber matching <seealso cref="Action{TPayload}"/>.
        /// </summary>
        /// <param name="subscriber">The <see cref="Action{TPayload}"/>
        /// used when subscribing to the event.</param>
        /// <returns><see langword="true"/> if there is an
        /// <seealso cref="Action{TPayload}"/> that matches; 
        /// otherwise <see langword="false"/>.</returns>
        public virtual bool Contains(Action<TPayload> subscriber)
        {
            IEventSubscription eventSubscription;
            lock (Subscriptions)
            {
                eventSubscription = Subscriptions.Cast<EventSubscription<TPayload>>().
                FirstOrDefault(evt => evt.Action == subscriber);
            }
            return eventSubscription != null;
        }

    }
}

IEventSubscription is the interface for EventSubscription, DispatcherEventSubscription and BackgroundEventSubscription. The only change in this interface is the addition of the EventRef property. This is used to store the reference to the application level event so it can be sent as part of the payload to the Event DashBoard.

C#
using System;

namespace Microsoft.Practices.Prism.Events
{
    ///<summary>
    /// Defines a contract for an event subscription to be used by <see cref="EventBase"/>.
    ///</summary>
    public interface IEventSubscription
    {
        /// <summary>
        /// Gets or sets a <see cref="Events.SubscriptionToken"/>
        /// that identifies this <see cref="IEventSubscription"/>.
        /// </summary>
        /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
        SubscriptionToken SubscriptionToken { get; set; }

        /// <summary>
        /// store EventObj with subscription so it can be sent with PrismSubscriberExecutionNotificationEvent.
        /// </summary>
        EventBase EventRef { get; set; }

        /// <summary>
        /// Gets the execution strategy to publish this event.
        /// </summary>
        /// <returns>An <see cref="Action{T}"/> with the execution strategy, or
        /// <see langword="null" /> if the 
        /// <see cref="IEventSubscription"/> is no longer valid.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage
        ("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        Action<object[]> GetExecutionStrategy();
    }
}

In EventSubscription, there are only a few updates. The addition of 3 new namespaces, the setting of the "EventRef" from the interface above, and calling the new method "PublishSubscriptionExecutionNotificationEvent" from the "InvokeAction" method.

C#
//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using System.Globalization;
using Microsoft.Practices.Prism.PrismEvents;
using Microsoft.Practices.Prism.Properties;
using Microsoft.Practices.ServiceLocation;
using System.Diagnostics;

namespace Microsoft.Practices.Prism.Events
{
    /// <summary>
    /// Provides a way to retrieve a <see cref="Delegate"/> to execute an action depending
    /// on the value of a second filter predicate that returns true if the action should execute.
    /// </summary>
    /// <typeparam name="TPayload">The type to use for the generic
    /// <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.
    /// </typeparam>
    public class EventSubscription<TPayload> : IEventSubscription
    {
        private readonly IDelegateReference _actionReference;
        private readonly IDelegateReference _filterReference;

        // keep reference to Event to pass when published.
        public EventBase EventRef { get; set; }

        ///<summary>
        /// Creates a new instance of <see cref="EventSubscription{TPayload}"/>.
        ///</summary>
        ///<param name="actionReference">A reference to a delegate of type
        ///<see cref="System.Action{TPayload}"/>.</param>
        ///<param name="filterReference">A reference to a delegate of type
        ///<see cref="Predicate{TPayload}"/>.</param>
        ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or
        ///<see paramref="filterReference"/> are <see langword="null" />.</exception>
        ///<exception cref="ArgumentException">When the target of
        ///<paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
        ///or the target of <paramref name="filterReference"/> 
        ///is not of type <see cref="Predicate{TPayload}"/>.
        ///</exception>
        public EventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
        {

            if (actionReference == null)
                throw new ArgumentNullException("actionReference");
            if (!(actionReference.Target is Action<TPayload>))
                throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
                Resources.InvalidDelegateRerefenceTypeException, typeof
                (Action<TPayload>).FullName), "actionReference");

            if (filterReference == null)
                throw new ArgumentNullException("filterReference");
            if (!(filterReference.Target is Predicate<TPayload>))
                throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
                Resources.InvalidDelegateRerefenceTypeException, 
                typeof(Predicate<TPayload>).FullName), "filterReference");

            _actionReference = actionReference;
            _filterReference = filterReference;
        }

        /// <summary>
        /// Gets the target <see cref="System.Action{T}"/>
        /// that is referenced by the <see cref="IDelegateReference"/>.
        /// </summary>
        /// <value>An <see cref="System.Action{T}"/> or <see langword="null" />
        /// if the referenced target is not alive.</value>
        public Action<TPayload> Action
        {
            get { return (Action<TPayload>)_actionReference.Target; }
        }

        /// <summary>
        /// Gets the target <see cref="Predicate{T}"/> 
        /// that is referenced by the <see cref="IDelegateReference"/>.
        /// </summary>
        /// <value>An <see cref="Predicate{T}"/> or
        /// <see langword="null" /> if the referenced target is not alive.</value>
        public Predicate<TPayload> Filter
        {
            get { return (Predicate<TPayload>)_filterReference.Target; }
        }

        /// <summary>
        /// Gets or sets a <see cref="Events.SubscriptionToken"/>
        /// that identifies this <see cref="IEventSubscription"/>.
        /// </summary>
        /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
        public SubscriptionToken SubscriptionToken { get; set; }

        /// <summary>
        /// Gets the execution strategy to publish this event.
        /// </summary>
        /// <returns>An <see cref="System.Action{T}"/>
        /// with the execution strategy, or <see langword="null" />
        /// if the <see cref="IEventSubscription"/> is no longer valid.</returns>
        /// <remarks>
        /// If <see cref="Action"/> or <see cref="Filter"/> are no longer valid because they were
        /// garbage collected, this method will return <see langword="null" />.
        /// Otherwise it will return a delegate that evaluates the <see cref="Filter"/> and if it
        /// returns <see langword="true" /> will then call <see cref="InvokeAction"/>. The returned
        /// delegate holds hard references to the <see cref="Action"/> and <see cref="Filter"/> target
        /// <see cref="Delegate">delegates</see>. As long as the returned delegate is not garbage collected,
        /// the <see cref="Action"/> and <see cref="Filter"/> references delegates won't get collected either.
        /// </remarks>
        public virtual Action<object[]> GetExecutionStrategy()
        {
            Action<TPayload> action = this.Action;

            Predicate<TPayload> filter = this.Filter;
            if (action != null && filter != null)
            {
                return arguments =>
                           {
                               TPayload argument = default(TPayload);
                               if (arguments != null && arguments.Length > 0 && arguments[0] != null)
                               {
                                   argument = (TPayload)arguments[0];
                               }
                               if (filter(argument))
                               {
                                   InvokeAction(action, argument);
                               }
                           };
            }
            return null;
        }

        /// <summary>
        /// Invokes the specified <see cref="System.Action{TPayload}"/> synchronously when not overridden.
        /// </summary>
        /// <param name="action">The action to execute.</param>
        /// <param name="argument">The payload to pass
        /// <paramref name="action"/> while invoking it.</param>
        /// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/>
        /// is thrown if <paramref name="action"/> is null.</exception>
        public virtual void InvokeAction(Action<TPayload> action, TPayload argument)
        {
            if (action == null) throw new System.ArgumentNullException("action");
            action(argument);

            PublishSubscriptionExecutionNotificationEvent(argument, ThreadOption.PublisherThread);
        }

        /// <summary>
        /// called when ever a subscribed event handler is executed. Handled on client side in Event Dashboard module.
        /// </summary>
        /// <param name="argument"></param>
        protected void PublishSubscriptionExecutionNotificationEvent(TPayload argument, ThreadOption threading)
        {
            try
            {
                // don't publish notifications for the DashBoard Events!.
                if (!(argument is PrismPublishNotificationEventArgs) &&
                !(argument is PrismSubscribeNotificationEventArgs) &&
                !(argument is PrismSubscriberExecutionNotificationEventArgs) &&
                !(argument is PrismUnSubscribeNotificationEventArgs))
                {
                    IEventAggregator eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
                    var SubscriptionExecutionNotificationEvent =
                    eventAggregator.GetEvent<PrismSubscriberExecutionNotificationEvent>();

                    if (SubscriptionExecutionNotificationEvent != null)
                    {
                        // Pass the token of the event being published to the subscribingExecutionEvent.
                        SubscriptionExecutionNotificationEvent.Publish(
                                    new PrismSubscriberExecutionNotificationEventArgs
                                    (targetReference: Action, eventObj: this.EventRef,
                                    subscriptionToken: this.SubscriptionToken.GetHashCode(),
                                    appEventPayLoad: argument, threadingType: threading));
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error occurred during Execution Notification event {0}", ex);
            }

        }<a id="PublishSubscriptionExecutionNotificationEvent"/>
    }
}

In the "DispatcherEventSubscription" method, the only change is the call to the "PublishSubscriptionExecutionNotificationEvent" as shown below in the "InvokeAction" method. "PublishSubscriptionExecutionNotificationEvent" is in the "EventSubscription" class above which serves as a base class to "DispatcherEventSubscription" and "BackGroundEventSubscription" shown below.

C#
/// <summary>
/// Invokes the specified <see cref="System.Action{TPayload}"/>
/// asynchronously in the specified <see cref="Dispatcher"/>.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <param name="argument">The payload to pass
/// <paramref name="action"/> while invoking it.</param>
public override void InvokeAction(Action<TPayload> action, TPayload argument)
   {
      dispatcher.BeginInvoke(action, argument);
      PublishSubscriptionExecutionNotificationEvent(argument, ThreadOption.UIThread);
   }

The "PublishSubscriptionExecutionNotificationEvent" method is located in the "EventSubscription" class. This class is the base class for the "BackgroundEventSubscription" and the "DispatcherEventSubscription". Which one of these is called is determined by the second parameter when initially subscribing to an event. You can specify whether it runs on the background, UI or Publisher thread. Depending on which one, this code in the "Subscribe" method in "CompositePresentationEvent" will instantiate the correct subclassed EventSubscription:

C#
switch (threadOption)
           {
               case ThreadOption.PublisherThread:
                   subscription = new EventSubscription<TPayload>(actionReference, filterReference);
                   break;
               case ThreadOption.BackgroundThread:
                   subscription = new BackgroundEventSubscription<TPayload>(actionReference, filterReference);
                   break;
               case ThreadOption.UIThread:
                   subscription = new DispatcherEventSubscription<TPayload>(actionReference, filterReference, UIDispatcher);
                   break;
               default:
                   subscription = new EventSubscription<TPayload>(actionReference, filterReference);
                   break;
           }

In the "BackgroundEventSubscription", there is an identical change in the "InvokeAction" method:

C#
public override void InvokeAction(Action<TPayload> action, TPayload argument)
       {
           ThreadPool.QueueUserWorkItem((o) => action(argument));
           PublishSubscriptionExecutionNotificationEvent(argument, ThreadOption.BackgroundThread);
       }

Notice that the only change between this version and the version in "DispatcherEventSubscription" above, is the second parameter in the call to "PrismSubscriptionExecutionNotificationEvent". Here is on the background thread, and in "DispatcherEventSubscription" it is on the UIThread.

Remember that when "InternalPublish" is called from "CompositePresentationEvent" as shown above, the "InternalPublish" method iterates over the collection of subscriptions to that event being published and the above "InvokeAction" is executed for each event handler that is subscribed to. So when each event handler is being executed, the PublishSubscriptionExecutionNotificationEvent is also being published and then logged in the Event Dashboard.

Using the DashBoard in an Application

To use the new DashBoard view itself in your application involves just a few changes. You need to add the reference to the DashBoard Module.

Image 8

And you need to create a content control and specify that it use the region "DashBoardRegion" as shown below.

Event Dashboard View

The Event Dashboard is a simple view with a backing viewmodel. All of the code for the XAML is basic straightforward XAML that is available in the supplied source. As a summary, here is a screen shot of the PrismEventDashBoardView.xaml.

Image 9

This view is injected into the Shell.xaml file in the main StockTraderRI project by using this block of XAML:

xal
<ContentControl x:Name="EventDashBoard" cal:RegionManager.RegionName=
"{x:Static inf:RegionNames.DashBoardRegion}" Grid.Row="3" Margin="10,10,10,10">
                <ContentControl.Template>
                    <ControlTemplate TargetType="ContentControl">
                        <Grid>
                            <Controls:RoundedBox />
                            <ContentPresenter  Content="{TemplateBinding Content}" />
                        </Grid>
                    </ControlTemplate>
                </ContentControl.Template>
            </ContentControl>

The above new region, "RegionNames.DashBoardRegion" establishes where the dashboard will be injected. Then in the " PrismEventDashBoardView.xaml.cs" file, the view's class is decorated with the following attributes:

C#
[ViewExport(RegionName = RegionNames.DashBoardRegion)]
   [PartCreationPolicy(CreationPolicy.NonShared)]
   public partial class PrismEventDashBoardView

The reference to "RegionNames.DashBoardRegion" is added to the "RegionNames" class that is in the StockTraderRI.Infrastructure project. It's a good idea to have a similar class to this in your own project to define the various regions that are used by Prism.

By Exporting the view with the ViewExport attribute, this is telling Prism that this view should be cataloged so that it can be later injected into the region defined above in the Shell.xaml file. This is one of the jobs of Prism... to maintain a catalog of these exported views and then inject them into named regions at runtime for final composition of all the parts... pretty cool.

Basically the view consists of 2 Observable collections, "EventExecutionSummaryCollection" and "EventDetailsCollection". In the upper right, it uses 4 properties to display the tallies for each of the 4 new Prism Events... Subscriptions, Publishes, Executions and Unsubscribes. These properties are incremented as each event is raised by Prism. In the lower right are the details for the currently selected event in the "Event Executions Details" grid in the lower left. The "Additions Event Details" it will show the target class, Module name, location on disk for the DLL, the calling class and method. On the "Payload Data" tab, it will display the payload data for the application level event that has executed. Since this payload data can be substantial, I created a "Refresh" button. Click on this when you need to see the data for the current executing event. First time around, I was pulling out the payload as the events loaded, but this actually created memory allocation errors when using StringBuilder so I had to go this route. Very reasonable trade off.

"EventExecutionSummaryCollection" and "EventDetailsCollection" are both Observable collections since these need to be updated in real time as the application level events are handled by the dash board. "EventExecutionSummaryCollection" is a collection of "EventExecutionSummaryViewModel".

The following XAML snippet defines the lower left grid "Event Execution Details". The "EventDetailsCollection" is an observable collection of the "EventDetailsViewModel" class. The SelectedItem binding uses the "SelectedEvent" property which represents the current instance of the single "EventDetailsViewModel" item that is selected in the ListView.

C#
<ListView  x:Name="Events"
       Height="300"
       ItemsSource="{Binding EventDetailsCollection}"
       SelectedItem="{Binding SelectedEvent, Mode=TwoWay}"
       ScrollViewer.VerticalScrollBarVisibility="Auto"
       ScrollViewer.HorizontalScrollBarVisibility="Visible">

Then all of the fields that are referenced on the right in the "Additional Event Details" group box are bound to the "SelectedEvent" object and one of the contained properties. For example, the "Module Name" textblock is referenced this way:

C#
<TextBlock Grid.Column="1"
                         Grid.Row="1"
                         TextAlignment="Left"
                         Text="{Binding SelectedEvent.ModuleName}" />

Event DashBoard ViewModel

The backing view model is where all the work is done. After the view has been imported into the region, the "SubscribeEvents" method is called via the "OnImportsSatisfied" method. This method is called by the Prism Framework when the view has been fully loaded. This is made possible by having the viewmodel inherit from "IPartImportsSatisfiedNotification" like this:

C#
public class PrismEventDashBoardViewModel : ViewModelBase, IPartImportsSatisfiedNotification

The "SubscribeEvents" method hooks up the event handlers in this viewmodel to these events when they are published from Prism Event Aggregator:

C#
private void SubscribeEvents()
       {
           // Internal Prism Events for notification of Publish/Subscribe calls.
           PrismSubscribeNotificationEvent prismSubscribeNotificationEvent =
               EventAggregator.GetEvent<PrismSubscribeNotificationEvent>();
           prismSubscribeNotificationEvent.Subscribe
               (PrismSubscribeNotificationEventHandler, ThreadOption.UIThread, false);

           PrismUnSubscribeNotificationEvent prismUnSubscribeNotificationEvent =
               EventAggregator.GetEvent<PrismUnSubscribeNotificationEvent>();
           prismUnSubscribeNotificationEvent.Subscribe
               (PrismUnSubscribeNotificationEventHandler, ThreadOption.UIThread, false);

           PrismPublishNotificationEvent prismPublishNotificationEvent =
               EventAggregator.GetEvent<PrismPublishNotificationEvent>();
           prismPublishNotificationEvent.Subscribe
               (PrismPublishNotificationEventHandler, ThreadOption.UIThread, false);

           PrismSubscriberExecutionNotificationEvent prismSubscriberExecutionNotificationEvent =
               EventAggregator.GetEvent<PrismSubscriberExecutionNotificationEvent>();
           prismSubscriberExecutionNotificationEvent.Subscribe(PrismSubscriberExecutionNotificationEventHandler,
                                                               ThreadOption.UIThread, false);

       }

These are the 4 new events that have been created in the Prism Event Aggregator in the beginning of the article. These events are only raised by the Prism framework itself and not from within your application.

The "ViewModelBase" that the PrismEventDashBoardViewModel inherits from is a basic view model base class. This serves to provide the reference to the event aggregator and to implement the "IsActive" property from the "IActiveAware" Prism supplied interface. This is set to true by the Prism framework when the view has become active.

C#
using System;
using Microsoft.Practices.Prism;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.ViewModel;
using Microsoft.Practices.ServiceLocation;

namespace StockTraderRI.Modules.EventDashBoard.ViewModels
{
    public class ViewModelBase : NotificationObject, IActiveAware
    {
        private IEventAggregator _eventAggregator;
        public event EventHandler IsActiveChanged = delegate { };

        public IEventAggregator EventAggregator
        {
            get { return _eventAggregator ??
            (_eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>()); }

        }

        private bool isActive;
        public bool IsActive
        {
            get { return isActive; }
            set
            {
                if (isActive != value)
                {
                    isActive = value;
                    if (this.IsActiveChanged != null)
                    {
                        IsActiveChanged(this, EventArgs.Empty);
                    }
                }
            }
        }
    }
}

New Prism Event Handlers

The backbone of this view model are the event handlers for the 4 new Prism Events. These are executed when the Prism Event Aggregator is subscribing to an event, publishing an event, executing an event and unsubscribing from an event. Let's take a look at each of them.

The "PrismSubscribeNotificationEventHandler" is executed anytime there is a subscription made in the application level code such as this:

C#
eventAggregator.GetEvent<MarketPricesUpdatedEvent>().Subscribe(MarketPricesUpdated, ThreadOption.UIThread);

Assuming the "args" parameter is not null, a new instance of the "EventDetailViewModel" is instantiated. This is the backing view for the information shown in the Event Execution Details grid. There is an enumeration of "EventTypes" that is used to specify that this EventType is of type Subscription. Using "GetType()" on the EventObj parameter, the name of the Event is determined. Then using the "TargetReference" property, the Method Name and the Target Name of the handler method is returned.

C#
private void PrismSubscribeNotificationEventHandler(PrismSubscribeNotificationEventArgs args)
       {
           if (args != null)
           {
               SubscriptionTally++;
               EventDetailViewModel detail = new EventDetailViewModel();
               detail.EventType = Enums.EventTypes.Subscription;
               detail.EventName = args.EventObj.GetType().ToString();
               detail.MethodName = args.TargetReference.Method.Name;
               detail.TargetName = args.TargetReference.Target != null ?
               args.TargetReference.Target.ToString() : "Anonymous";
               EventDetailsCollection.Add(detail);
           }
       }

The Target Name is the name of the class that contains the Method name that will perform the handling of this event. If an anonymous method is used instead of a named event handing method, the args.TargetReference.Target property will be null. In this case, the string "Anonymous" is returned instead and this will be displayed as the "Target" in the Detail tab to the right of the Detail grid. This new instance of the EventDetailViewModel is then added to the ObservableCollection "EventDetailsCollection". The "SubscriptionTally" property is also incremented so the running total of subscriptions will be displayed in the upper right panel.

Subscription Tally and Target

The "PrismPublishNotificationEventHandler" event handler is executed whenever your application level code performs a Publish operation like this:

C#
eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(CurrentPositionSummaryItem.TickerSymbol);

The same pattern is used here. The "PublishTally" property is incremented and a new EventDetailViewModel instance is instantiated. As shown in the above listing for the "CompositePresentationEvent" class, the CallingMethod and CallingClass are determined in the "Publish" method of "CompositePresentionEvent" using the StackTrace class in the System.Diagnostic namespace.

C#
private void PrismPublishNotificationEventHandler(PrismPublishNotificationEventArgs args)
       {
           if (args != null)
           {
               PublishTally++;

               EventDetailViewModel detail = new EventDetailViewModel();
               detail.CallingMethod = args.CallingMethod;
               detail.CallingClass = args.CallingClass;
               detail.EventType = Enums.EventTypes.Publish;
               detail.EventName = args.EventObj != null ? args.EventObj.GetType().ToString() : "";

               EventDetailsCollection.Add(detail);
           }
       }

Publish Tally

The next handler, "PrismSubscriberExecutionNotificationEventHandler", provides the key details in the running of events in your application. As was shown above, when an event is subscribed to, the "InternalSubscribe" method in "EventBase" is executed by a call from the "Subscribe" method in the "CompositePresentationEvent" class. This "InternalSubscribe" adds an entry to the "Subscriptions" collection as shown here:

C#
 protected virtual SubscriptionToken InternalSubscribe
(IEventSubscription eventSubscription, ThreadOption threadingType)
        {
            if (eventSubscription == null) throw new System.ArgumentNullException("eventSubscription");

            eventSubscription.SubscriptionToken = new SubscriptionToken(Unsubscribe);
            eventSubscription.EventRef = this;

            lock (Subscriptions)
            {
                Subscriptions.Add(eventSubscription);
            }
            return eventSubscription.SubscriptionToken;
        }

It is this collection of subscriptions for an event that is iterated over when a "Publish" event is raised for an event. In the listing above for the "CompositePresentationEvent" class, the "Publish" method first publishes the new Prism Event "PrismPublishNotificationEvent". It then calls the "internalPublish" event in the "EventBase" class. Doing it in this order will show the original application level event and then its subscribers being executed in the "Event Execution Details" grid in the proper order.

The "InternalPublish" method in "EventBase" starts by calling the "PruneAndReturnStrategies" method in the same class. This method iterates over the subscriptions associated with the current event and determines for each subscription whether or not it should be executed. When subscribing to an event, there is an optional 4th parameter for a filter expression. You may only want the subscribed event to be executed under certain circumstances. The "PruneAndReturnStrategies" method evaluates this filter and if it's true, it will return that subscription instance to the List<> of events to be executed. Once this list is created, the "foreach" loop below will then invoke each handler in turn. The "executionStrategy" var below is a delegate to the event handler.

C#
protected virtual void InternalPublish(params object[] arguments)
        {
            List<Action<object[]>> executionStrategies = PruneAndReturnStrategies();
            foreach (var executionStrategy in executionStrategies)
            {
                executionStrategy(arguments);
            }
        }

Each EventSubscription type (EventSubscription, BackgroundEventSubscription, DispatcherEventSubscription) has their own version of "InvokeAction" that executes the event handler on the proper thread. It then calls the PublishSubscriptionExecutionNotificationEvent method in the "EventSubscription" class to publish the new Prism event "PrismSubscriberExecutionNotificationEvent".

Here is the "InvokeAction" method for the "BackGroundEventSubscription" class. Which one of these versions of "EventSubscription" will be used is determined here in the "Subscribe" method in the "CompositePresentationEvent" class.

C#
public override void InvokeAction(Action<TPayload> action, TPayload argument)
       {
           ThreadPool.QueueUserWorkItem((o) => action(argument));
           PublishSubscriptionExecutionNotificationEvent(argument, ThreadOption.BackgroundThread);
       }

The handler for the new Prism Event "PrismSubscriberExecutionNotificationEventHandler" is shown below. This handler handles the event payload of the original application level event so that it can be displayed in the "Additional Event Details" group box to the right of any particular event. Having access to this original event payload can be very helpful in troubleshooting errors. In order to display the values of the properties of the event payload, I modified a version of the "ObjectDumper" class that is used in the "Official Visual Studio 2010" samples download. It's modified to output a string for all of the class properties so it can be displayed in a textbox correctly.

PayLoad Data

When sitting on an Execution Detail record and you click on the "Payload Data" tab, to see the data just click on the "Refresh" button. Getting the payload data only when needed solved some memory issues that occurred when allocating all of the memory for potentially hundreds of events.

The "RefreshEventPayloadCommandExecute" method is bound to the above "Refresh" button and simply calls the "ObjectDumper" utility class. You pass in the object reference and it traverses the object tree of the class listing out each property and its value.

C#
private void RefreshEventPayloadCommandExecute()
       {
           if (SelectedEvent != null && SelectedEvent.Arguments != null)
           {
               SelectedEvent.PayloadValues = ObjectDumper.Write(SelectedEvent.Arguments, 2);
               RaisePropertyChanged(() => SelectedEvent);
           }
       }

With the parameters that are passed to this event handler, details such as the application level event handler, the class where it resides and its location on disk are all known. These details are assigned to a new instance of the "EventDetailViewModel" class and added to the "EventDetailsCollection" ObservableCollection.

The "SubscriptionToken" value, which is just a GUID created when an event is subscribed to, is used as a key in the "EventExecutionSummaryCollection". So the first time a subscription to an event handler is handled by this method, it does not yet exist in the "EventExecutionSummaryCollection". So it is added to this collection and the "ExecuteCounter" value is set to 1. If it is executed again, the Guid lookup will succeed and the counter is incremented. These results are displayed in the "Event Execution Summary" group box in the upper left of the dashboard.

C#
/// <summary>
       ///   Handle when a Publish Event is raised
       /// </summary>
       /// <param name="args"></param>
       private void PrismSubscriberExecutionNotificationEventHandler(PrismSubscriberExecutionNotificationEventArgs args)
       {
           if (args != null)
           {
               ExecutionTally++;
               EventDetailViewModel detail = new EventDetailViewModel();
               detail.Arguments = args.AppEventPayLoad;
               detail.EventType = Enums.EventTypes.SubscriberExecution;
               detail.SubscriptionToken = args.SubscriptionToken;
               detail.EventName = args.EventObj != null ? args.EventObj.GetType().ToString() : "";
               detail.MethodName = args.TargetReference.Method.Name;
               detail.TargetName = args.TargetReference.Target != null ?
               args.TargetReference.Target.ToString() : "Anonymous";
               detail.ThreadingType = args.ThreadingType.ToString();
               if (args.TargetReference != null && args.TargetReference.Method !=
                   null && args.TargetReference.Method != null)
               {
                   detail.ModuleName = args.TargetReference.Method.Module.Name;
               }
               else
               {
                   detail.ModuleName = "";
               }
               if (args.TargetReference != null && args.TargetReference.Method != null)
               {
                   detail.Location = args.TargetReference.Method.Module.Assembly.Location;
               }
               else
               {
                   detail.Location = "";
               }

               detail.SubscriptionToken = args.SubscriptionToken;

               EventDetailsCollection.Add(detail);

               // if token already exists, increment counter, if not add it...
               if (EventExecutionSummaryCollection.Any(EventExecutionSummaryViewModel =>
               EventExecutionSummaryViewModel.SubscriptionToken.Equals(args.SubscriptionToken)))
               {
                   EventExecutionSummaryCollection.First(EventExecutionSummaryViewModel =>
                   EventExecutionSummaryViewModel.SubscriptionToken.Equals(args.SubscriptionToken)).Increment();
               }
               else
               {
                   EventExecutionSummaryViewModel newSummary = new EventExecutionSummaryViewModel();
                   newSummary.SubscriptionToken = args.SubscriptionToken;
                   newSummary.MethodName = detail.MethodName;
                   newSummary.TargetName = detail.TargetName;
                   newSummary.ExecuteCounter = 1;
                   EventExecutionSummaryCollection.Add(newSummary);
               }
           }
       }
private void PrismUnSubscribeNotificationEventHandler(PrismUnSubscribeNotificationEventArgs args)
       {
           if (args != null)
           {
               UnSubscribeTally++;

               EventDetailViewModel detail = new EventDetailViewModel();
               detail.CallingMethod = args.CallingMethod;
               detail.CallingClass = args.CallingClass;
               detail.EventType = Enums.EventTypes.UnSubscribe;
               detail.EventName = args.EventObj.GetType().ToString();

               EventDetailsCollection.Add(detail);

           }
       }

Enhancement Ideas

For the purposes of this article, I created it to just use native WPF controls and to be as straightforward as possible. But there are ways to jazz it up a bit more.
  1. I recommend using the XCeed Extended WPF Toolkit Community Edition. It can be downloaded here on CodePlex. I've used it on another WPF project and it works great. It's free and contained in one DLL that you can drop in and start using very quickly. Great suite of controls. You could replace the standard listview control with their datagrid and get a lot more functionality.
  2. For a nicely enhanced tab control, try FabTab. Another very easy to use control with lots of nice tab options. One nice feature is hovering over an unselected tab and seeing a preview of the tab.
  3. To make this more graphical, you could replace the counters for example with more dashboard type controls. A pretty nice suite of dashboard controls can be found here on Codeplex. Contains lots of controls with all source. Attractive and easy to use.

Utilities

A very handy utility for packaging up a project to send it to someone via email or for use in a CodeProject article like this is the CleanProject utility on MSDN. This will start in the home project of the project and delete all of the files in the bin and obj folders and other temporary files and then zip them up in a clean zip file. It has reduced the file size by half when I 've used it.

Another useful too is Reference Assistance. It can found here on Codeplex. This will remove any unneeded project references. Just Expand the "References" section and right-click and choose "Remove Unused References". It will determine which ones are needed and delete the rest. Works great.

In Summary...

Through the use of four new Prism events, that Prism itself publishes in response to your application level publishing of custom events, this Event Dashboard utility is able to provide you with detailed information on your entire application. It requires no change in your existing code and no change in how you write new code. The Prism framework is the single pipe line for all eventing operations. It is here that these new events are raised so that the dashboard view model can listen for them and properly handle them to update the dash boards statistics, summary and detailed information on each Subscribe, Publish, Execution and UnSubscribe event that takes place.

History

  • 03/12/2014 - First version published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I have been a software engineer for the past 30 years. I specialize in C#/WPF development. I have been a speaker at MS conferences and user groups. I also wrote a book "WebRad: Building data driven websites with Visual FoxPro".

Comments and Discussions

 
QuestionSame in Prism8 Pin
Geoff Scott 202221-Jun-22 16:22
Geoff Scott 202221-Jun-22 16:22 
GeneralMy vote of 5 Pin
fredatcodeproject17-Nov-14 3:58
professionalfredatcodeproject17-Nov-14 3:58 
GeneralRe: My vote of 5 Pin
Harold Chattaway25-Nov-14 10:05
professionalHarold Chattaway25-Nov-14 10:05 
QuestionGreat article - well done on the mammoth task... Pin
Craig Petersen12-Oct-14 10:43
Craig Petersen12-Oct-14 10:43 
AnswerRe: Great article - well done on the mammoth task... Pin
Harold Chattaway25-Nov-14 10:11
professionalHarold Chattaway25-Nov-14 10:11 
QuestionI use prism a lot Pin
Sacha Barber15-Mar-14 9:06
Sacha Barber15-Mar-14 9:06 
AnswerRe: I use prism a lot Pin
Harold Chattaway15-Mar-14 11:07
professionalHarold Chattaway15-Mar-14 11:07 
QuestionMessage Closed Pin
14-Mar-14 15:55
abo mahmoud14-Mar-14 15:55 
AnswerRe: Greaaaaaat post Pin
Harold Chattaway15-Mar-14 2:56
professionalHarold Chattaway15-Mar-14 2:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.