tags:

views:

487

answers:

2

Ok,

this question is for people with either a deep knowledge of PRISM or some magic skills I just lack (yet). The Background is simple: Prism allows the declaration of events to which the user can subscribe or publish. In code this looks like this:

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

Now this is nice, especially because these events are strongly typed, and the declaration is a piece of cake:

public class LayoutChangedEvent : CompositePresentationEvent<string>
{
}

But now comes the hard part: I want to trace events in some way. I had the idea to subscribe using a lambda expression calling a simple log message. Worked perfectly in WPF, but in Silverlight there is some method access error (took me some time 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 this would be possible, I would be happy, because I could easily trace all events using a single line to subscribe. But it does not... The alternative approach is writing a different functions for each event, and assign this function to the events. Why different functions? Well, I need to know WHICH event was published. If I use the same function for two different events I only get the payload as argument. I have now way to figure out which event caused the tracing message.

I tried:

  • using Reflection to get the causing event (not working)
  • using a constructor in the event to enable each event to trace itself (not allowed)

Any other ideas? Chris

PS: Writing this text took me most likely longer than writing 20 functions for my 20 events, but I refuse to give up :-) I just had the idea to use postsharp, that would most likely work (although I am not sure, perhaps I end up having only information about the base class).. Tricky and so unimportant topic...

+2  A: 

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

Here's the current Publish behavior:

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

So you could 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);
}

Here I'm using the logger facility built into Prism, but feel free to substitute your own (or better, just implement ILoggerFacade!).

I was surprised that there were any default messages being published or places to plug in tracing in this system... as much as EventAggregator is abused by people, you'd think this would be a big request!

Anderson Imes
Thanks, I think I will give it a try. Seems like the easiest way to go..
Christian
What's crazy is I trolled the code again for an hour after posting this in disbelief there wasn't a better place to plug this in... nothing.
Anderson Imes
Yeah, there are still a lot of little things to improve when using PRISM (especially in combination with multi-targeting). Also SL has still a lot of rough edges (sealed classes, missing XML stuff), but still, I like both. Makes life easier and helps to blur the lines between desktop and web (who knows when you need some neat trick, and now you can use it in two environments)... Azure will be my next playground :-)
Christian
A: 

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

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

This post above suggests using Extension methods on Event Aggregator to simplify the call to publish/subscribe. 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 this is great, but rather than hacky extension methods I implemented my own event service as follows:

/// <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 bootstrapper and forget about using the IEventAggregator, just use this (however if someone uses the IEventAggregator, its the same instance as that used by the EventService so will still work).

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

// 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 the above won't work in Silverlight (the use of the StackFrame), but the rest does. I've found this invaluable when debugging the multitude of events flying around a Prism app!

Andrew Burnett-Thompson