Glenn Block

Sponsors

The Lounge

News

  • View Glenn Block's profile on LinkedIn

    Me

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Firing generic events with EventAggregator

It's been a while since i posted anything on Prism. When I left p&p, i said you would see more posts on Prism on my blog. I've been pretty immersed in MEF since leaving and haven't done any posts on Prism yet. Today, I got inspired (albeit late in the evening) based on a post on the forums to actually do that, so here I am :-). The post was from TSChaena about using EventAggregator to fire generic events similar to the way we did things with EventBroker in CAB. Using EventBroker allows you to dynamically define events in your application that are identified through a topic name rather than needing to define a strongly typed class as you do with the EventAggregator. There are several advantages to not using the approach in EB which I have identified in this post. However, there are times when you want to do a dynamic eventing system. The good news is that there actually is a solution for doing this with our EventAggreator though it is not exactly the same as the way we did it in EB.

CompositeWPFEvent

Before we look at the solution I came up with, lets talk quickly about CompositeWPFEvent. CompositeWPFEvent is a generic class that contains one type parameter TPayload. TPayload defines the payload that will be passed into the event when it is fired. The subscriber also uses the payload to provide filters for the subscription. For example if TPayload is a FundOrder as in the EventAggregator QuickStart, then you can supply a lambda such as fundOrder=>fundOrder.CustomeriD == customerID for example to filter on only events that are received for a specific customer. The common pattern you will see for defining such events is to create a class that inherits from CompositeWPFEvent for each event that is typed to the specific paramers. For example below is the definition for the FundOrderAdded event.

public class FundOrderAdded : CompositeWpfEvent<FundOrderAdded> {}

This event is then retrieved from the EventAggregator by calling the GetEvent method passing FundOrderAdded as the event. Now, although this is the common pattern, there is nothing about the EventAggregator that requires you to create a new event class for each event. CompositeWPFEvent is not an abstract class, so you can simply "use" it as you will, even in a generic case. For example you can do the following.

   1: public class ThrowsEvents {
   2:   public ThrowsEvents(IEventAggregator eventAgg) {
   3:     eventAgg.GetEvent<CompositeWPFEvent<string>>().Publish("SomethingEvent")
   4:     eventAgg.GetEvent<CompositeWPFEvent<string>>().Publish("SomethingElseEvent")
   5:   }
   6: }
   7:  
   8: public class HandlesEvents {
   9:   public HandlesEvents(IEventAggregator eventAgg) {
  10:     CompositeWPFEvent genericEvent = eventAgg.GetEvent<CompositeWPFEvent<string>>();
  11:     genericEvent.Subscribe(action=>Console.WriteLine("SomethingEvent fired", ThreadOption.UIThread, 
  12:       false, e=>e == "SomethingEvent");
  13:     genericEvent.Subscribe(action=>Console.WriteLine("SomethingElseEvent fired", ThreadOption.UIThread, 
  14:       false, e=>e == "SomethingElseEvent");
  15:   } 
  16: }

If you look at the above code, you'll notice that we are using CompositeWPFEvent event directly, rather than creating a specific inheritor. When we call for the event from the aggregator, we are passing in a param of type string which represents the EventName / Topic. I am then using our event subscription mechanism to subscribe two different handlers to the same "event" by using the eventName as the filter. So here we have the basics of doing generic event publication and subscription. However, we are missing something important....that payload :). To handle this, you could instead create your own custom class that carries two parameters, EventName, and Value. With that approach, you can pass both the event name and the value, still filter on the event, and you can pass a value along. For example the above code passing  a value would look like the following.

   1: public class SomeEventParams {
   2:   public SomeEventParams(string eventName, object value) {
   3:     EventName = eventName;
   4:     Value = value;
   5:   }
   6:   
   7:   public string EventName {get;private set;}
   8:   public object Value {get; private set;}
   9: }
  10:  
  11:  
  12: public class ThrowsEvents {
  13:  
  14:   public ThrowsEvents(IEventAggregator eventAgg) {
  15:     eventAgg.GetEvent<CompositeWPFEvent<SomeEventParams>>().Publish(new SomeEventParams("SomethingEvent","SomeValue"));
  16:     eventAgg.GetEvent<CompositeWPFEvent<SomeEventParams>>().Publish(new SomeEventParams("SomethingElseEvent", "SomeOtherValue"));
  17:   }
  18: }
  19:  
  20: public class HandlesEvents {
  21:   public HandlesEvents(IEventAggregator eventAgg) {
  22:     CompositeWPFEvent genericEvent = eventAgg.GetEvent<CompositeWPFEvent<string>>();
  23:     genericEvent.Subscribe(action=>Console.WriteLine("SomethingEvent fired" + action.Value, ThreadOption.UIThread, 
  24:       false, e=>e.EventName == "SomethingEvent");
  25:     genericEvent.Subscribe(action=>Console.WriteLine("SomethingElseEvent fired" + action.Value, ThreadOption.UIThread, 
  26:       false, e=>e.EventName == "SomethingElseEvent");
  27:   } 
  28: }

That's OK, except now the parameters are simply object. That means we are losing the type safety that the EventAgg was built for in the first place! Now you can further refactor and make SomeEventParams a generic type that accepts a type param for the value. The only downside of this, is the code will get much more verbose and harder to read. For example retrieving the event to publish will now look like...

eventAgg.GetEvent<CompositeWPFEvent<SomeEventParams<string>>().Publish...

Suboptimal. I bet your thinking you could refactor this a bit more..yes, you can. This is what led me to a GenericEvent.

GenericEvent

If we keep refactoring, we can get rid of alot of the DRY behavior, by creating an inheritor of CompositeWPFEvent, GenericEvent. The event and associated parameters class looks like this

public class EventParameters<TValue>
{
  public string Topic { get; private set; }
  public TValue Value { get; private set; }
 
 
  public EventParameters(string topic, TValue value)
  {
    Topic = topic;
    Value = value;
  }
}
 
public class GenericEvent<TValue> : CompositeWpfEvent<EventParameters<TValue>> {}

Subscribing and publishing is now easier as well. The previous GetEvent code now looks like

eventAgg.GetEvent<GenericEvent<string>().Publish...

Because I have strongly typed my Value, I know have back my strongly typed filters and delegates.

Putting the rubber to the road with the EventAggregation QuickStart.

In order to test this out, I took a copy of the EventAggregaton QuickStart that is included with the Prism bits, and I modified it to use the new GenericEvent. I also added a Remove button to the QS in order to demonstrate using more than one event. The new Quickstart looks like the following.

image 

In the new version of the Quickstart, the FundOrderAddedEvent is removed. Instead, I have added two constants to define the different events.

public class Events
{
  public const string FundAdded = "FundAdded";
  public const string FundRemoved = "FundRemoved";
}

I added a RemoveFund method to the AddFundPresenter as well as refactored the AddFund method as follows.

void RemoveFund(object sender, EventArgs e)
{
    FundOrder fundOrder = new FundOrder();
    fundOrder.CustomerId = View.Customer;
    fundOrder.TickerSymbol = View.Fund;
 
    if (!string.IsNullOrEmpty(fundOrder.CustomerId) && !string.IsNullOrEmpty(fundOrder.TickerSymbol))
        eventAggregator.GetEvent<GenericEvent<FundOrder>>().
          Publish(new EventParameters<FundOrder>(Events.FundRemoved, fundOrder));
    
}
 
void AddFund(object sender, EventArgs e)
{
    FundOrder fundOrder = new FundOrder();
    fundOrder.CustomerId = View.Customer;
    fundOrder.TickerSymbol = View.Fund;
 
    if (!string.IsNullOrEmpty(fundOrder.CustomerId) && !string.IsNullOrEmpty(fundOrder.TickerSymbol))
        eventAggregator.GetEvent<GenericEvent<FundOrder>>().
          Publish(new EventParameters<FundOrder>(Events.FundAdded, fundOrder));
}

Finally, I refactored the ActivityPresenter in a similar fashion

public string CustomerId
{
    get { return _customerId; }
    set
    {
        _customerId = value;
 
        GenericEvent<FundOrder> fundOrderEvent = eventAggregator.GetEvent<GenericEvent<FundOrder>>();
 
        if (fundAddedSubscriptionToken != null)
        {
            fundOrderEvent.Unsubscribe(fundAddedSubscriptionToken);
            fundOrderEvent.Unsubscribe(fundRemovedSubscriptionToken);
            
        }
 
        fundAddedSubscriptionToken = fundOrderEvent.Subscribe(FundAddedEventHandler, ThreadOption.UIThread, false,
                                                     parms => parms.Topic == Events.FundAdded && parms.Value.CustomerId == _customerId);
 
        fundRemovedSubscriptionToken = fundOrderEvent.Subscribe(FundRemovedEventHandler, ThreadOption.UIThread, false,
                                                     parms => parms.Topic == Events.FundRemoved && parms.Value.CustomerId == _customerId);
 
        View.Title = string.Format(CultureInfo.CurrentCulture, Resources.ActivityTitle, CustomerId);
    }
}

Notice how in the subscription I am now filteirng on the event Topic in addition to the value. This is the result of moving to a generic event.

Wrapping Up

Using the approach show in this post, we've seen how you can utilize the existing EventAggregator infrastructure to do generic eventing similar to the way EventBroker in CAB functions.

Personally I think using strongly typed specific events is more maintainable. The reasoning is because the event payload type is intrinsically defined to the event wheras in this model they are not. For example with generic events I might have an event that publishes passing a customer, but on the receiving side I have  defined it as a string. This event will never get handled, because the susbscriber and publisher don't match. If you use strongly typed events that is not the case, as the type is the match ;) However there are scenarios where it may make sense to have something more dynamic, for example if you have a metadata driven system that needs to do dynamic wiring.

In the link below, you'll find the code for my modified version of the QuickStart. Let me know if this works for you. Now time to get some sleep :)

http://tinyurl.com/GenericEventAgg-zip


Posted Fri, Aug 22 2008 3:08 AM by Glenn Block

[Advertisement]

Comments

Sean Feldman wrote re: Firing generic events with EventAggregator
on Fri, Aug 22 2008 10:36 AM

Is there a road map for Prism project? Would be interestng to know where are you heading with it. Thank you.

Dew Drop - August 23, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - August 23, 2008 | Alvin Ashcraft's Morning Dew
on Sat, Aug 23 2008 10:17 AM

Pingback from  Dew Drop - August 23, 2008 | Alvin Ashcraft's Morning Dew

Udi Dahan: The Software Simplist wrote re: Firing generic events with EventAggregator
on Mon, Aug 25 2008 9:59 AM

I've got to say that the fact that the definition of the event (const string) is separate from the parameter definition is... unfortunate.

I think it would be simpler to keep the definition of events/commands in one place like this:

public static class DomainEvents

{

     public static readonly DomainEvent<IProduct> ProductReportedLost = new DomainEvent<IProduct>;

     public static readonly DomainEvent<ICart> CartIsFull = new DomainEvent<ICart>;

}

This holds for composite client commands and server-side domain model events.

I've got the infrastructure that supports this up on my post <a href="www.udidahan.com/.../">Domain Events - Take 2</a>.

Thoughts?

Udi Dahan: The Software Simplist wrote re: Firing generic events with EventAggregator
on Mon, Aug 25 2008 3:17 PM

Fixing the link in the previous comment:

www.udidahan.com/.../domain-events-take-2

2008 August 25 - Links for today « My (almost) Daily Links wrote 2008 August 25 - Links for today &laquo; My (almost) Daily Links
on Mon, Aug 25 2008 7:25 PM

Pingback from  2008 August 25 - Links for today &laquo; My (almost) Daily Links

Glenn Block wrote re: Firing generic events with EventAggregator
on Tue, Aug 26 2008 5:14 AM

@Udi, the event implementation we shipped with Prism actually does bind the two. CompositeWpfEvent<T> is bound to the parameters.

This implementation (which needs a bunch of cleaning) is for dynamically creating an event from a string.

Berch wrote re: Firing generic events with EventAggregator
on Sun, Dec 28 2008 1:38 PM
Hi Glenn It seems like the attached zip is corrupt.... :-( And it looks just like what i need. Can you pleeeeeezzzz check it ? maybe I'm doing something wrong ?? many thx Hai haib@innovashare.com
Glenn Block wrote re: Firing generic events with EventAggregator
on Sun, Dec 28 2008 9:35 PM

Hi Berch

You are right, it is corrupted :) Seems like it may have been that way for a long time. I tried updating another zip and the same thing happened.

Anyway, luckily I still have the original. I went and uploaded it to a skydrive. You can get it at the link below (which I also added to my post).

tinyurl.com/GenericEventAgg-zip

Thanks

Berch wrote re: Firing generic events with EventAggregator
on Mon, Dec 29 2008 6:17 AM
Nope, Thank you !! =-}
Chakshu Shah wrote re: Firing generic events with EventAggregator
on Wed, Jul 1 2009 7:21 AM

I still am not able to download the code attached with this link. It says 'Page not found'.

vgupta wrote re: Firing generic events with EventAggregator
on Wed, Feb 3 2010 1:52 AM

Hi Glenn,

I had a implementation for threading Using EventAggregator. Can you please review it at stackoverflow.com/.../can-prism-eventaggregator-be-used-for-threading-needs

Please guide me if this could be a possible implementation using EventAggregator

Thanks

Add a Comment

(required)  
(optional)
(required)  
Remember Me?