Decoupling Through the Sitecore Event Pool
There are several ways to allow disparate components in a .net application to communicate. And usually it involves events. Standard .net events require a reference to the object exposing the event to connect an event handler to that event, in the following manner.
var someObject = FindObject();
someObject.TheEvent += new EventHandler(MyEventHandler);
How that FindObject()
method is implemented will depend on the context in which the method will be called (from a control, from a service, etc) and where the object being found resides (who owns it).
Bringing the problem to the realm of Sitecore, let’s start with a solid example.
Let’s say we have 2 sublayouts hosted on a page that need to communicate. The easiest way for one sublayout to find the other would be to expose a method to locate the second sublayout on a component that will host both sublayouts. This could be a parent sublayout if the two sublayouts will always be hosted within that sublayout, or a layout if there is no common parent control layer in the control tree.
The issue with this approach is that it’s quite rigid. Both sublayouts at some point need a common ancestor control to expose the method used to locate the sublayout exposing the event. This ties both sublayouts to that ancestor control for this approach to work. Not to mention the fact that the subscriber sublayout must know about the publisher sublayout and the ancestor control ahead of time.
This approach is tightly coupled, and we know that’s a bad thing…in some situations. On an implementation for a specific site this might be fine as all the components involved are part of the same project. But what if you’re a module developer (like me!) and need to expose events from your component and allow other developers to subscribe where there will not necessarily be a common ancestor control. Or what if you just want to avoid the hassle of all that wiring up?
Enter the Sitecore event pool. The Sitecore event pool allows disparate components to publish and subscribe to events easily. This is because the linking up of the subscriber to the publisher is done through the pool using an event name, thus decoupling the components. Well, loosely coupling them at least.
There are two ways to subscribe an event handler to an event in Sitecore. Firstly events can be subscribed to declaratively through a Sitecore configuration file. Entries in the /configuration/sitecore/events
element define the events and show the event handlers for each event. Sitecore defines many events which are fired when things of interest happen in the system such as finishing a publish, adding a new user or saving an item. This is also a perfect example of a good use for decoupled events.
The second way to subscribe to an event is programmatically by calling the Sitecore.Events.Event.Subscribe(string, EventHandler)
method. This method allows adding an event handler as a subscriber to an event given the event name.
OK, so that takes care of the subscription to the event, but what about firing the event? It’s as easy as calling the Sitecore.Events.Event.RaiseEvent(string, params object[])
method. One thing to note is that the name of the event (the first parameter) is arbitrary. It does not need to be defined in a configuration file.
Let’s put all this into context. Let’s say we have a sublayout which searches for content using a Lucene index. If I wanted to provide more information about the search results, such as a summary, I would normally have to update the sublayout. And if the sublayout was part of a module it complicates the issue as I’m not in control of the code that I need to update.
We can expose the search results from the sublayout doing the heavy lifting by raising an event. Other disparate code, from other modules or project specific, could then subscribe to the event and act on the parameters passed in the event arguments.
Onto the code. First, we’ll define a custom EventArgs
class to expose the data relevant to the event.
public class CustomEventArgs : EventArgs
{
public Item[] Items { get; set; }
}
Now we’ll raise the event in the sublayout that performs the search.
var args = new CustomEventArgs();
args.Items = GetSearchItems();
Sitecore.Events.Event.RaiseEvent("custom:searchresults", args);
And we’ll need to subscribe to the event in the summary sublayout.
var handler = new EventHandler(ResultHandler);
Sitecore.Events.Event.Subscribe("custom:searchresults", handler);
And the event handler itself.
protected void ResultHandler(object sender, EventArgs args)
{
var customArgs = Sitecore.Events.Event.ExtractParameter(args, 0) as CustomEventArgs;
ResultCount = customArgs.Items.Length;
}
Note how we extract the custom event args from the generic event args of the handler using the Sitecore.Events.Event.ExtractParameter()
method.
Now, a little gotcha. When you subscribe to an event it creates a strong reference through the event handler. The strong reference will prevent the object containing the event handler from being eligible for garbage collection and the object will hang around for the lifetime of the application. While the object lives, the event handler will continue to respond to the event being raised. And in an ASP.NET application, with each request an instance of the sublayout codebehind (well, an derivative of the codebehind class) is created. Because previous instances are not garbage collected due to the strong reference, this actually looks like a memory leak. Memory usage will continue to rise over time.
To counter this, we need to ensure any events subscribed to programmatically are unsubscribed from. We can do this by storing the event handler reference in a member variable and unsubscribing from the event at a point later in the request lifecycle.
private EventHandler m_handler = null;
protected void Page_Load(object sender, EventArgs args)
{
m_handler = new EventHandler(ResultHandler);
Sitecore.Events.Event.Subscribe("custom:searchresults", m_handler);
}
protected void Page_Unload(object sender, EventArgs args)
{
if(m_handler != null)
Sitecore.Events.Event.Unsubscribe("custom:searchresults", m_handler);
}
And one more note, using this technique you’ll need to ensure your event subscriptions occur before any events are raised for that event, or you’ll miss handing that call of the event. This is quite easily done by having the subscriptions occur as early as possible, such as in the constructor of the class.
With this technique you now have components which are only loosely coupled, making it much easier to respond to the event.
Nice explanation.
Have you considered implementing an Event Aggregator to further reduce the coupling between publishers and subscribers? This pattern also deals directly with the memory management issues associated with eventing and can negate the requirement for explicit unsubscribing.