Wednesday, July 4, 2012

Singleton Design Pattern with SharePoint


A Singleton design pattern is a software design principle which ensures that a class has one and only one object instance and a global point access to that instance.
In simpler words, you cannot have two instances of a Singleton class.

UML Class diagram:
Singleton
-instance : Singleton
-Singleton()
+Instance() : Singleton

Singleton design pattern is perhaps the simplest design pattern and is used quite widely. It is very handy to implement Facade objects, State objects, your own personalised and customised cached object store etc.
Other uses include implementing Singleton classes in Abstract Factory, Builder, and Prototype patterns
The Singleton pattern is quite widely used in ASP .Net projects. But the current post deals with the usage of this pattern in a SharePoint context. I frequently use this pattern in custom SharePoint development. In customised SharePoint implementation, more often than not, we would be using our own custom WebParts, user controls, workflows etc. In such implementations we might need to use a lot of configurable values. In a typical ASP .Net project, these values are fetched from <appsettings> in the web.config file. The same could be done in SharePoint project i.e. get those values from web.config file of the web-application.
 But, to keep things flexible, I tend to create a custom list in my SharePoint implementation and get the values from the list through a key-value pair. To fetch the data from such configuration lists, I implement a Singleton layer which stores all the configuration data in the memory, inside my custom Singleton object.
The advantages for this are:
  • You reduce database trips to fetch data which can be easily cached and your performance is a lot better.
  • You get a WYSIWYG editor given to you by SharePoint which comes in handy to manage the configuration data for your application.
  • If you have a multi-server farm with a number of Web-Front-End servers, then you do not need to modify configuration data in appsettings in each of the server, every time you need to change them. Just change it from the configuration list and effect will be global.
  • The cached configuration data can be used to store data for a huge variety of stuff such as error or success messages/ subject or body of out going email messages, time out values, URL of logos in site headers and footers etc, as text for labels etc. Site footer HTMLs etc. By maintaining the values in a configurable list, you will avoid getting inside the server to make trivial changes such as correcting the spelling mistake that you might have made in the label of a text box field or to increase/decrease the wait time for a workflow to start.

Now let’s take a look at the code:
In this example, I will be using a SharePoint list called “Configuration_List” which has two fields:

Sr. No.
Field Name
Field Type
Comments
1
Key
Single line of text
This serves as the Key which is used to fetch the configurable value stored against the key. (Originally this was the default title field which I renamed in our example)
2
Value
Multiple line of text
Used to store the value for the particular key. (For the sake of simplicity, I’m not using a Rich Text field. You can use a Rich Text field for a real life implementation).

I’ll be using a .Net Console Application in this example to show the implementation.

    /// <summary>
    /// The Singleton class which holds the configuration values in memory
    /// </summary>
    public class ConfigurationInMemory
    {
        #region variable declarations
        //thread safe object
        private static readonly object padlock = new object();
        //the private instance of the Singleton class
        private static ConfigurationInMemory _instance;
        // the Key-Value object storage from which I will be generating my cached
        // values
        private static List<ConfigKeyValue> objKeyValueCollection = new List<ConfigKeyValue>();
        //this timer object will be refreshing my key value store from time to time
        private System.Timers.Timer _timer = new System.Timers.Timer();
        #endregion

        /// <summary>
        /// the Singleton has a private constructor to ensure that object of the class
        /// cannot be instantiated outside the class itself. This is to ensure that no
        /// more than one instance of the class gets created
        /// </summary>
        private ConfigurationInMemory()
        {
            //Build your key value collection from the SharePoint Configuration_List
            //when the class is getting instantiated.
            BuildKeyValueCollection();

            //Setting the timer interval as 30 mins. Every 30 mins the timer will
            // empty the cache to release memory and update any changes that might
     // have happened in the Config store  
            _timer.Interval = 1000*60*30;
            _timer.Enabled = true;
            _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
        }

        /// <summary>
        /// This property provides a global handle to the Singleton
        /// </summary>
        public static ConfigurationInMemory Instance
        {
            get
            {
                //thread safety
                lock (padlock)
                {
                    //if there is no existing instance then create one. Else return
                    //the existing one.
                    if (_instance == null)
                        _instance = new ConfigurationInMemory();

                    return _instance;
                }
            }
        }

        /// <summary>
        /// This event fires whenever the timer times out
        /// </summary>
                void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            //Build the cache from scratch
            RefreshConfigValuesInMemory();
            Console.WriteLine("Cache is emptied to refresh/release memory");
        }

       
        /// <summary>
        /// This method builds the key value collection in memory by fetching
        /// the key value pairs from the Configuration_List in SharePoint
        /// </summary>
        private void BuildKeyValueCollection()
        {
            using (SPSite ospSite = new SPSite(" http://<url to the site collection>/ "))
            {
                using (SPWeb ospWeb = ospSite.RootWeb)
                {
                    SPList ospList = ospWeb.Lists["Configuration_List"];
                    //Build the query to get all items from the list
                    SPQuery ospQuery = new SPQuery();
                    ospQuery.Query = "<Where><Gt><FieldRef Name='ID' /><Value Type='Counter'>0</Value></Gt></Where>";
                    ospQuery.ViewFields = "<FieldRef Name='Title' NULLABLE='TURE' /><FieldRef Name='Value' NULLABLE='TURE'/>";
                    
                    //Get all items from the list
                    SPListItemCollection ospListItemColl = ospList.GetItems(ospQuery);

                    //Fill up your cache
                    foreach (SPListItem ospListItem in ospListItemColl)
                    {
                        ConfigKeyValue objConfigKeyValue = new ConfigKeyValue();
                        objConfigKeyValue.ConfigKey = ospListItem["Title"].ToString();
                        objConfigKeyValue.ConfigValue = ospListItem["Value"].ToString();
                        objKeyValueCollection.Add(objConfigKeyValue);
                    }
                }
            }

        }
        /// <summary>
        /// This method fetches the value against the supplied key
        /// from memory
        /// </summary>
        /// <param name="strKey">The key for which the value is needed</param>
        /// <returns>The associated value</returns>
        public string GetValueByKeyFromMemory(string strKey)
        {
            try
            {
                return objKeyValueCollection.Where(K => K.ConfigKey == strKey).FirstOrDefault().ConfigValue;
            }
            catch (Exception)
            {
                //In case the value for the key is not there in the memory
                //or the cache is unavailable, then get the value directly from
                // SharePoint. Its good to build a fail-safe so that your application
                // does not crash becuase of fancy coding :)
                return GetValueByKeyFromSharePoint(strKey);
            }
        }

        /// <summary>
        /// This method is a fall back option which will fetch data straight from
        /// SharePoint
        /// in case my custom caching mechanism is being unfaithful to me
        /// </summary>
        /// <param name="strKey"></param>
        /// <returns></returns>
        public string GetValueByKeyFromSharePoint(string strKey)
        {
            using (SPSite ospSite = new SPSite("http://<url to the site collection>/"))
            {
                using (SPWeb ospWeb = ospSite.RootWeb)
                {
                    try
                    {
                        SPList ospList = ospWeb.Lists["Configuration_List"];

                        //Write a simple SPQuery and get the item from SharePoint
                        SPQuery ospQuery = new SPQuery();
                        ospQuery.Query = string.Format(
                            "<Where><Eq><FieldRef Name='Title' /><Value Type='Text'>{0}</Value></Eq></Where>",
                            strKey);
                        ospQuery.ViewFields = "<FieldRef Name='Title' NULLABLE='TURE' /><FieldRef Name='Value' NULLABLE='TURE'/>";

                        return ospList.GetItems(ospQuery)[0]["Value"].ToString();
                    }
                    catch (Exception)
                    {
                        return "No value matching the key was found";
                    }
                }
            }
        }
        /// <summary>
        /// This method will clean the configuration Key value collection
        /// and build it from scratch once again
        /// </summary>
        public void RefreshConfigValuesInMemory()
        {
            objKeyValueCollection.RemoveAll(KV => !string.IsNullOrEmpty(KV.ConfigKey));
            BuildKeyValueCollection();
        }
    }

    /// <summary>
    /// This is a class representing the configuration key value pair
    /// </summary>
    public class ConfigKeyValue
    {
        public string ConfigKey { get; set; }
        public string ConfigValue { get; set; }
    }

    /// <summary>
    /// The console app used to test the concept
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            ConfigurationInMemory objConfigInMemory = ConfigurationInMemory.Instance;
            string strChoice="N";
            do
            {
                //Key to search for
                Console.WriteLine("Enter Key");
                string strKey = Console.ReadLine();

                //Compare the value in memory to the value in SharePoint
                Console.WriteLine(string.Format(
                "Value in Memory: {0}",
                objConfigInMemory.GetValueByKeyFromMemory(strKey)));
                Console.WriteLine(string.Format(
               "Value in SharePoint: {0}",
               objConfigInMemory.GetValueByKeyFromSharePoint(strKey)));

                //Check whether the memory refresh works
                Console.WriteLine("Do you want to Refresh the configuration values in memory? Type Y/N");
                string strRefreshChoice = Console.ReadLine();
                if (strRefreshChoice.ToUpper() == "Y")
                {
                    objConfigInMemory.RefreshConfigValuesInMemory();                   
                }
                Console.WriteLine("Do you want to exit? Type Y/N");
                strChoice = Console.ReadLine();
                Console.WriteLine("////////////////////////////////////////////////////////////");
            } while (strChoice.ToUpper() == "N");
           
           

        }
    }


Now let’s run the application. I have a configuration list with an item whose “Key” field is Sample Key 1.

First I run the console application and check what is the value stored inside my in-memory Singleton collection and what is the value stored in the SharePoint List. Both should be same.

Then I change the value in SharePoint and run the query once again without updating my Singleton collection.

 I see that my in-memory value which is cached remains same, while the value I query from SharePoint has changed.
Next when I refresh my collection, the value is updated and both the value in SharePoint and my Singleton collection are identical once again.