I just returned from a trip to California with my son. We have just started to look at colleges for him. He is a junior in high school, but this is when kids start considering colleges seriously. We went to see U Cal at Berkeley, Stanford, and Pomona. All three very different colleges.
Warning to parents: good grades and good standardized test scores just don't cut it anymore. Stanford rejects an awful lot of kids who get 1600's on their SATs. All of the colleges want to see rigorous courses in high school, great grades, great SAT I and SAT II and AP/IB scores. But they also want students that are unique in some way. The essays that kids have to write for their college applications are extremely important.
It is not only amazing difficult to get into the top-tier schools. It is also getting increasing difficult to get into the upper-middle tier schools. Applications are skewed by professional college counselors who you can hire for $10,000 to $30,000 .... you wonder if a kid gets into a certain college because of his/her own worth, or because daddy's paycheck is fat enough to hire a pro.
Now, we have to capture the one big intangible that my son has into an essay .. the fact that he is a leader ... not in the academic sense (ie: he is not the editor of the school newspaper nor the quarterback of his school's football team) ... but the fact that he has packs of kids that follow him around, that kids are constantly calling him and messaging him, that he can snap his fingers and a whole cadre of students will appear to schlep his drumset from gig to gig. In other words, someone who has that special magnetism that command attention amongst his peers (think Tony Soprano).
I wonder if Stanford is interested in the future leader of La Cosa Nostra.....
(To the British readers here .. does the same thing apply to Oxford and Cambridge? Is most of the UK clamoring to get into these two schools?)
©2006 Marc Adler - All Rights Reserved
Saturday, April 29, 2006
Event Manager Code - Possible Enhancements
Now that I have posted the code for the Event Manager that I have used in several projects, I would like to list some possible enhancements that I have considered:
Precompile regular expressions
The Event Manager supports wildcards. The .NET RegularExpression classes are used to perform matching of a published topic with a wildcarded subscription. Howver, I do not pre-compile the regexps. This is a very simple change to make.
Enhanced regular expressions
We can expand the regular expressions that wildcarded subscriptions will accept. For example, to subscribe to an insert or delete action for a trade, we should be able to have a wildcard topic that looks like this:
Magmasystems.TradingSystem.Trade.[InsertedDeleted]
Stronger typing of args for event declarations
The third argument of EventManager.Fire() is anything that derives from EventManagerArgs. We can add more type safety by specifying the subclass of EventManagerArgs that a publisher expects to send and that the corresponding subscribers expect to receive.
Scoping
Microsoft's CAB has the notion of event scoping. We can limit a published event to subscribers that run in the same thread (or a certain thread) as the publisher. We can limit the scope to a named "applet". Otherwise, by default, the event is published globally.
EventManager.Register(this)
I am not totally thrilled with the fact that, for every object that publishes or subscribes, a call to EventManager.Register(this) is called. The Register() function will examine the passed class, using reflection on the class to build up a dictionary of publishers and subscribers to a topic.
Dynamically register the publisher and subscriber
Instead of using the static way of decorating a method, we may want to add publications and subscriptions dynamically. For instance, we should be able to say something liek this:
EventManager.Subscribe("Magmasystems.TradingSystem.Trade.*", this.OnTradeEvent);
Sinks for event publishing
Implement Sinks so a class can have a crack at an event args before it is sent to the subscribers.
©2006 Marc Adler - All Rights Reserved
Precompile regular expressions
The Event Manager supports wildcards. The .NET RegularExpression classes are used to perform matching of a published topic with a wildcarded subscription. Howver, I do not pre-compile the regexps. This is a very simple change to make.
Enhanced regular expressions
We can expand the regular expressions that wildcarded subscriptions will accept. For example, to subscribe to an insert or delete action for a trade, we should be able to have a wildcard topic that looks like this:
Magmasystems.TradingSystem.Trade.[InsertedDeleted]
Stronger typing of args for event declarations
The third argument of EventManager.Fire() is anything that derives from EventManagerArgs. We can add more type safety by specifying the subclass of EventManagerArgs that a publisher expects to send and that the corresponding subscribers expect to receive.
Scoping
Microsoft's CAB has the notion of event scoping. We can limit a published event to subscribers that run in the same thread (or a certain thread) as the publisher. We can limit the scope to a named "applet". Otherwise, by default, the event is published globally.
EventManager.Register(this)
I am not totally thrilled with the fact that, for every object that publishes or subscribes, a call to EventManager.Register(this) is called. The Register() function will examine the passed class, using reflection on the class to build up a dictionary of publishers and subscribers to a topic.
Dynamically register the publisher and subscriber
Instead of using the static way of decorating a method, we may want to add publications and subscriptions dynamically. For instance, we should be able to say something liek this:
EventManager.Subscribe("Magmasystems.TradingSystem.Trade.*", this.OnTradeEvent);
Sinks for event publishing
Implement Sinks so a class can have a crack at an event args before it is sent to the subscribers.
©2006 Marc Adler - All Rights Reserved
Friday, April 28, 2006
Event Manager Code - EventManager.cs
Here is the code for the main EventManager class.
There are a number of ways that I would like to improve the code, but just never have had the time. When I get a few free moments, I will discuss some of the enhancements that I would like to put in the code eventually.
©2006 Marc Adler - All Rights Reserved
There are a number of ways that I would like to improve the code, but just never have had the time. When I get a few free moments, I will discuss some of the enhancements that I would like to put in the code eventually.
// for MethodImplAttrbute
using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
// for ISynchronizer
// --------------------------------------------------------------------
//
// This code is (C) Copyright 2005 Marc Adler
//
// --------------------------------------------------------------------
namespace Magmasystems.EventManager
{
public delegate bool EventManagerEventHandler(object sender, string topicName, EventArgs e);
public class EventManager
{
#region Delegates
// Internal delegate used to help us fire async events
private delegate void AsyncFire(Delegate del, object[] args);
#endregion
#region Variables
// The singleton EventManager
private static EventBroker _theEventManager = null;
// A Dictionary of EventManagerEventInfo classes, indexed by the topic name
private Hashtable _theEventDictionary = null;
private Hashtable _theWildcardSubscribers = null;
// Used to disabled and re-enable the firing of events
private bool _isEnabled = true;
#endregion
#region Constructors and Static Instance
protected EventBroker()
{
}
// This is the way that the EventManager singleton is accessed
static public EventManager Instance
{
get
{
if(_theEventManager == null)
{
_theEventManager = new EventBroker();
_theEventManager._theEventDictionary = new Hashtable();
_theEventManager._theWildcardSubscribers = new Hashtable();
_theEventManager._isEnabled = true;
}
return _theEventManager;
}
}
#endregion
#region Properties
private Hashtable Dictionary
{
get
{
// make sure that the dictionary and event manager are instantiated
return _theEventDictionary;
}
}
private Hashtable WildcardDictionary
{
get
{
// make sure that the dictionary and event manager are instantiated
return _theWildcardSubscribers;
}
}
public bool Enabled
{
get { return this._isEnabled; }
set { this._isEnabled = value; }
}
#endregion
#region General Methods
static public void Register(object o)
{
Type type = o.GetType();
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo mi in methods)
{
// if(mi.DeclaringType != type)
// continue;
object[] oAttrs = mi.GetCustomAttributes(true);
foreach (object oAttr in oAttrs)
{
EventManagerEventInfo evInfo;
if(oAttr is EventSubscriberAttribute)
{
EventSubscriberAttribute evSubAttr = oAttr as EventSubscriberAttribute;
evInfo = FindTopicEntry(evSubAttr.Topic, true);
if(evInfo != null)
{
EventSubscriberInfo subInfo = evInfo.AddSubscriber(evSubAttr.Topic, o, evSubAttr.IsBackground, mi);
if(subInfo.HasWildcards)
{
EventBroker.Instance._theWildcardSubscribers[evSubAttr.Topic] = evInfo;
}
}
}
else if(oAttr is EventPublisherAttribute)
{
EventPublisherAttribute evPubAttr = oAttr as EventPublisherAttribute;
evInfo = FindTopicEntry(evPubAttr.Topic, true);
if(evInfo != null)
{
evInfo.AddPublisher(evPubAttr.Topic, o);
}
}
}
}
}
static private EventManagerEventInfo FindTopicEntry(string topicName, bool createIfEmpty)
{
EventManagerEventInfo evInfo ;
topicName = topicName.ToUpper();
object oEventInfo = EventBroker.Instance._theEventDictionary[topicName];
if(oEventInfo == null)
{
if(createIfEmpty)
{
// Allocate a new entry
evInfo = new EventManagerEventInfo();
EventBroker.Instance.Dictionary[topicName] = evInfo;
}
else
{
evInfo = null;
}
}
else
{
// Use the existing entry
evInfo = oEventInfo as EventManagerEventInfo;
}
return evInfo;
}
#endregion
#region Ways to Fire and Event
[MethodImpl(MethodImplOptions.NoInlining)]
static public void Fire(object sender, string topicName, EventArgs args)
{
if (EventBroker.Instance.Enabled == false)
return;
EventManagerEventInfo evInfo = FindTopicEntry(topicName, false);
if(evInfo != null)
{
evInfo.Fire(sender, topicName, args);
}
foreach (object oKey in EventBroker.Instance._theWildcardSubscribers.Keys)
{
string keyName = oKey as String;
Regex regExp = new Regex(keyName, RegexOptions.IgnoreCase);
if(regExp.IsMatch(topicName))
{
evInfo = EventBroker.Instance._theWildcardSubscribers[oKey] as EventManagerEventInfo;
evInfo.Fire(sender, topicName, args);
}
}
}
#endregion
#region Inner Class for EventManagerEventInfo
///
/// EventManagerEventInfo
/// This class represents information about a single event
///
public class EventManagerEventInfo
{
#region Variables
// Multicast delegate of all subcribers to this event
private event EventManagerEventHandler _eventHandler;
// The list of classes that publish this event
private ArrayList _publishers;
// The list of classes that subscribe to this event
private ArrayList _subscribers;
#endregion
#region Constructors
public EventManagerEventInfo()
{
this._publishers = new ArrayList();
this._subscribers = new ArrayList();
this._eventHandler = null;
}
#endregion
#region Events
public event EventManagerEventHandler EventManagerEvent
{
add { this._eventHandler += value; }
remove { this._eventHandler -= value; }
}
#endregion
#region Properties
public EventManagerEventHandler EventHandler
{
get { return this._eventHandler; }
}
public ArrayList Publishers
{
get { return this._publishers; }
}
public ArrayList Subscribers
{
get { return this._subscribers; }
}
#endregion
#region Methods
public EventSubscriberInfo AddSubscriber(string topicName, object objectRef, bool isBackground, MethodInfo mi)
{
EventSubscriberInfo subInfo = new EventSubscriberInfo(this, topicName, objectRef, isBackground, mi);
this._eventHandler += new EventManagerEventHandler(subInfo.OnPublisherFired);
this.Subscribers.Add(subInfo);
return subInfo;
}
public EventPublisherInfo AddPublisher(string topicName, object objectRef)
{
EventPublisherInfo pubInfo = new EventPublisherInfo(this, topicName, objectRef);
this.Publishers.Add(pubInfo);
return pubInfo;
}
public void RemoveSubscriber(EventSubscriberInfo sub)
{
this.Subscribers.Remove(sub);
}
public void RemovePublisher(EventPublisherInfo pub)
{
this.Publishers.Remove(pub);
}
#endregion
#region Event Firing
public void Fire(object sender, string topicName, EventArgs args)
{
// This handles the event firing to the SubscriberInfo class. In turn, the
// SubscriberInfo object will invoke the actual delegate. The SubscriberInfo
// object will determine whether the delegate shoul dbe called synchronously
// or asynchronously.
if(this.EventHandler != null)
{
this.EventHandler(sender, topicName, args);
}
}
#endregion
}
#endregion
#region Inner class for EventPublisherInfo
public class EventPublisherInfo
{
#region Variables
private EventManagerEventInfo _evInfo;
private WeakReference _objectRef;
private string _topicName; // the upper-cased name
private string _displayName; // used to generate context menus at runtime
#endregion
#region Constructors
private EventPublisherInfo()
{
this._objectRef = null;
}
public EventPublisherInfo(EventManagerEventInfo evInfo, string topicName) : this()
{
this._evInfo = evInfo;
this.DisplayName = topicName;
this._topicName = topicName.ToUpper();
}
public EventPublisherInfo(EventManagerEventInfo evInfo, string topicName, object objectRef) : this(evInfo, topicName)
{
this._objectRef = new WeakReference(objectRef);
}
#endregion
#region Properties
public WeakReference Publisher
{
get { return (this._objectRef.IsAlive == true) ? this._objectRef : null; }
set { this._objectRef = value; }
}
public string DisplayName
{
get { return this._displayName; }
set { this._displayName = value; }
}
#endregion
}
#endregion
#region Inner class for EventSubscriberInfo
public class EventSubscriberInfo
{
#region Variables
private EventManagerEventInfo _eventInfo; // ref back to the holding container
private WeakReference _objectRef;
private MethodInfo _methodInfo; // The method that the event firer should Invoke
private bool _isBackground;
private EventManagerEventHandler _delegateForAsync;
private string _topicName; // we may have wildcards
private bool _hasWildcards; // to help determine whether to use RegEx or not
#endregion
#region Constructors
private EventSubscriberInfo()
{
this._objectRef = null;
this._isBackground = false;
this._hasWildcards = false;
}
public EventSubscriberInfo(EventManagerEventInfo evInfo, string topicName) : this()
{
this._eventInfo = evInfo;
this.TopicName = topicName; // use the property so that formatting is done
}
public EventSubscriberInfo(EventManagerEventInfo evInfo, string topicName, object objectRef) : this(evInfo, topicName)
{
this._objectRef = new WeakReference(objectRef);
}
public EventSubscriberInfo(EventManagerEventInfo evInfo, string topicName, object objectRef, bool isBackground, MethodInfo mi) : this(evInfo, topicName, objectRef)
{
this._isBackground = isBackground;
this._methodInfo = mi;
if(isBackground)
{
this._delegateForAsync = (EventManagerEventHandler) Delegate.CreateDelegate(typeof (EventManagerEventHandler), objectRef, mi.Name);
}
}
#endregion
#region Properties
public object Subscriber
{
get { return (this._objectRef.IsAlive) ? this._objectRef.Target : null; }
}
public bool IsBackground
{
get { return this._isBackground; }
set { this._isBackground = value; }
}
public string TopicName
{
get { return this._topicName; }
set { this._topicName = FormatTopicName(value); }
}
public bool HasWildcards
{
get { return this._hasWildcards; }
}
#endregion
#region Methods
private string FormatTopicName(string topic)
{
// We want to make things easy for matching publishers and sunscribers.
// 1) We always use upper-case topic names.
// 2) Replace the dots with slashes so that the dot is not taken as a regexp
// character by the pattern matcher.
// 3) Let us know whether this topic has a wildcard in it so we know whether
// to use the slow regexp matcher or the faster Equals operator.
if(topic.IndexOfAny(new char[] {'*'}) >= 0)
this._hasWildcards = true;
return topic.Replace('.', '/').ToUpper();
}
#endregion
#region Event Firing
///
/// This gets called whenever the event manager fires an event.
/// This does the hard work in event firing. It eventually calls
/// the subscriber's delegate in order to process the event. it also determines
/// whether the delegate should be called synchronously or asynchronously.
///
///
///
///
///
//
public bool OnPublisherFired(object sender, string topicName, EventArgs args)
{
// If the object that this subscriber is bound to has been garbage-collected, then
// remove the subscriber from the EventInfo's subscriber list and return.
if(this.Subscriber == null)
{
this._eventInfo.RemoveSubscriber(this);
return true;
}
if(this.IsBackground)
{
AsyncFire asyncFire = new AsyncFire(InvokeDelegate);
asyncFire.BeginInvoke(this._delegateForAsync, new object[] {sender, topicName, args}, new AsyncCallback(Cleanup), null);
}
else
{
object oRet = this._methodInfo.Invoke(this.Subscriber, new object[] {sender, topicName, args});
if(oRet is Boolean)
return (bool) oRet;
}
return true;
}
private void InvokeDelegate(Delegate del, object[] args)
{
ISynchronizeInvoke synchronizer = del.Target as ISynchronizeInvoke;
if(synchronizer != null) // Requires thread affinity
{
if(synchronizer.InvokeRequired)
{
synchronizer.Invoke(del, args);
return;
}
}
// Not requiring thread afinity or invoke is not required
del.DynamicInvoke(args);
}
private void Cleanup(IAsyncResult asyncResult)
{
asyncResult.AsyncWaitHandle.Close();
}
#endregion
}
#endregion
}
}
©2006 Marc Adler - All Rights Reserved
Event Manager Code - EventManagerArgs.cs
These are two minor argument classes that are used to send information from the event publisher to the subscriber. The args are sent as the third parameter in the EventManager.Fire() function.
©2006 Marc Adler - All Rights Reserved
using System;
namespace Magmasystems.EventManager
{
///
/// Summary description for EventManagerArgs.
///
public class EventManagerArgs : EventArgs
{
public EventManagerArgs()
{
}
}
// This is used to pass string data between the publisher and subscriber
public class EventManagerMessageArgs : EventManagerArgs
{
private string _message;
public EventManagerMessageArgs(string msg)
{
this._message = msg;
}
public string Message
{
get { return this._message; }
}
}
}
©2006 Marc Adler - All Rights Reserved
Event Manager Code - EventSubscriberAttribute.cs
The EventSubscriberAttribute is used to decorate a C# function that is the recipient of a subscribed event from our event manager. The topic name can be a wildcard string, just like Tibco. For instance, to subscribe to all actions that happen to a trade, you can subscribe to topic "TradingSystem.Trade.*".
Notice that the attribute is declared with AllowMultiple=true. This lets a function have multiple subscription declarations.
The final thing to notice is the IsBackground flag. If a subscriber is declared with IsBackground=true, then the event will be dispatched to the subscriber on a worker thread (internally, Begin/EndInvoke is used).
Here is the code:
©2006 Marc Adler - All Rights Reserved
Notice that the attribute is declared with AllowMultiple=true. This lets a function have multiple subscription declarations.
The final thing to notice is the IsBackground flag. If a subscriber is declared with IsBackground=true, then the event will be dispatched to the subscriber on a worker thread (internally, Begin/EndInvoke is used).
Here is the code:
using System;
// --------------------------------------------------------------------
//
// This code is (C) Copyright 2005 Marc Adler
//
// --------------------------------------------------------------------
namespace Magmasystems.EventManager
{
///
/// EventSubscriberAttribute
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple=true)]
public class EventSubscriberAttribute : System.Attribute
{
private string _topicName;
private bool _isBackground;
public EventSubscriberAttribute()
{
}
///
/// EventSubscriberAttribute
///
/// The name of the event
public EventSubscriberAttribute(string topicName)
{
this._topicName = topicName;
this._isBackground = false;
}
public string Topic
{
get
{
return this._topicName;
}
set
{
this._topicName = value;
}
}
public bool IsBackground
{
get
{
return this._isBackground;
}
set
{
this._isBackground = value;
}
}
}
}
©2006 Marc Adler - All Rights Reserved
Event Manager Code - EventPublisherAttribute.cs
The EventPublisherAttribute is a C# attribute that decorates a function that publishes an internal event. Any function that publishes an event out to our little event broker should decorate itself with this attribute.For example:
Here is the code:
©2006 Marc Adler - All Rights Reserved
[EventPublisher("Brokerage.TradingSystem.Trade.ReceivedFromBackend")]
public void OnTradeReceived(Trade trade)
{
.......
EventManager.Fire(this, "Brokerage.TradingSystem.Trade.ReceivedFromBackend",
tradeArgs);
}
Here is the code:
using System;
// --------------------------------------------------------------------
//
// This code is (C) Copyright 2005 Marc Adler
//
// --------------------------------------------------------------------
namespace Magmasystems.EventManager
{
///
/// EventPublisherAttribute
///
public class EventPublisherAttribute : Attribute
{
private string _topicName;
public EventPublisherAttribute()
{
}
///
/// EventPublisherAttribute
///
/// The name of the event
public EventPublisherAttribute(string topicName)
{
this._topicName = topicName;
}
public string Topic
{
get
{
return this._topicName;
}
set
{
this._topicName = value;
}
}
}
}
©2006 Marc Adler - All Rights Reserved
Thursday, April 27, 2006
A Lightweight IntraApp Event Broker for C#/.NET
Over the years, I have managed to write a repository of interesting classes that has helped me in architecting and developing systems. Over the coming months, and as time allows, I will submit some of these classes for your use.
I wrote the EventBroker about two years ago for a trading system that had Tibco connectivity to a message bus that published quotes. I wanted a symmetrical processing model from the back end to the front end, and also, within the front end. So, I wrote a .NET/C#-based lightweight event broker that was like a min-Tibco within the GUI.
The Microsoft CAB has something similar, and when it came out, I took a few ideas from it (such as the handling of WeakReferences) and integrated it into my own Event Broker. CAB differs from mine in several respects .... namely, it has the concept of Event Scope, and it also has a different way of decorating code (it decorates event declarations, while mine decorates methods).
However, this event broker has been used in several trading systems and seems to function well.
In the next several posts, I will post the code of the Event Broker. You are free to use it as long as my copyright is maintained and proper acknowledgement is given. Also, I am anxious to hear of any improvments, enhancements and bug fixes you might make to any of the classes that I publish here. You can always reach me at magmasystems at yahoo dot com.
The Event Broker
The application uses a lightweight Publish/Subscribe (PubSub) model internally in order to notify the controller or UI layer of interesting events. A typical interesting event is a change in the underlying data model.
The mechanism that implements PubSub within the application is called the Event Broker. Event publishers and event subscribers will be automatically registered with the Event Manager by the use of customized .NET attributes.
The use of the Event Manager in no way precludes the application from doing its own event management using the standard .NET event/delegate mechanism.
Using the EventBroker in a class
Any class that wants to use the Pub/Sub capabilities of the EventBroker should do two thing.
1) Include a reference to the EventBroker's assembly. Also, reference the namespace within your class:
using Magmasystems.TradingSystem.EventManager;
2) Register the class with the EventBroker. A good place to do this is in the class' constructor. To register the class with the Event Broker, all that needs to be done is to put in the following line of code:
EventBroker.Register(this);
There are several .NET attributes that can be used to decorate functions in order to define them as either Event Publishers or Event Subscribers. When one of these attributes is constructed, the EventTopic parameter is examined. The EventTopic is added to the EventManager's internal Dictionary. A Dictionary collection is used in order to provide fast access to a particular Topic/Event pairing.
[EventPublisher(string EventTopic)]
This registers a function or class to be an event publisher. The class will publish an event with a specific name. Any subscribers to that topic will be notified.
[EventSubscriber(string EventTopic, options)]
This registers a subscriber to an event. The attribute should be placed directly above the function that will be used as the event handler. The options can be one of the following:
* Background - the handler is called asynchronously
The string-based topic name can be the actual name of a published topic, or it can be a wildcarded topic like "Magmasystems.TradingSystem.Trade.*".
To fire an event, the static method EventManager.Fire() is called
EventManager.Fire(object obj, string eventTopic, EventArgs args);
Event handlers have the signature:
public delegate bool EventManagerEventHandler(object sender,
string topicName, EventArgs e);
Here is a piece of code that publishes an event (The recipient could be a controller that processes events from views and from the models):
[EventPublisher("Magmasystems.TradingSystem.Controller.Action.GetTrades")]
private void GetTrades()
{
GetTradeArgs args = new GetTradeArgs();
args.CUSIP = "12345";
EventBroker.Fire(this, "Barcap.Gcdit.Odc.Controller.Action.GetTrades", args);
}
Here is a piece of code from a controller that subscribes to an event
[EventSubscriber("Magmasystems.TradingSystem.Controller.Action.GetTrades")]
public void GetTrades(object sender, string topicName, System.EventArgs e)
{
GetTradesUnitOfWork.GetTradesArgs args = e as GetTradesUnitOfWork.GetTradesArgs;
if (e == null)
return;
ExecuteAction(new GetTradesUnitOfWork(this._context.UIApplicationContext, args));
}
There is a .Net event associated with every topic that is registered. Events are really Multicast Delegates. Every subscriber to an event adds an event handler to the list of delegates. When an event is fired, the delegate list is traversed and each delegate will be invoked either synchronously or asynchronously. The options can include the Background flag. This tells the event firing mechanism that the underlying delegate should be invoked asynchronously. If the Background flag is not present in the attribute declaration, then the underlying delegate will be invoked synchronously.
Guidelines for Usage
Any class can publish an event, and any class can subscribe to that event. The event bus is global to the entire application.
The line of code that registers a class with the EventBroker is best put in the constructor of the Base Class.
Following our adoption of a namespace convention, we have also adopted a general naming convention for events. This convention is:
companyname.department.application.domainobject.action
©2006 Marc Adler - All Rights Reserved
I wrote the EventBroker about two years ago for a trading system that had Tibco connectivity to a message bus that published quotes. I wanted a symmetrical processing model from the back end to the front end, and also, within the front end. So, I wrote a .NET/C#-based lightweight event broker that was like a min-Tibco within the GUI.
The Microsoft CAB has something similar, and when it came out, I took a few ideas from it (such as the handling of WeakReferences) and integrated it into my own Event Broker. CAB differs from mine in several respects .... namely, it has the concept of Event Scope, and it also has a different way of decorating code (it decorates event declarations, while mine decorates methods).
However, this event broker has been used in several trading systems and seems to function well.
In the next several posts, I will post the code of the Event Broker. You are free to use it as long as my copyright is maintained and proper acknowledgement is given. Also, I am anxious to hear of any improvments, enhancements and bug fixes you might make to any of the classes that I publish here. You can always reach me at magmasystems at yahoo dot com.
The Event Broker
The application uses a lightweight Publish/Subscribe (PubSub) model internally in order to notify the controller or UI layer of interesting events. A typical interesting event is a change in the underlying data model.
The mechanism that implements PubSub within the application is called the Event Broker. Event publishers and event subscribers will be automatically registered with the Event Manager by the use of customized .NET attributes.
The use of the Event Manager in no way precludes the application from doing its own event management using the standard .NET event/delegate mechanism.
Using the EventBroker in a class
Any class that wants to use the Pub/Sub capabilities of the EventBroker should do two thing.
1) Include a reference to the EventBroker's assembly. Also, reference the namespace within your class:
using Magmasystems.TradingSystem.EventManager;
2) Register the class with the EventBroker. A good place to do this is in the class' constructor. To register the class with the Event Broker, all that needs to be done is to put in the following line of code:
EventBroker.Register(this);
There are several .NET attributes that can be used to decorate functions in order to define them as either Event Publishers or Event Subscribers. When one of these attributes is constructed, the EventTopic parameter is examined. The EventTopic is added to the EventManager's internal Dictionary. A Dictionary collection is used in order to provide fast access to a particular Topic/Event pairing.
[EventPublisher(string EventTopic)]
This registers a function or class to be an event publisher. The class will publish an event with a specific name. Any subscribers to that topic will be notified.
[EventSubscriber(string EventTopic, options)]
This registers a subscriber to an event. The attribute should be placed directly above the function that will be used as the event handler. The options can be one of the following:
* Background - the handler is called asynchronously
The string-based topic name can be the actual name of a published topic, or it can be a wildcarded topic like "Magmasystems.TradingSystem.Trade.*".
To fire an event, the static method EventManager.Fire() is called
EventManager.Fire(object obj, string eventTopic, EventArgs args);
Event handlers have the signature:
public delegate bool EventManagerEventHandler(object sender,
string topicName, EventArgs e);
Here is a piece of code that publishes an event (The recipient could be a controller that processes events from views and from the models):
[EventPublisher("Magmasystems.TradingSystem.Controller.Action.GetTrades")]
private void GetTrades()
{
GetTradeArgs args = new GetTradeArgs();
args.CUSIP = "12345";
EventBroker.Fire(this, "Barcap.Gcdit.Odc.Controller.Action.GetTrades", args);
}
Here is a piece of code from a controller that subscribes to an event
[EventSubscriber("Magmasystems.TradingSystem.Controller.Action.GetTrades")]
public void GetTrades(object sender, string topicName, System.EventArgs e)
{
GetTradesUnitOfWork.GetTradesArgs args = e as GetTradesUnitOfWork.GetTradesArgs;
if (e == null)
return;
ExecuteAction(new GetTradesUnitOfWork(this._context.UIApplicationContext, args));
}
There is a .Net event associated with every topic that is registered. Events are really Multicast Delegates. Every subscriber to an event adds an event handler to the list of delegates. When an event is fired, the delegate list is traversed and each delegate will be invoked either synchronously or asynchronously. The options can include the Background flag. This tells the event firing mechanism that the underlying delegate should be invoked asynchronously. If the Background flag is not present in the attribute declaration, then the underlying delegate will be invoked synchronously.
Guidelines for Usage
Any class can publish an event, and any class can subscribe to that event. The event bus is global to the entire application.
The line of code that registers a class with the EventBroker is best put in the constructor of the Base Class.
Following our adoption of a namespace convention, we have also adopted a general naming convention for events. This convention is:
companyname.department.application.domainobject.action
©2006 Marc Adler - All Rights Reserved
Friday, April 14, 2006
Note To Self - Traversing Rows in a Single Band
I am not really a hardcore Infragistics person. I find it amazing that some of the features that I consider essential in their UltraWEBGrid offering are not found in their UltraWINGrid product.
I am writing a prototype of an app that has multiple levels of grouping. I need to be able to double-click on the column header of a band, and have it do something to only the rows in that band. For instance, if the user double-clicks on the column that says 'Selected', then I want every checkbox in that group to be toggled without affecting the rows in any other group.
Sounds easy, right? Find out which header you double-clicked on, and then get the child rows that are associated with that header. The problem is that headers are not considered true rows within UltraWinGrid. They are "UI Elements". They do not have references to the rows that are directly beneath them.
After a while of messing around with the HeaderDoubleClicked event, I finally decided to base my procesing off of the MouseDoubleClick event.
Here is what I finally came up with after a few hours of tinkering around. (By the way, the function 'ToggleSelected' is a good way of walking down the entire tree of rows in a grouped grid.)
It works. The key was the UIElement.GetContext() function, which I finally found by reading a two sentence Knowledge Base article on the Infragistics website.
©2006 Marc Adler - All Rights Reserved
I am writing a prototype of an app that has multiple levels of grouping. I need to be able to double-click on the column header of a band, and have it do something to only the rows in that band. For instance, if the user double-clicks on the column that says 'Selected', then I want every checkbox in that group to be toggled without affecting the rows in any other group.
Sounds easy, right? Find out which header you double-clicked on, and then get the child rows that are associated with that header. The problem is that headers are not considered true rows within UltraWinGrid. They are "UI Elements". They do not have references to the rows that are directly beneath them.
After a while of messing around with the HeaderDoubleClicked event, I finally decided to base my procesing off of the MouseDoubleClick event.
Here is what I finally came up with after a few hours of tinkering around. (By the way, the function 'ToggleSelected' is a good way of walking down the entire tree of rows in a grouped grid.)
private UltraGridRow m_firstRowOfTheHeaderThatWasDoubleClickedOn = null;
private void dgTrades_MouseDoubleClick(object sender, MouseEventArgs e)
{
Infragistics.Win.UIElement element =
this.dgTrades.DisplayLayout.UIElement.ElementFromPoint(new Point(e.X, e.Y));
if (element != null)
{
this.CalcGridRowForDblClick(element);
if (element is Infragistics.Win.TextUIElement)
{
HeaderBase hdr = element.GetContext(typeof(HeaderBase)) as HeaderBase;
if (hdr != null)
this.dgTrades_DoubleClickHeader(hdr);
}
}
}
private void dgTrades_DoubleClickHeader(HeaderBase hdr)
{
if (hdr.Column.Key == "Selected")
{
if (this.m_firstRowOfTheHeaderThatWasDoubleClickedOn != null)
this.ToggleSelected(e.Header,
this.m_firstRowOfTheHeaderThatWasDoubleClickedOn);
}
this.m_firstRowOfTheHeaderThatWasDoubleClickedOn = null;
}
private void ToggleSelected(HeaderBase header, UltraGridRow row)
{
if (row == null)
return;
UltraGridBand band = header.Band;
UltraGridRow childRow = row.GetChild(ChildRow.First, band);
if (childRow == null)
{
// May be a leaf node
UltraGridColumn colSelected = band.Columns["Selected"];
if (colSelected == null)
return;
int idx = colSelected.Index;
for (; row != null; row = row.GetSibling(SiblingRow.Next, false))
{
row.Cells[idx].Value = !((bool)row.Cells[idx].Value);
}
}
else
{
// Is a non-leaf node
this.ToggleSelected(header, childRow);
this.ToggleSelected(header, row.GetSibling(SiblingRow.Next, false));
}
}
private UltraGridRow CalcGridRowForDblClick(Infragistics.Win.UIElement uiElement)
{
this.m_firstRowOfTheHeaderThatWasDoubleClickedOn = null;
if (uiElement == null)
return null;
object elementContext = uiElement.GetContext(typeof(UltraGridRow));
if (elementContext == null)
return null;
UltraGridRow row = elementContext as UltraGridRow;
if (row != null)
this.m_firstRowOfTheHeaderThatWasDoubleClickedOn = row;
return row;
}
It works. The key was the UIElement.GetContext() function, which I finally found by reading a two sentence Knowledge Base article on the Infragistics website.
©2006 Marc Adler - All Rights Reserved
Thursday, April 06, 2006
RIP: Doug Walker (Alien Planetscapes)
Doug Walker, long-time mainstay of the New York Spacerock scene and founder of the band Alien Planetscapes, passed away Tuesday April 4th at home. Doug had a heart condition for several years, and it finally caught up to him.
Doug was one of my oldest friends. I met Doug in 1975, and played in several seminal free-jazz bands with him in the mid to late 70's, including the great Third Sun.
Doug lost his wife Fran 2 years ago in a horrific car crash.
Doug leaves behind his son Evan, who was born three weeks after my own son.
God Bless You, Doug. Now you can jam with Robert Moog.
©2006 Marc Adler - All Rights Reserved
Doug was one of my oldest friends. I met Doug in 1975, and played in several seminal free-jazz bands with him in the mid to late 70's, including the great Third Sun.
Doug lost his wife Fran 2 years ago in a horrific car crash.
Doug leaves behind his son Evan, who was born three weeks after my own son.
God Bless You, Doug. Now you can jam with Robert Moog.
©2006 Marc Adler - All Rights Reserved
Subscribe to:
Posts (Atom)