Wednesday 22 April 2015

Using JSON as an input parameter for Custom Web Part

Yesterday I stumbled into a problem where we need to create a sophisticated parameter for our web part. An object of class that store configuration for that SharePoint web part. We could create many parameter or using some unique character that could distinguished some information we need. But creating that parameter required additional script to convert it into an object. I know about JSON and what it could do to make transferring object from server side to JavaScript easy. .NET Framework has provided a method to translate a string into a class or vice versa. It was fit to our requirement so we used JSON as an input parameter. First of all I created a class library that will become the structure for JSON input in our Web Part.

public enum EnumClass
{
    Enum1, Enum2
}
public class DomainClass
{
    public string String1 { get; set; }
    public EnumClass EnumField { get; set; }
}

I've created an enum class to show that it can be used to convert enum which become handy for storing configuration parameter. If you want to use this class as an JSON object, you need to add attribute Data DataContract() to its class and DataMember() to its property. Then I created a parameter inside class web part.

[WebBrowsable(true),
 WebDisplayName("JSON"),
 WebDescription("Input JSON String"),
 Personalizable(PersonalizationScope.Shared),
 Category("JSON")]
public string JSONInput { get; set; }

We could setup a default value for this parameter so that we could use it as an input example. To setup default value inside web part, add that value inside webpart file.

<properties>
    <property name="Title" type="string">JSONParameter - WebPartJSONParameter</property>
    <property name="Description" type="string">JSON</property>
    <property name="JSONInput" type="string">[{"EnumField":0,"String1":"String1"},{"EnumField":1,"String1":"String2"}]</property>
</properties>

I set that default value as a generic DomainClass list (List<DomainClass>) . The next thing was to code a class that will deserialize it into object.

public static class Converter
{
    public static string Serialize<T>(this T obj)
    {
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
        MemoryStream ms = new MemoryStream();
        serializer.WriteObject(ms, obj);
        string retVal = Encoding.UTF8.GetString(ms.ToArray());
        return retVal;
    }
    public static T Deserialize<T>(this string json)
    {
        T obj = Activator.CreateInstance<T>();
        MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
        obj = (T)serializer.ReadObject(ms);
        ms.Close();
        return obj;
    }
}

Here I used extension class to make it easy when Deserialize or serialize an object. The serialize class will come in handy when you want to make JSON for this web part, just call that method from another project. To deserialize that JSONInput parameter call this in our code.

List<DomainClass> result = JSONInput.Deserialize<List<DomainClass>>();

The result…

download the complete project here: JSON Input

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.

Friday 10 April 2015

AppFabric Command

This are a list of useful command that I've gathered while developing custom web part under SharePoint 2013. This is a distributed cache services command and I've developed my custom web part to use this services. I'm aware that Microsoft didn't support custom development against this distributed cache services. But I need a simple cache solution to support our custom application under SharePoint farm.

​Remove Distributed Cache

Stop-SPDistributedCacheServiceInstance -Graceful
Remove-SPDistributedCacheServiceInstance

Host not found when issuing Remove command above

$SPFarm = Get-SPFarm
$cacheClusterName = "SPDistributedCacheCluster_" + $SPFarm.Id.ToString() 
$cacheClusterManager = [Microsoft.SharePoint.DistributedCaching.Utilities.SPDistributedCacheClusterInfoManager]::Local 
$cacheClusterInfo = $cacheClusterManager.GetSPDistributedCacheClusterInfo($cacheClusterName); 
$instanceName ="SPDistributedCacheService Name=AppFabricCachingService"
$serviceInstance = Get-SPServiceInstance | ? {($_.Service.Tostring()) -eq $instanceName -and ($_.Server.Name) -eq $env:computername}  
$serviceInstance.Delete()

Add a server and Start Distributed Cache service

Add-SPDistributedCacheServiceInstance

Stop distributed cache service instance

$instanceName ="SPDistributedCacheService Name=AppFabricCachingService"
$serviceInstance = Get-SPServiceInstance | ? {($_.service.tostring()) -eq $instanceName -and ($_.server.name) -eq $env:computername}
$serviceInstance.Unprovision()

Start distributed cache service instance

$instanceName ="SPDistributedCacheService Name=AppFabricCachingService"
$serviceInstance = Get-SPServiceInstance | ? {($_.service.tostring()) -eq $instanceName -and ($_.server.name) -eq $env:computername}
$serviceInstance.Provision()

Add cache

New-Cache <CacheName>

Set timetolive cache data

Stop-CacheCluster
Set-CacheConfig -CacheName <cachename> -TimeToLiveMins 20
Start-CacheCluster

Remove cache

Remove-Cache <CacheName>

Get all cache available

Get-Cache | select CacheName

Get cache statistic

Get-CacheStatistics <cachename>