OK, what have we done at this point? Well, we talked about what an event broker is and how I would like to use one in a future project. I warned everyone about how new my code is, and about how I'm likely to change things down the road. We looked at the attribute classes that my event broker uses to associate a topic with a publisher and/or subscriber. We also looked at the model classes that my event broker uses to represent publishers and subscribers at runtime. Now, finally, we can start to look at the actual event broker. WOOHOO!!!
The EventBroker class itself is too large to list verbatim here. Instead, I'll group my discussion of the code logically, and limit my listings to individual methods and/or code snippets. If you want to see the entire EventBroker class in all it's glory then click on the downloads tab and find the link for the CG.Event library.
The operation of the EventBroker can be broken down into three areas:
- Registration - the process of finding event and handlers (publishers and subscribers) on a class or object.
- Unregistration - the process of locating and removing old information for a publisher or subscriber.
- Operation - the code that actually forwards publications to subscribers at runtime.
Let's start by discussing the registration process. First of all, here are the signatures for the registration methods:
public virtual void Register(object client)
public virtual void Register(Type clientType)
Notice that we can register an object instance or a class type. Both of these public methods defer their processing to a pair of private methods called RegisterEvents and RegisterHandlers. There are also to versions of these methods as well:
private void RegisterEvents(object client)
private void RegisterEvents(Type clientType)
The internal differences between the two version are negligible, and essentially boil down to this: If you register an object instance then the EventBroker will reflect on all the static and non-static handlers and events. If you register a class type then the EventBroker will only reflect on static handlers and events. I leave the choice of which registration method to use up to you.
Here is what the code for one version of the RegisterEvents method looks like:
private void RegisterEvents(
Type clientType
)
{
// Get the collection of all the events.
EventInfo[] tempEvents = clientType.GetEvents(
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic
);
// Loop and find the decorated events.
for (int x = 0; x < tempEvents.Length; x++)
{
// Get the collection of all custom attributes for the event.
object[] attributes = tempEvents[x].GetCustomAttributes(
typeof(EventPublicationAttribute),
true
);
// Loop and find all the topics.
for (int y = 0; y < attributes.Length; y++)
{
// Recover the custom attribute reference.
EventPublicationAttribute attr =
(EventPublicationAttribute)attributes
;
// Create a model for the event.
EventPublisherModel messageEvent = new EventPublisherModel(
this,
null,
clientType,
tempEvents[x],
attr.AsyncEvent,
attr.Topic
);
try
{
// Play nicely with other threads.
syncLock.AcquireWriterLock(Timeout.Infinite);
// Get the list for the topic.
List<EventPublisherModel> list =
GetTopicEventList(attr.Topic);
// Should we add the model to the list?
if (!list.Contains(messageEvent))
{
list.Add(messageEvent);
messageEvent.Open();
}
}
finally
{
// Cleanup the lock.
syncLock.ReleaseWriterLock();
}
}
}
}
The first step is to reflect on the type in order to locate all the static events. Then, for each event we find, we check for the presence of our custom EventPublicationAttribute attribute. The events that aren't decorated we don't care about. For the decorated events we recover the associated attribute and use the information contained within it to create a model instance to represent the publisher. Once we have created the model instance we store it in the EventBroker's dictionary using the topic as a lookup key. The dictionary stores a list of EventPublisherModel instances, with each one representing a different publisher for the same topic. In other words, you might have three classes with event that publish on the "foo" topic. In that scenario, there would be three EventPublisherModel instances in a list, stored in the EventBroker's dictionary under the topic "foo". Make sense?
The code that registers event handlers (subscribers) looks almost identical to the code that registers events (publishers) with just a few minor differences. Here is what one version of the RegisterHandlers method looks like:
private void RegisterHandlers(
Type clientType
)
{
// Get the collection of all the methods.
MethodInfo[] tempMethods = clientType.GetMethods(
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic
);
// Loop and find the decorated methods.
for (int x = 0; x < tempMethods.Length; x++)
{
// Get the collection of all custom attributes for the method.
object[] attributes = tempMethods[x].GetCustomAttributes(
typeof(EventSubscriptionAttribute),
true
);
// Loop and find all the handler topics.
for (int y = 0; y < attributes.Length; y++)
{
// Recover the custom attribute reference.
EventSubscriptionAttribute attr =
(EventSubscriptionAttribute)attributes
;
// Create a model for the event handler.
EventSubscriberModel model = new EventSubscriberModel(
this,
null,
clientType,
tempMethods[x],
attr.Synchronize
);
try
{
// Play nicely with other threads.
syncLock.AcquireWriterLock(Timeout.Infinite);
// Get the list of models for the topic.
List<EventSubscriberModel> list =
GetTopicHandlerList(attr.Topic);
// Should we add the model to the list?
if (!list.Contains(model))
list.Add(model);
}
finally
{
// Cleanup the lock.
syncLock.ReleaseWriterLock();
}
}
}
}
If you look at the code you'll notice that the only differences are that we look for event handlers instead of events, we check for EventSubscriptionAttribute attributes instead of EventPublicationAttribute attributes, and we create EventSubscriberModel instances instead of EventPublisherModel. Which makes sense when you remember that this method is registering handlers instead of events. Oh yea, and subscribers have their own dictionary inside the EventBroker class.
That's about all the time I have for blogging today. Tomorrow I'll cover the unregistation process, then we'll cover the actual process of intercepting and dispatching events at runtime.