Monday, 23 March 2015

Creating your own context objects

Context objects


Sitecore provides a number of contexts to allow you to enable and disable certain framework features on a case by case basis. For example if you want to run a piece of code without any security concerns you can use the SecurityDisabler.
using (new SecurityDisabler())
{
    // your code here
}
Any code inside the SecurityDisabler block will ignore all security concerns, so it can create items, edit items, delete items etc without the logged in user having the appropriate permissions. Another common context to use is the BulkUpdateContext. If you are doing a lot of updates to Sitecore items as part of a maintenance routine then putting your code inside a BulkUpdateContext stops Sitecore from recording things in the history engine which will increase the performance of your code.  Similarly the EventDisabler prevents events from being raised.

It's a handy technique that you might want to leverage yourself. In one solution we had caching built at a deep layer, but for certain admin functions we didn't want to use the cache, we always needed the actual data. The solution was to use our own context that could disable caching.

Creating the context object

using Sitecore.Web;
using System;
using System.Collections.Generic;
using System.Threading;

namespace MyNamespace
{
    public class CacheDisabler : System.IDisposable
    {
        // the state of the context is stored in the Context.Items collection
        // and this is the collection key.
        // When the state is "1" the CacheDisabler is enabled, otherwise it is an empty string
        private const string ItemsKey = "CacheDisablerContext";
        // this will store what state the context was in when it
        // was it was created
        private string _oldValue;
        // a variable to track if we have been disposed
        private int m_disposed;
        // an object that allows us to implement locks for
        // thread safety
        private static object _lock = new object();

        public CacheDisabler()
        {
            lock (CacheDisabler._lock)
            {
                // when the context is created we record what the current state is
                // this is needed to support nested context objects.
                this._oldValue = WebUtil.GetItemsString(ItemsKey);
                // set the state in the Items collection to "1"
                WebUtil.SetItemsValue(ItemsKey, "1");
            }
        }

        public void Dispose()
        {
            // I know this looks strange, but it is a thread safe way of saying
            // if (m_disposed == 0) { m_disposed == 1; return 0; }
            if (System.Threading.Interlocked.CompareExchange(ref this.m_disposed, 1, 0) == 0)
            {
                // we have not been previously disposed so we are being disposed now
                // put the state back to what it was when we were created
                WebUtil.SetItemsValue(ItemsKey, this._oldValue);
            }
        }

        public static bool IsActive
        {
            get
            {
                // return if the CacheDisabler is currently active
                return WebUtil.GetItemsString(ItemsKey) == "1";
            }
        }
    }
}
And that's all there is to the disabler itself.

Using the context object


You use the context object the same way you use the built-in ones.
using (new CacheDisabler())
{
    // your code here
}

Implementing the context


All the disabler is doing is tracking its current state though. It is up to you to decide when to use that state, so inside our caching class, before we attempt to get items from the cache we check if the cache disabler is enabled.
public T Get<t>(string key) where T : class
{
    if (CacheDisabler.IsActive)
    {
        return default(T);
    }

    // rest of code to attempt to retrieve the
    // requested item from the cache
}

Context logic explained


Some of the logic in the disabler may seem odd, especially tracking the previous state, but you have to remember that your code could end up in nested contexts. Let's imagine we call code inside a CacheDisabler (we'll call this the outer disabler) that also uses a CacheDisabler (which we'll call the inner disabler).

1 When the outer disabler is created, the Items entry will be an empty string so it stores that in _oldValue, then sets the Items entry to "1".

Context.Items["CacheDisablerContext"] = "1" (IsActive = true)
outer disabler _oldValue = ""

2 When the inner disabler is created the Items entry is "1" so it stores that in _oldValue then sets the Items entry to "1" (in other words, it doesn't change the state).

Context.Items["CacheDisablerContext"] = "1" (IsActive = true)
outer disabler _oldValue = ""
inner disabler _oldValue = "1"

3 When the inner disabler is disposed it puts its _oldValue ("1") back into the Items entry, so the Items entry is still "1".

Context.Items["CacheDisablerContext"] = "1" (IsActive = true)
outer disabler _oldValue = ""

4 When the outer disabler is disposed it puts its _oldValue ("") back into the Items entry, so now the Items entry is an empty string, and if asked the disabler will report it is not active.

Context.Items["CacheDisablerContext"] = "" (IsActive = false)

So using this technique of remembering the current value, then replacing it on disposal means that no matter how many times we nest the context, the first time the context is created the state is flagged as active and subsequent context object creations do nothing. And similarly as the nested context items are disposed, the state does not become inactive until the final outermost context is disposed.

No comments:

Post a Comment