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.