Saturday 18 April 2015

Create a class that will switch AppFabric & Page.Cache easily

As I have written on previous blog, that I used AppFabric to store frequently used data within SharePoint 2013 farm. This AppFabric is controlled by Distributed Cache service and because I realize that Distributed Cache wasn't supposed to be used by custom application. I've created a class that will handle the switch between using Page.Cache and AppFabric.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Configuration;

using Microsoft.ApplicationServer.Caching;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.DistributedCaching.Utilities;
using Microsoft.SharePoint.Utilities;

namespace Utility
{
    public class CacheManager
    {
        private static readonly object _lock = new object();
        private static CacheManager _cacheManager;
        private static bool? isAllowedToUseAppFabric = null;
        public CacheManager() { }
        public static CacheManager CurrentCache
        {
            get
            {
                if (_cacheManager == null)
                    _cacheManager = new CacheManager();
                return _cacheManager;
            }
        }
        /// <summary>
        /// get or store cache object
        /// </summary>
        /// <param name="cacheKey">cache key</param>
        /// <param name="isUseAppFabric">indicate if you want to use AppFabri for storing cache data</param>
        /// <returns>object that was stored in cache</returns>
        public object this[string cacheKey, bool isUseAppFabric]
        {
            get
            {
                if (IsAllowedToUseAppFabric && isUseAppFabric)
                {
                    try
                    {
                        return GetAppFabricCache[cacheKey];
                    }
                    catch (Exception e)
                    {
                        //Log the exception to log;
                    }
                }
                return HttpRuntime.Cache[cacheKey];
            }
            set
            {
                lock (_lock)
                {
                    if (value != null)
                    {
                        if (IsAllowedToUseAppFabric && isUseAppFabric)
                        {
                            try
                            {
                                GetAppFabricCache[cacheKey] = value;
                                return;
                            }
                            catch (Exception e)
                            {
                                //Log the exception to log
                            }
                        }
                        HttpRuntime.Cache[cacheKey] = value;
                    }
                    else
                        Remove(cacheKey);
                }
            }
        }
        /// <summary>
        /// get or store cache object
        /// </summary>
        /// <param name="cacheKey">cacheName</param>
        /// <returns>object that was stored in cache</returns>
        public object this[string cacheKey]
        {
            get { return this[cacheKey, true]; }
            set { this[cacheKey, true] = value; }
        }
        /// <summary>
        /// remove item from cache
        /// </summary>
        /// <param name="cacheKey"cacheName>the name of cache object you want to remove</param>
        /// <param name="isUseAppFabric">remove object from AppFabric cache</param>
        /// <returns>true if the object has been removed</returns>
        /// <remarks>
        /// If not using AppFabric this method return object that was stored in cache
        /// </remarks>
        public object Remove(string cacheKey, bool isUseAppFabric)
        {
            lock (_lock)
            {
                if (IsAllowedToUseAppFabric && isUseAppFabric)
                {
                    try
                    {
                        return GetAppFabricCache.Remove(cacheKey);
                    }
                    catch (Exception e)
                    {
                        //Log exception to Log
                    }
                }
                return HttpRuntime.Cache.Remove(cacheKey);
            }
        }
        /// <summary>
        /// remove item from cache
        /// </summary>
        /// <param name="cacheKey"cacheName>the name of cache object you want to remove</param>
        /// <returns>true if the object has been removed</returns>
        public object Remove(string cacheKey)
        {
            return Remove(cacheKey, true);
        }
        #region AppFabric
        private static DataCache _appFabricCache;
        private DataCache CreateCache()
        {
            DataCacheFactory factory = new DataCacheFactory();
            DataCache cache = factory.GetCache(CacheName);
            return _appFabricCache;
        }
        private string CacheName
        {
            get
            {
                return "CacheServiceName";//change this to get a value from configuration file
            }
        }
        private DataCache GetAppFabricCache
        {
            get
            {
                if (_appFabricCache == null)
                    _appFabricCache = CreateCache();
                return _appFabricCache;
            }
        }
        private bool IsAllowedToUseAppFabric
        {
            get
            {
                if (!isAllowedToUseAppFabric.HasValue)
                    isAllowedToUseAppFabric = bool.parse(ConfigurationManager.AppSettings["IsUseAppFabric"]);
                return isAllowedToUseAppFabric.Value;
            }
        }
        #endregion
    }
}

Configuration file (web.config)

Inside tag configSections
<section name="dataCacheClient" type="Microsoft.ApplicationServer.Caching.DataCacheClientSection, Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" allowLocation="true" allowDefinition="Everywhere" />

Inside tag Configuration after closing tag configSections
<dataCacheClient>
    <hosts>
      <host name="<cacheservername>" cachePort="22233" />
    </hosts>
</dataCacheClient>

Inside tag assemblyBinding
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />        
        <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.ApplicationServer.Caching.Core.dll"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.ApplicationServer.Caching.Client.dll"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.WindowsFabric.Common" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.WindowsFabric.Common.dll"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.WindowsFabric.Data.Common" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.WindowsFabric.Data.Common.dll"/>
      </dependentAssembly>

Inside tag appSettings
<add key="IsUseAppFabric" value="true" />

To store an object to cache with key "Key1", use this command
Utility.CacheManager.CurrentCache["Key1"]; = object1;

To get an object from cache with key "Key1", use this command
var object1 = Utility.CacheManager.CurrentCache["Key1"];

To change from AppFabric to HttpRuntime.Cache (equivalent with page.cache), set value to false in appSetting "IsUseAppFabric".

Some point to note:

  1. If SharePoint is configured using load balancer with 2 or more WFE, AppFabric should be used to prevent data inconsistency.
  2. HttpRuntime.Cache is faster than AppFabric and can process up to 500.000 items a second. Use HttpRuntime.Cache if SharePoint is having only 1 WFE.
  3. AppFabric will store your data by serializing it this include object from linq to sql. But there is one object that is FK field column which reference to another table that didn't get DataMember attribute and It won't get stored by AppFabric (it will lose it's value when it get retrieved from cache).
  4. Unless you have a very fast network connection between server. You shouldn't save big data into the AppFabric cache, transporting that data over the network can take some time. You should make sure that data transfer between server should be fast and short (network latency should be bellow <1 ms)
This purpose of this class was to switch off to AppFabric whenever I encounter any problem when using Distributed Cache.

No comments:

Post a Comment