Decoupling Through the Sitecore Event Pool

6

October 21, 2012 by Alistair Deneys

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.

Advertisements

6 thoughts on “Decoupling Through the Sitecore Event Pool

  1. 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.

    • Alistair Deneys says:

      Hey Kevin,
      Isn’t the Sitecore event pool implemented like an event aggregator? I suppose you could address the unsubscribe issue by allowing subscribers to specify the lifetime of their subscription (app lifetime or request cycle only). I’d be interested to read how you’d improve on the Sitecore event pool. There’s your next blog post idea 🙂

  2. Philip says:

    I suppose this technique is only valid for events raised by sublayouts that are not cached? Or is there a mechanism to have the events re-raised if the control is served out of the Sitecore cache?

    • Alistair Deneys says:

      Hi Philip.

      You’re correct, the events are only raised when the sublayout it executed, and if it’s cached it is not executed. I have used this technique in the past to allow different sublayouts to communicate with each other. The publisher notified the subscriber that it has certain content that the subscriber would link to. In this case I can safely cache both sublayouts and everything continues to work on subsequent requests.

      • Philip says:

        I’m still skeptical about using this technique in scenarios where caching is a possibility.

        Philosophically, your goal was to avoid tight coupling. Using the Sitecore event pool accomplishes this with respect to code, but the components are still tightly coupled with respect to configuration. The subscriber has to assume that the publishers caching policies are compatible with its own. And it is the responsibility of the Sitecore developer/admin implement this (and endure it doesn’t get changed). At least the assumptions made in code are detectable at runtime (hopefully through unit tests). I’m not sure how the configuration could be tested to remain consistent.

        Even if the caching policies are the same, I wonder how reliable this design is. Isn’t the Sitecore cache actively managed by background processes? So the subscriber could get pruned out of the cache at different times than the publisher? Then when the subscriber re-renders, there is no event data available. I doubt the subscriber will get properly updated until the next time a publish action invalidates the entire cache.

        All that said, I still don’t want to give up on the idea of using the Sitecore event queue. I just think we need a mechanism to replay the events. Perhaps serializing the event data to be stored in the cache with the sublayout? And then Sitecore automatically re-raises those events each time the sublayout is rendered from cache? Do you think this is a reasonable enhancement request?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

The views expressed on this blog are solely my own and do not necessarily reflect the views of my employer.
%d bloggers like this: