CODEGATOR

.NET info worth sinking your teeth into!
Welcome to CODEGATOR Sign in | Join | Help
in Search

Martin Cook

Yet another C# developer with a blog.

An event broker solution - part 2.

Last time I discussed what I wanted an event broker for and how I envisioned using one in a project. This time I'm going to continue by talking more about my particular event broker solution. I'll also try to cover some code internals. Since I blog on my lunch hour I'm going to break things up into multiple blog posts - instead of trying to build one long boring blog entry. I think that approach will be better for both of us.

Note that the actual code for my event broker solution can be downloaded at any time by navigating to the downloads section of this website and locating the link for the CG.Event library. Now a quick word of warning: This code is new and hasn't been tested very well and I'm likely to change lots of stuff before I'm happy with things. If you decide to use the code now you'll just have to live with that. Don't complain to me if you decide to use my code today and I change something tomorrow that breaks your build. I'll just say I told you so. Also, my event broker probably doesn't look like anyone else's - and that is by design. I could have tried to clone Microsoft's but I just don't see the point of doing that. The event broker I'm going to outline here meets my needs and is simple enough for me to explain in a weblog. If you need something more flexible and/or feature-rich then go download Microsoft's code from their website.



First lets review what an event broker is: An event broker is a thing that maintains a list of publishers and a list of subscribers. The publishers represent objects with one or more events, and the subscribers represent objects with one or more event handlers. Now, an object can be a publisher, or a subscriber, or both, depending upon it's makeup of events and/or handlers. The event broker listens for events to be fired from it's list of publishers. Whenever an event is detected, the event broker forewords that event to all the subscribers that are interested in it. The publishers and the subscribers don't have to know anything about each other at compile time - they only have to agree on a common set of topics. The topic values are how the event broker decides what events to route to which handlers. Make sense? I hope so!

My event broker works exactly like I described above. It uses attributes to associate a topic with an event or handler at compile time and then reflects on those attributes at runtime to build the list of publishers and subscribers. My solution consists of the following classes:

  • EventBroker - The actual event broker class.
  • EventPublisherModel - An internal class used to represent a publisher.
  • EventSubscriberModel - An internal class used to represent a subscriber.
  • EventSubscribreAttribute - Used to associate an event handler with a topic.
  • EventPublisherAttribute - Used to associate an event with a topic.

Lets talk about the code now! We'll start with the EventPublisherModel class, which is used to represent an event publisher and it looks like this:

internal sealed class EventPublisherModel

{

 

    // The owner of the model.

    private EventBroker owner;

 

    // The client's instance (if any).

    private WeakReference client;

 

    // The client's type.

    private Type clientType;

 

    // The client's event metedata.

    private EventInfo eventInfo;

 

    // The callback for dispatching events.

    private Delegate messageDelegate;

 

    // Indicates whether or not to handle the event asynchronously.

    private bool asyncEvent;

 

    // The topic associated with the event.

    private string topic;

 

    public EventPublisherModel(

        EventBroker owner,

        object client,

        Type clientType,

        EventInfo eventInfo,

        bool asyncEvent,

        string topic

        )

    {

 

        // Sanity check the arguments before using them.

        Guard.ThrowIfNull(owner, "owner");

        Guard.ThrowIfNull(clientType, "clientType");

        Guard.ThrowIfNull(eventInfo, "eventInfo");

 

        // Save the fields.

        this.owner = owner;

        this.client = (client != null) ? new WeakReference(client) : null;

        this.clientType = clientType;

        this.eventInfo = eventInfo;

        this.asyncEvent = asyncEvent;

        this.topic = topic;

 

        // Get the metadata for the internal event handler.

        MethodInfo handler = GetType().GetMethod(

            "ForegroundEventHandler",

            BindingFlags.Instance |

            BindingFlags.NonPublic

            );

 

        // Create the delegate for the callback.

        this.messageDelegate = Delegate.CreateDelegate(

            eventInfo.EventHandlerType,

            this,

            handler

            );

    }

 

    public void Open()

    {

        eventInfo.AddEventHandler(

            client != null ? client.Target : null,

            messageDelegate

            );

    }

 

    public void Close()

    {

        try

        {

            eventInfo.RemoveEventHandler(

                client != null ? client.Target : null,

                messageDelegate

                );

        }

        catch

        {

            // TODO : figure out what to do here.

        }

    }

 

    public override bool Equals(

        object obj

        )

    {

 

        // Sanity check the argument before using it.

        Guard.ThrowIfNull(obj, "obj");

 

        // Is the object the wrong type?

        if (obj.GetType() != typeof(EventPublisherModel))

            return false;

 

        // Recover a reference to the other model.

        EventPublisherModel model = (EventPublisherModel)obj;

 

        // Compare the fields and return the results.

 

        if (model.client != null && client != null &&

            (model.client.Target != client.Target))

            return false;

 

        if (model.clientType != clientType)

            return false;

 

        if (model.eventInfo != eventInfo)

            return false;

 

        return true;

 

    }

 

    public override int GetHashCode()

    {

        return base.GetHashCode() +

            ((client != null) ? client.Target.GetHashCode() : 0) +

            clientType.GetHashCode() +

            eventInfo.GetHashCode();

    }

 

    private void ForegroundEventHandler(object sender, EventArgs e)

    {

        // Should we handle the event synchronously or asynchronoulsy?

        if (asyncEvent)

            ThreadPool.QueueUserWorkItem(

                new WaitCallback(BackgroundEventHandler),

                new object[] { sender, e }

                );

        else

            BackgroundEventHandler(

                new object[] { sender, e }

                );

    }

 

    private void BackgroundEventHandler(object state)

    {

        owner.ForwardToHandlers(

            topic,

            (object[])state

        );

    }

}

 

Let's look at the code from top to bottom by discussing the fields first, then constructor, etc. The model maintains a back reference to the event broker because there is a chance that I might want to have multiple instances of EventBroker down the road. The reference to the publisher itself (what I refer to as a client) is kept via a WeakReference, so that my internal list wont prevent the garbage collector from doing it's job when a publisher is destroyed. The clientType field is what I use to reflect on the publisher at runtime. The eventInfo field tells me everything I'll need to know about the event on the publisher. The messageDelegate field points to a dynamically generated delegate that I'll use to route the event at runtime. The asyncEvent field tells me whether to process events synchronously or asynchronously - with respect to this publisher. And lastly the topic field is the topic that is associated with the publisher.

The constructor begins by validating the parameters (always a good idea) then proceeds to save some of the field values. It then uses reflection to locate an internal event handler that is used to forward events at runtime. Then the delegate for the internal handler is created and saved to the messageDelegate field. Events will be forwarded using this dynamically generated delegate.

The Open method just attach's our delegate to the list of handlers associated with our internal event handler. This will forward the event from the publisher to our internal event handler.

The Close method just removes our delegate from the list of handlers associated with out internal event handler. This will prevent event the publisher from firing events into our internal event handler.

The Equals and GetHashCode overrides are self-explanatory.

The ForegroundEventHandler is our internal event handler, the one called indirectly whenever a publisher fires an event. Inside this handler we look at the asyncEvent field to determine whether we should post the event to the thread-pool in order to process it asynchronously or call the BackgroundEventHandler directly.

 

I've run out of time to blog today so I'll have to continue this another day. Look for the next entry soon.

Comments

No Comments

Leave a Comment

(required) 
(optional)
(required) 
Submit

About Martin

I work as a software engineer specializing in designing and building object-oriented business solutions for Windows platforms using C#. I have been programming professionally for roughly 20 years.

This Blog

Syndication

Terms of Service | Privacy Statement