Tracing PRISM / CAL events (best practice?)

Good,

this question is for people with a deep knowledge of PRISM or some magical skill that I just lack (yet). The background is simple: Prism allows you to announce events to which the user can subscribe or post. In code, it looks like this:

  _eventAggregator.GetEvent<LayoutChangedEvent>().Subscribe(UpdateUi, true);
  _eventAggregator.GetEvent<LayoutChangedEvent>().Publish("Some argument");

      

This is fine now, especially since these events are strongly typed and the declaration is a piece of cake:

public class LayoutChangedEvent : CompositePresentationEvent<string>
{
}

      

But now comes the tricky part: I want to track events somehow. I had the idea to subscribe using a lambda expression causing a simple log message. Worked fine in WPF, but there is some method access error in Silverlight (it took me a while to figure out the reason). If you want to see for yourself, try this in Silverlight:

eA.GetEvent<VideoStartedEvent>().Subscribe(obj => TraceEvent(obj, "vSe", log));

      

If it were possible, I would be happy, because I could easily track all events using one line to subscribe. But this is not the case ... An alternative approach is to write different functions for each event and assign this function to events. Why different functions? Well, I need to know what event was posted. If I use the same function for two different events, I get the payload as an argument. Now I have a way to figure out which event triggered the trace message.

I tried:

  • using Reflection to get the calling event (doesn't work)
  • using a constructor in case each event can track itself (not allowed)

Any other ideas? Chris

PS: It probably took me longer to write this text than writing 20 functions for my 20 events, but I refuse to give up :-) I just got the idea to use postharp, which will most likely work (although I don't of course, maybe I only get base class information). A difficult and so insignificant topic ...

+2


source to share


2 answers


Probably the simplest would be to subclass CompositePresentationEvent and override the behavior of the Publish event. Here's the source for the CompositePresentationEvent: http://compositewpf.codeplex.com/SourceControl/changeset/view/26112#496659

Here's the current posting behavior:

public virtual void Publish(TPayload payload)
{
     base.InternalPublish(payload);
}

      

So, you can just add a little to this:



public virtual override void Publish(TPayload payload)
{
     ILoggerFacade logger = ServiceLocator.Current.GetInstance<ILoggerFacade>();
     logger.Log("Publishing " + payload.ToString(), Category.Debug, Priority.Low);
     base.InternalPublish(payload);
}

      

I'm using Prism's built-in registration facility here, but feel free to replace your own (or better, just implement ILoggerFacade!).

I was surprised that any default posts were posted or placed to enable tracing on this system ... as much as the EventAggregator is being abused by people, you think it would be a big request!

+5


source


A little late, but better late than never! I recently had the same problem and solved it.

Firstly, I didn't like the Prism method for publishing / subscribing to events, so I used a method like this instead: http://neverindoubtnet.blogspot.com/2009/07/simplify-prism-event-aggregator.html

The above post suggests using extension methods to aggregate events to make it easier to call to publish / sign. As a result, your client code looks like this:

IEventAggregator ev;
ev.Publish<MyCustomMessage>();

//or

ev.Publish(new MyCustomMessage(someData));

//and similarly subscription

ev.Subscribe<MyCustomMessage(this.OnCustomMessageReceived);

// ... 

private void OnCustomMessageReceived(MyCustomMessage message)
{
   // ...
}

// With a BaseMessageEvent class as follows (see the blog post above for where this comes from)

/// <summary>
/// Base class for all messages (events) 
/// </summary>
/// <typeparam name="TMessage">The message type (payload delivered to subscribers)</typeparam>
public class BaseEventMessage<TMessage> : CompositePresentationEvent<TMessage>
{
}

      

Ok, that's great, but instead of hacky extension methods, I implemented my own event service like this:



/// <summary>
/// The EventService instance
/// </summary>
public class EventService : IEventService
{
    private readonly IEventAggregator eventAggregator;
    private readonly ILoggerFacade logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="EventService"/> class.
    /// </summary>
    /// <param name="logger">The logger instance.</param>
    /// <param name="eventAggregator">The event aggregator instance.</param>
    public EventService(IEventAggregator eventAggregator, ILoggerFacade logger)
    {
        this.logger = logger;
        this.eventAggregator = eventAggregator;
    }

    #region IEventService Members

    /// <summary>
    /// Publishes the event of type TMessageType to all subscribers
    /// </summary>
    /// <typeparam name="TMessageType">The message type (Payload), must inherit CompositeEvent</typeparam>
    public void Publish<TMessageType>() where TMessageType : BaseEventMessage<TMessageType>, new()
    {
        TMessageType message = Activator.CreateInstance<TMessageType>();

        this.Publish(message);
    }

    /// <summary>
    /// Publishes the event of type TMessageType to all subscribers
    /// </summary>
    /// <typeparam name="TMessageType">The message type (Payload), must inherit CompositeEvent</typeparam>
    /// <param name="message">The message to publish</param>
    public void Publish<TMessageType>(TMessageType message) where TMessageType : BaseEventMessage<TMessageType>, new()
    {
        // Here we can log our message publications
        if (this.logger != null)
        {
           // logger.log etc.. 
        }
        this.eventAggregator.GetEvent<TMessageType>().Publish(message);
    }

    /// <summary>
    /// Subscribes to the event of type TMessage
    /// </summary>
    /// <typeparam name="TMessageType">The message type (Payload), must inherit CompositeEvent</typeparam>
    /// <param name="action">The action to execute when the event is raised</param>
    public void Subscribe<TMessageType>(Action<TMessageType> action) where TMessageType : BaseEventMessage<TMessageType>, new()
    {
        // Here we can log our message publications
        if (this.logger != null)
        {
           // logger.log etc.. 
        }
        this.eventAggregator.GetEvent<TMessageType>().Subscribe(action);
    }

    #endregion
}

      

Then I register IEventService / EventService as a singleton in the loader and forget about using IEventAggregator, just use that (however, if anyone is using IEventAggregator, then the same instance as in EventService will work).

Finally, another trick to add is to use a Stack Frame to tell me where the posts and subscriptions are coming from. Note that this is a slow process (unwinding the stack frame), so use it sparingly. If you are regularly raising an event, perhaps put a flag in your BaseEventMessage and check if posts should be logged for certain types of events.

// Inside Publish method ... Log the subscription
if (this.logger != null)
{
   Type messageType = typeof(TMessageType);
   Type callingType = GetCallingType();
   string methodName = GetCallingMethod().Name;

   // Log the publication of this event
   this.logger.Log(
         string.Format("Event {0} was published by {1}.{2}()", 
            messageType.Name,
            callingType.Name,
            methodName),
      Category.Debug, 
      Priority.Low));
}

// Additional methods to add to EventService to get the calling type/class
//

/// <summary>
/// Gets the Type that called the method or property where GetCallingType is called
/// </summary>
/// <returns>The class type that called</returns>
[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static Type GetCallingType()
{
    int skip = 2;
    MethodBase method = new StackFrame(skip, false).GetMethod();
    return method.DeclaringType;
}

/// <summary>
/// Gets the Method that called the method or property where GetCallingMethod is called
/// </summary>
/// <returns>The method type that was called</returns>
public static MethodBase GetCallingMethod()
{
    return new StackFrame(2, false).GetMethod();
}

      

Note that the above will not work in Silverlight (using StackFrame), but everything else. I found this invaluable when debugging the many events floating around the Prism app!

+1


source







All Articles