Friday, May 13, 2011

Enterprise Library 5.0 Caching Provider for Azure AppFabric Caching – Production release updates

My original post has been based on AppFabric Caching CTP2 assemblies and config settings.   There are several updates you need to make it work with the current production assemblies and configuration.

Add references to these assemblies:

From AppFabric SDK:

  • Microsoft.ApplicationServer.Caching.Client
  • Microsoft.ApplicationServer.Caching.Core
  • Microsoft.Web.DistributedCache
  • Microsoft.WindowsFabric.Common
  • Microsoft.WindowsFabric.Data.Common

From EntLib 5.0:

  • Microsoft.Practices.EnterpriseLibrary.Cashing
  • Microsoft.Practices.EnterpriseLibrary.Common
  • Microsoft.Practices.EnterpriseLibrary.Configuration.DesignTime

cashinglibs

NOTE: reference AppFabric cashing and Enterprise library dependencies explicitly on file system, and make sure “Copy Local=True” for those references.  Azure VM does not have EntLib assemblies in GAC like your development environment does.

In the current production release you CAN use named configurations for DataCacheFactoryConfiguration class, and I used azureDefault to explicitly tie EntLib provider to the azure cache config element in the config file.

I have added the retry policy because if your client is not in the cloud, DNS lookups of CNAME records for your azure app may take long and sometimes fail the call to azure cache for the first time, but succeed the next.  Hence my retry code additions below.

AzureCacheManager updates

 

using Microsoft.Practices.EnterpriseLibrary.Caching.Azure.Configuration;


using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;


using Microsoft.ApplicationServer.Caching;


using System.Collections.Generic;


using Microsoft.Practices.EnterpriseLibrary.Caching.Configuration;


using System.Collections.Specialized;


using System.Threading;


using System.Diagnostics;


 


namespace Microsoft.Practices.EnterpriseLibrary.Caching.Azure


{


    [ConfigurationElementType(typeof(CustomCacheManagerData))]


    public class AzureCacheManager : ICacheManager


    {


        #region Consts


 


        private const string DefaultRegionName = "default";


 


        #endregion


 


        #region Members


 


        private DataCache _realCache;


        private int maxRetries = 3;


        private bool suppressExs = true;


 


        #endregion


 


        #region Ctor


 


        /// <summary>


        /// <para>Initializes a new instance of the <see


        /// cref="AzureCacheManager"/>.</para>


        /// </summary>


        /// <param name="configuration">The configuration object


        /// used to set the runtime values</param>


        public AzureCacheManager(AzureCacheManagerData configuration)


        {


            maxRetries = configuration.AzureDataCacheMaxRetries;


            suppressExs = configuration.AzureDataCacheSuppressExceptions;


 


            int attemptNum = 0;


            System.Exception exc = null;


            bool success = false;


            _realCache = null;


 


            do


            {


                attemptNum++;


                try


                {


                    var dcfConfig = new DataCacheFactoryConfiguration(configuration.AzureDataCacheClient);


                    //var dcfConfig = new DataCacheFactoryConfiguration();


                    var factory = new DataCacheFactory(dcfConfig);


                    _realCache = factory.GetDefaultCache();


 


                    success = true;


                }


                catch (System.Exception ex)


                {


                    exc = ex;


                    Thread.Sleep(50);


                    Trace.TraceError(ex.ToString());


                }


            } 


            while ( attemptNum < maxRetries && !success );


 


            if ( !success && !suppressExs )


            {


                throw exc;


            }


        }


 


        /// <summary>


        /// <para>Initializes a new instance of the <see


        /// cref="AzureCacheManager"/>.</para>


        /// </summary>


        /// <param name="configuration">The configuration object


        /// used to set the runtime values</param>


        public AzureCacheManager(NameValueCollection configSettings):this(new AzureCacheManagerData(configSettings))


        {


        }


 


        #endregion


 


        #region ICacheManager Members


 


        public void Add(string key, object value, CacheItemPriority scavengingPriority,


    ICacheItemRefreshAction refreshAction, params ICacheItemExpiration[] expirations)


        {


            Add(key, value);


        }


 


        public void Add(string key, object value)


        {            


            int attemptNum = 0;


            System.Exception exc = null;


            bool success = false;


 


            do


            {


                attemptNum++;


                try


                {


                    // other parameters are currently ignored


                    _realCache.Add(key, value);


 


                    success = true;


                }


                catch (System.Exception ex)


                {


                    exc = ex;


                    Thread.Sleep(50);


                }


            } 


            while ( attemptNum < maxRetries && !success );


 


            if ( !success && !suppressExs )


            {


                throw exc;


            }


        }


 


        public bool Contains(string key)


        {            


            int attemptNum = 0;


            System.Exception exc = null;


            bool success = false;


 


            do


            {


                attemptNum++;


                try


                {


                    // other parameters are currently ignored


                    object obj = _realCache.Get(key);


                    success = true;


                    return obj != null;


                }


                catch (System.Exception ex)


                {


                    exc = ex;


                    Thread.Sleep(50);


                }


            } 


            while ( attemptNum < maxRetries && !success );


 


            if ( !success && !suppressExs )


            {


                throw exc;


            }


 


            return false;


        }


 


        public int Count


        {


            get


            {


                int attemptNum = 0;


                System.Exception exc = null;


                bool success = false;


 


                do


                {


                    attemptNum++;


                    try


                    {


                        int counter = 0;


 


                        // other parameters are currently ignored


                        foreach (var item in _realCache.GetObjectsInRegion("default"))


                        {


                            counter++;


                        }


                        success = true;


                        return counter;


                    }


                    catch (System.Exception ex)


                    {


                        exc = ex;


                        Thread.Sleep(50);


                    }


                } 


                while ( attemptNum < maxRetries && !success );


 


                if ( !success && !suppressExs )


                {


                    throw exc;


                }


                return 0;


            }


        }


 


        public void Flush()


        {


            int attemptNum = 0;


            System.Exception exc = null;


            bool success = false;


 


            do


            {


                attemptNum++;


                try


                {


                    // other parameters are currently ignored


                    _realCache.ClearRegion(DefaultRegionName);


 


                    success = true;


                }


                catch (System.Exception ex)


                {


                    exc = ex;


                    Thread.Sleep(50);


                }


            } 


            while ( attemptNum < maxRetries && !success );


 


            if ( !success && !suppressExs )


            {


                throw exc;


            }


        }


 


        public object GetData(string key)


        {


            int attemptNum = 0;


            System.Exception exc = null;


            bool success = false;


 


            do


            {


                attemptNum++;


                try


                {


                    // other parameters are currently ignored


                    object result = _realCache.Get(key);


 


                    success = true;


                    return result;


                }


                catch (System.Exception ex)


                {


                    exc = ex;


                    Thread.Sleep(50);


                }


            }


            while (attemptNum < maxRetries && !success);


 


            if (!success && !suppressExs)


            {


                throw exc;


            }


            return null;


        }


 


        public void Remove(string key)


        {


            int attemptNum = 0;


            System.Exception exc = null;


            bool success = false;


 


            do


            {


                attemptNum++;


                try


                {


                    // other parameters are currently ignored


                    _realCache.Remove(key);


 


                    success = true;


                }


                catch (System.Exception ex)


                {


                    exc = ex;


                    Thread.Sleep(50);


                }


            }


            while (attemptNum < maxRetries && !success);


 


            if (!success && !suppressExs)


            {


                throw exc;


            }


        }


 


        public object this[string key]


        {


            get


            {


                return GetData(key);


            }


        }


 


        #endregion


    }


}


 



 



 



AzureCacheManagerData updates



 



using System.Configuration;


using Microsoft.Practices.EnterpriseLibrary.Caching.Configuration;


using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;


using System.Collections.Specialized;


 


namespace Microsoft.Practices.EnterpriseLibrary.Caching.Azure.Configuration


{


    /// <summary>


    /// Represents the configuration data for a <see


    /// cref="AzureCacheManager"/>.


    /// </summary>


    public class AzureCacheManagerData : CustomCacheManagerData


    {


        #region Consts


 


        public const string AzureDataCacheClientString = "AzureDataCacheClient";


        public const string AzureDataCacheMaxRetriesString = "AzureDataCacheMaxRetries";


        public const string AzureDataCacheSuppressExceptionsString = "AzureDataCacheSuppressExceptions";


        public const string NamedCacheString = "NamedCache";


 


        #endregion


 


        #region Ctor


 


        /// <summary>


        /// Initializes a new instance of the <see


        /// cref="AzureCacheManagerData"/> class.


        /// </summary>


        public AzureCacheManagerData()


            : base("AzureCacheManager", typeof(AzureCacheManager))


        {


            AzureDataCacheClient = "default";


            NamedCache = "default";


        }


 


        public AzureCacheManagerData(string azureDataCacheClient, string namedCache)


            : base("AzureCacheManager", typeof(AzureCacheManager))


        {


            AzureDataCacheClient = azureDataCacheClient;


            NamedCache = namedCache;


        }


 


        public AzureCacheManagerData(NameValueCollection configSettings)


            : base("AzureCacheManager", typeof(AzureCacheManager))


        {


            AzureDataCacheClient = configSettings[AzureCacheManagerData.AzureDataCacheClientString] ?? "default";


            NamedCache = configSettings[AzureCacheManagerData.NamedCacheString] ?? "default";


            string maxRetriesStr = configSettings[AzureCacheManagerData.AzureDataCacheMaxRetriesString];


            int mr = 3;


            int.TryParse(maxRetriesStr, out mr);


            //overwrite retries to be a valid number, if needed


            AzureDataCacheMaxRetries = (mr > 0 && mr < 5) ? mr : 3;


            string suppressExStr = configSettings[AzureCacheManagerData.AzureDataCacheSuppressExceptionsString];


            bool se = true;


            bool.TryParse(suppressExStr, out se);


            AzureDataCacheSuppressExceptions = (bool.TryParse(suppressExStr, out se))? se : true;


        }


 


        #endregion


 


        #region Properties


 


        [ConfigurationProperty(AzureDataCacheClientString, IsRequired = true)]


        public string AzureDataCacheClient


        {


            get


            {


                return (string)base[AzureDataCacheClientString];


            }


            set


            {


                base[AzureDataCacheClientString] = value;


            }


        }


 


        [ConfigurationProperty(AzureDataCacheMaxRetriesString, IsRequired = false, DefaultValue = 3)]


        public int AzureDataCacheMaxRetries


        {


            get


            {


                return (int)base[AzureDataCacheMaxRetriesString];


            }


            set


            {


                base[AzureDataCacheMaxRetriesString] = value;


            }


        }


 


        [ConfigurationProperty(AzureDataCacheSuppressExceptionsString, IsRequired = false, DefaultValue = true)]


        public bool AzureDataCacheSuppressExceptions


        {


            get


            {


                return (bool)base[AzureDataCacheSuppressExceptionsString];


            }


            set


            {


                base[AzureDataCacheSuppressExceptionsString] = value;


            }


        }


        [ConfigurationProperty(NamedCacheString, IsRequired = true)]


        public string NamedCache


        {


            get


            {


                return (string)base[NamedCacheString];


            }


            set


            {


                base[NamedCacheString] = value;


            }


        }


 


        #endregion


    }


}


 



config sections related to the Enterprise Library Cashing based on this custom Azure Caching provider should look like this:



<configuration>
  <configSections>
    <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
   


<section name="dataCacheClients" type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection, Microsoft.ApplicationServer.Caching.Core" allowLocation="true" allowDefinition="Everywhere"/>



  </configSections>

 


<cachingConfiguration defaultCacheManager="Cache Manager">


    <cacheManagers>


      <add AzureDataCacheClient="azureDefault" type="Microsoft.Practices.EnterpriseLibrary.Caching.Azure.AzureCacheManager, AzureCacheProvider, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"


        name="Cache Manager" />


    </cacheManagers>


    <backingStores>


      <add type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"


        name="NullBackingStore" />


    </backingStores>


  </cachingConfiguration>


  <dataCacheClients>


    <dataCacheClient name="azureDefault">


      <hosts>


        <host name="[YOURSTORE].cache.windows.net" cachePort="22243" />


      </hosts>


      <securityProperties mode="Message" sslEnabled="true">


        <messageSecurity


          authorizationInfo="[YOURTOKEN]">


        </messageSecurity>


      </securityProperties>


      <transportProperties channelInitializationTimeout="60000" receiveTimeout="600000"/>


    </dataCacheClient>


  </dataCacheClients>


 
  <!-- If session state needs to be saved in AppFabric Caching service add the following to web.config inside system.web -->
  <sessionState mode="Custom" customProvider="AppFabricCacheSessionStoreProvider">
    <providers>
      <add name="AppFabricCacheSessionStoreProvider
"
              type="Microsoft.Web.DistributedCache.DistributedCacheSessionStateStoreProvider, Microsoft.Web.DistributedCache
"
              cacheName="default
"
              useBlobMode="false" />
    </providers>
  </sessionState>

  <!-- If output cache content needs to be saved in AppFabric Caching service add the following to web.config inside system.web -->
  <caching>
    <outputCache defaultProvider="DistributedCache">
      <providers>
        <add name="DistributedCache
"
        type="Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider, Microsoft.Web.DistributedCache
"
        cacheName="default" />
      </providers>
    </outputCache>
  </caching>
</configuration
>