.NET Distributed Caching Solution Part 4: Configuring Sitecore with Windows Server AppFabric & Cache Eviction

Jun 25, 2012

Here we go into the Part 4 of Configuring Sitecore with Windows Server AppFabric Distributed Caching Solution, and how we can model the cache eviction based on CMS publishes by hooking into the Sitecore publish events. In this, we would discuss first integrating the cache cluster that is installed and configured, and then show how we can create publish handler which hooks into the publish events of sitecore and allows us to evict cache on publish of items.


In the previous parts, I have talked about the installation, configuration and creation of cache clusters. Just to recap:
  •  Part 1 - Installation of Windows Server AppFabric Distributed Caching Solution
  •  Part 2 - Configuration Options for Windows Server AppFabric Distributed Caching Solution
  •  Part 3 - Common Commands for Windows Server AppFabric Distributed Caching Solution
  •  Part 4 - Configuring Sitecore with Windows Server AppFabric Distributed Caching Solution and Cache Eviction using Sitecore Events.
  •  Part 5 - Production Configuration for Windows Server AppFabric Distributed Caching Solution

Now lets get into the usage of the configured cache cluster with Sitecore. Configuration of Sitecore to use AppFabric Solution follows similar steps as configuration of .NET App to use AppFabric as mentioned here. Below is similar object code to help with this integration.


Caching Strategy

public class AppFabricCachePlatformManager {
      private DataCacheFactory _DataCacheFactory = null;
      
public DataCacheFactory CacheFactory {
            get
            
{
                  
return _DataCacheFactory;
             }
            set
             {
                 _DataCacheFactory =
value;
             }
      }
 
     private string _CacheName;
     
public string CacheName
      {
          
get
           
{
              
return _CacheName;
           }
          
set
           {
              _CacheName =
value;
           }
      }
     public AppFabricCachePlatformManager(string CachingHosts, string CacheName)
     {
          this.CacheName = CacheName;
          
List<DataCacheServerEndpoint> cacheCluster = new List<DataCacheServerEndpoint>();
          //Assuming you have list of cache servers delimited by ";"
        
string[] cacheServers = CachingHosts.Split((new string[] { ";" }), StringSplitOptions.RemoveEmptyEntries);
        
if (cacheServers != null)
         {
             
foreach (string cacheServer in cacheServers)
              {
                  try
                 
{
                     
int serverNameLength = cacheServer.IndexOf(":");
                     
string cacheServerName = cacheServer.Substring(0, serverNameLength);
                     
int cacheServerPort = Convert.ToInt32(cacheServer.Substring(serverNameLength + 1));
                      cacheCluster.Add(
new DataCacheServerEndpoint(cacheServerName, cacheServerPort));
                  }
catch (Exception ex) {
                    
throw new Exception("Configuration key CacheHosts value entry should be of the format server1:port1;server2:port2;server3:port3;", ex);
                  }
             }
        } 
        
DataCacheFactoryConfiguration config = new DataCacheFactoryConfiguration();
        config.Servers = cacheCluster;
        config.LocalCacheProperties =
new DataCacheLocalCacheProperties();
      
DataCacheSecurity security = new DataCacheSecurity(DataCacheSecurityMode.None,  DataCacheProtectionLevel.None);
        config.SecurityProperties = security;
     }

     public DataCache GetCache(string p_CacheName)
     {
        
DataCache cache = null;
         cache =
this.CacheFactory.GetCache(p_CacheName);
        
return cache;
     }

     public object Get(string p_CacheKey, string p_CacheName)
     {
         
this.GetCache(p_CacheName).Get(p_CacheKey);
     }

     public bool Remove(string p_CacheKey, string p_CacheName)
     {
         
return this.GetCache(p_CacheName).Remove(p_CacheKey);
     }
 
     public bool Put(string p_CacheKey, object p_CacheValue)
     {
        
return this.GetCache(p_CacheName).Put(p_CacheKey, p_CacheValue);
     }
}

 

 

 

 

Now you can create an instance of this object and be able to integrate the cache cluster on your .NET app.

AppFabricCachePlatformManager appfabric = new AppFabricCachePlatformManager("OSHYN-PN:22233;OSHYN-PN2:22233", "DEV_Cache")

and this would connect to the DEV_Cache on the cache cluster. Now an object "product1" can be stored in cache as follows using the Put statement:

appfabric.Put("ProductKey_1", product1);

and this can be easily retrieved again as:

appfabric.Get("ProductKey_1");


Now assume this Product is an item being managed in Sitecore CMS. As such if the item content updates, then we need to flush this item from cache and and put it in cache the updated object. This is handled by hooking up into the Sitecore publish event and making sure this item is published. 

Eviction Strategy

The eviction strategy consits of clearing the item from cache when the item gets updated on sitecore and is published.  We can add an event handler for publish event and clear cache on publish. Creation of the Publish Handler is as follows:

namespace Company.Domain.Sitecore.Publishing
{
        
public class PublishHandler
        
{
              
public void OnItemPublished(object sender, EventArgs args)
               {
                   
global::Sitecore.Diagnostics.Log.Debug(String.Format("Started Publish handler OnItemPublished"));
                    
if (args == null)
                    {
                        
global::Sitecore.Diagnostics.Log.Debug(String.Format("Finished Publish handler OnItemPublished args=null"));
                       
return;
                     }

                    
global::Sitecore.Publishing.Pipelines.PublishItem.ItemProcessedEventArgs theArgs = args as global::Sitecore.Publishing.Pipelines.PublishItem.ItemProcessedEventArgs
 
                    global::Sitecore.Data.Items.Item currentItem = theArgs.Context.PublishHelper.GetSourceItem(theArgs.Context.ItemId); 
 
                    if ((currentItem != null) && (currentItem.Paths != null) && (currentItem.Paths.IsContentItem))
                     {
                             //Get the product template  
                             string productTemplateGUID = "{AB9791FE-EF31-4BC5-D9EF-8B886C928ACB}";
                            
global::Sitecore.Data.ID productTemplateID = new global::Sitecore.Data.ID(productTemplateGUID);
                             global::Sitecore.Diagnostics.Log.Debug(String.Format("productTemplateID is not null")); 

 
                            if (!currentItem.TemplateID.IsNull && !productTemplateID.IsNull && currentItem.TemplateID == productTemplateID)
                            {
                                  
global::Sitecore.Diagnostics.Log.Debug(String.Format("Product Item with ID={0} has been published", currentItem.ID.ToString())); 
 
                                  //assuming the key is build using sitecore itemid whne storing in cache layer 
                                  
//if there is some external key that you are using to store in cache, 
                                  //then you need to have a Dictionary mapping of sitecore item ids to that 

                                 
//external key and use that instead of currentItem.ID.ToString();
                                 
CacheManager.Instance.CachePlatform.Remove("ProductKey_" + currentItem.ID.ToString()); 
                                 global::Sitecore.Diagnostics.Log.Debug(String.Format("Product Item with ID={0} has been removed from titles cache", currentItem.ID.ToString()));
                              }
                      } 
                     global::Sitecore.Diagnostics.Log.Debug(String.Format("Finished Publish handler OnItemPublished"));
             }
      }
}

Modifying the web.config to take trigger this handler on publish event. In the "<events>" section of the web.config of sitecore, add this:

<event name="publish:itemProcessed" help="Receives an argument of type ItemProcessedEventArgs (namespace: Sitecore.Publishing.Pipelines.PublishItem)" >
            <
handler type="Company.Domain.Sitecore.Publishing.PublishHandler, CompanyDLL" method="OnItemPublished" />
</
event>

This would trigger the publish handler once publish engine finishes processing the item on publish.

In this way, you can check if product item that is cached is modified and can evict it from cache when the item gets updated and published on the sitecore cms side. This event hook up methodology can be used in other forms of events exposed by sitecore.