A New Internet Library: Add Your Website/Blog or Suggest A Website/Blog to our Free Web Directory http://anil.myfunda.net.

Its very simple, free and SEO Friendly.
Submit Now....

Monday, August 25, 2008

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.

Attached, 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 :)



Source Click Here.

No comments:

Post a Comment

Post your comments here:

Originals Enjoy