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.



Wednesday, May 30, 2012

Google Analytics in SharePoint

In this post, I'm going to discuss how to use Google Analytics with SharePoint. It is quite easy to implement and I have done it with a user control which I developed and with a little bit of additional JavaScript on top of what Google Provides.
Armed with this, you can use Google Analytics for free (I mean its free anyway!) to generate some great tracking data for your websites. Simple, reliable and easy to implement; here is the solution:

  1. In my project solution, I had created an ascx file called GoogleAnalytics.ascx and a code behind file called GoogleAnalytics.ascx.cs. 
  2. In the GoogleAnalytics.ascx file, I have added the script which you get by registering your website in Google Analytics website.
  3. From the code behind file I assigned the tracking code and the meta content for the Google webmaster tool which is provided by Google.
  4. Additionally, I have added a piece of JavaScript which lets you track events such as downloads that happen in your website
The code for the ascx file is as follows:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="GoogleAnalytics.ascx.cs"
    Inherits="MySolution.UserControls.GoogleAnalytics, MySolution, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31578ca297cd4d8e" %>

<script type="text/javascript" src="http://www.google-analytics.com/ga.js"></script>
<script type="text/javascript">
  
    var _gaq = _gaq || []; 
    _gaq.push(['_setAccount', '<%=GoogleAnalyticTrackingCode %>']); 
    _gaq.push(['_trackPageview']); 

    (function () {

        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;

        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);

    })();

    document.onclick = function (event) {
        event = event || window.event;
        var target = event.target || event.srcElement;
        var targetElement = target.tagName.toLowerCase();
        if (targetElement == "a") {
            var href = target.getAttribute("href"); eventCheck(href);
        }

        function eventCheck(href) {
            if ((href.match(/^https?\:/i)) && (!href.match(document.domain))) {
                _gaq.push(['_trackEvent', 'External', 'click', href]);
            }
            else if (href.match(/^mailto\:/i)) {
                _gaq.push(['_trackEvent', 'Email', 'click', href.substr(7)]);
            }
            else if (href.match(/^.*\.(doc|docx|eps|jpg|jpeg|png|svg|xls|xlsx|ppt|pptx|ppt|pdf|zip|txt|swf|vsd|vxd|js|css|rar|exe|dmg|wma|mov|avi|wmv|mp3)$/i)) {
                _gaq.push(['_trackEvent', 'Download', 'click', href]);
            }
        }
    }; 

</script>

<meta name="google-site-verification" content="<%=GoogleAnalyticSiteVerificationMetaName %>" /> 

The code for the ascx.cs file is as follows:



    /// <summary>
    /// The code behind class for the user control
    /// </summary>
    public partial class GoogleAnalytics : System.Web.UI.UserControl
    {
        /// <summary>
        /// This is the tracking code which is provided by Google when you register your website in Google Analytics
        /// Here I'm fetching it from web.config. You can get it from other sources such as your custom SharePoint list etc.
        /// </summary>
        public string GoogleAnalyticTrackingCode
        {
            get
            {
                return ConfigurationManager.AppSettings["GoogleAnalyticTrackingCode"];
            }
        }
        /// <summary>
        /// This is the verification meta name which is provided by Google when you install Google Webmaster in your website
        /// Here I'm fetching it from web.config. You can get it from other sources such as your custom SharePoint list etc.
        /// </summary>
        public string GoogleAnalyticSiteVerificationMetaName
        {
            get
            {
                return ConfigurationManager.AppSettings["GoogleAnalyticSiteVerificationMetaName"];
            }
        }
     
        #region "PageLoadEvent"
        protected void Page_Load(object sender, EventArgs e)
        {
            
        }
        #endregion
    }

Once I have built my solution, I deployed the DLL to GAC and the ascx file in the SharePoint's CONTROLTEMPLATES directory in the 14 hive.


Next I have edited the master page of my site and added the control in the <head> section of the website.
Waited for a couple of hours and the data started coming through.


To learn more about Google Analytics, you can visit the official Google's website here  http://www.google.com/analytics/
Please note: You can use the same control in an ASP .Net website as well.