Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Advance Caching with Shared Cache

4.33/5 (7 votes)
12 Feb 2009GPL36 min read 62.1K   692  
This article describes how to implement advance level caching with SharedCache.

Table of Contents

Introduction

A couple of weeks ago, I found a superb article on caching (Exploring Caching in ASP.NET) by Abhijeet Jana. It was an excellent effort by Abhijeet (Thanks Abhi), and I found it a very good starter for learning caching in ASP.NET. For continuing the thread, I would like to give some overview and implementation details of advance level caching.

Background

Ab initio, I would like to provide a glimpse of ASP.NET caching basics. ASP.NET has a powerful caching system that we can use to improve the response time of our applications. It is very useful when an object consumes a lot of server resources during creation. While we cache an object, re-usage of it is faster than any other access. As an implication of this, Web Server responses are much faster than when create objects each time from scratch.

For implementing caching in web applications, ASP.NET provides the Cache object. This object is based on the behavior of a dictionary where the cached items are associated with a simple string key, much like an associate array is supported by other languages, e.g., Perl.

The following code snippet can be used to store and retrieve data from cache in ASP.NET:

For storing:

C#
Cache["keyString"] = "Any kind of object, that can be serialized";

For retrieving:

C#
object cachedValue = Cache["keyString"];

The Problem

Although this sounds very dulcet, by using a single line of code, we can implement a powerful caching feature in web applications. However, it is not the end of the story here. No doubts, ASP.NET caching provides an intuitive and essential feature for web application development, vut it introduces some other intricacies. For example:

  1. It increases Web Server memory usage.
  2. The ASP.NET runtime automatically purges items from cache in the case of heavy memory usage (this is unfair). For more in depth details, you can take a look at: http://www.mnot.net/cache_docs/

At first glimpse, these problems look negligible. This is quite correct in the case of small, simple web applications. But if we consider a large scale enterprise application, which consists of a Business Layer, DAL (Data Access Layer), and sometimes a separate domain layer (which consists of all domain entities), then it becomes quite obvious to opt for some advance level of caching solution that implements distributed caching and load balancing features. In concise, a caching solution must provide the following features:

  • It must provide different levels of caching:
  • It must implement different purging algorithms. For example:
    • LRU – Least Recent Used Item.
    • LFU – Least Frequently Used Item.
    • Time based.
  • It must provide a simple server and client configuration with custom provider sections.
  • It must be absolutely free and Open Source!

Solution

I have explored through several Open Source toolboxes, and I found a solution that has a lot of features as well as is free besides being Open Source. The solution I found is SharedCache.

Now, I would like to provide the details for implementing SharedCache in your application. Please follow the steps given below:

  1. Download the installer package from http://www.sharedcache.com/releases and install it on your system.
  2. Open or create an application in which you want to implement this caching.
  3. Add a reference to MergeSystem.Indexus.WinServiceCommon.dll, which is available in the installation directory of the shared cache.
  4. Add the configuration file into the application (discussed later).
  5. If you want to store a custom object (e.g., an entity or POCO) in the cache, then make it serializable. All value types can be cached without any extraneous effort. E.g.: The following code creates a Person class which can be cached.
  6. C#
    /// <summary>
    /// A custom object that represent a person entity and can be store into cache
    /// </summary>
    [Serializable()]
    class Person
    {
        private string _fName;
        private string _lName;
        private int _age;
    
        public Person()
        {
            this._fName = string.Empty;
            this._lName = string.Empty;
            this._age = 0;
        }
    
        public Person(string firstName, string lastName, int age)
        {
            this._fName = firstName;
            this._lName = lastName;
            this._age = age;
        }
    
        public string FirstName
        {
            get { return _fName; }
            set { _fName = value; }
        }
    
        public string LastName
        {
            get { return _lName; }
            set { _lName = value; }
        }
    
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
    
        public override string ToString()
        {
            return string.Format("First Name : {0},Last Name : {1}, Age : {2}", 
            _fName, _lName, _age);
        }
    
    }
  7. Now, implement a caching wrapper utility class that will be used to invoke the caching functions on SharedCache. Although it is not mandatory, you can directly call those functions.
  8. C#
    /// <summary>
    /// A wrapper class to provide cache manipulation
    /// </summary>
    class CacheUtil
    {
        /// <summary>
        /// Add an object to cache with specified key
        /// </summary>
        /// <param name="key">A key value to asscociate with given object</param>
        /// <param name="obj">An object to store into cache</param>
        public static void Add(string key, object obj)
        {
            IndexusDistributionCache.SharedCache.Add(key, obj);
        }
        /// <summary>
        /// Retrieve an object from cache using given key value
        /// </summary>
        /// <param name="key">A key value to retrieve object from hash</param>
        /// <returns>An object that is associated with given key value</returns>
        public static object Get(string key)
        {
            return IndexusDistributionCache.SharedCache.Get(key);
        }
    
        /// <summary>
        /// Remove an object from cache using given key value
        /// </summary>
        /// <param name="key">A key value to remove item from cache</param>
        /// <returns>Result of remove operation true or false</returns>
        public static bool Remove(string key)
        {
            return IndexusDistributionCache.SharedCache.Remove(key);
        }
        /// <summary>
        /// List all objects present in cache
        /// </summary>
        /// <param name="serverName">Name of server for retrieving cache items</param>
        /// <returns>List of objects from cache</returns>
        public static List<Object> GetObjectList(string serverName)
        {
            List<Object> objs = new List<object>();
            foreach  (string k in IndexusDistributionCache.SharedCache.GetAllKeys(
                serverName))
            {
                objs.Add(IndexusDistributionCache.SharedCache.Get(k));
            }
            return objs;
        }
    }
  9. Yippee! Now we are ready to cache objects. The following code creates a menu based console application that demonstrate several ways to cache objects, delete objects from the cache, as well as to retrieve objects from the cache.
  10. In brief, we can use the following functions to retrieve/add/delete objects to/from cache.

    • CacheUtil.Add - To add objects into the cache.
    • CacheUtil.Get - To retrieve objects from the cache.
    • CacheUtil.Remove - To remove objects from the cache.
    • CacheUtil.GetObjectList - To retrieve all objects stored in the cache.
    C#
    class Program
    {
        static void Main(string[] args)
        {
            Person dummyObject = new Person("Test", "User", 55);
            char myChoice = PrintMenu();
            while (myChoice != 'E')
            {
                string key;
                switch (myChoice)
                {
                    case 'A':
                        key = ReadKeyValue();
                        dummyObject = ReadPersonDetail();
                        CacheUtil.Add(key, dummyObject);
                        Console.WriteLine();
                        Console.WriteLine("Object Added with key : " + key);
                        DisplayObjects();
                        break;
                    case 'B':
                        key = ReadKeyValue();
                        dummyObject = (Person)CacheUtil.Get(key);
                        Console.WriteLine();
                        Console.WriteLine("Object retrieved with key : " + key);
                        Console.WriteLine(dummyObject.ToString());
                        break;
                    case 'C':
                        key = ReadKeyValue();
                        CacheUtil.Remove(key);
                        Console.WriteLine();
                        Console.WriteLine("Object deleted from cache with key : " + key);
                        DisplayObjects();
                        break;
                    case 'D':
                        DisplayObjects();
                        break;
                }
                myChoice = PrintMenu();
            }
        }
    
        static char PrintMenu()
        {
            Console.WriteLine();
            Console.WriteLine("*************************************");
            Console.WriteLine("A : Add an item into cache");
            Console.WriteLine("B : Retrieve item from cache");
            Console.WriteLine("C : Delete item from cache");
            Console.WriteLine("D : List items in cache");
            Console.WriteLine("E : Quit");
            Console.WriteLine("*************************************");
            Console.WriteLine("Enter [A,B,C,D] :");
    
            char ch= Console.ReadKey(false).KeyChar;
            while (!(ch >= 'A' && ch < 'F'))
            {
                Console.WriteLine();
                Console.WriteLine("Invalid choice : " + ch);
                Console.WriteLine("Enter [A,B,C,D] :");
                ch=Console.ReadKey(false).KeyChar;
            }
            return ch;
        }
    
        static string ReadKeyValue()
        {
            Console.WriteLine();
            Console.Write("Enter Key value :");
            return Console.ReadLine();
        }
    
        static Person ReadPersonDetail()
        {
            Person newPerson = new Person();
            Console.WriteLine();
            Console.WriteLine("Enter First Name : ");
            newPerson.FirstName = Console.ReadLine();
            Console.WriteLine("Enter Last Name : ");
            newPerson.LastName = Console.ReadLine();
            Console.WriteLine("Enter Age : ");
            char age = Console.ReadKey(false).KeyChar;
            while (! char.IsNumber(age))
            {
                Console.WriteLine("Invalid, Enter again : ");
                age = Console.ReadKey(false).KeyChar;
            }
            newPerson.Age = int.Parse(age.ToString());
            return newPerson;
        }
    
        static void DisplayObjects()
        {
            List<Object> objs=CacheUtil.GetObjectList("127.0.0.1");
            Console.WriteLine("Object's in cache ");
            Console.WriteLine("************************************");
            foreach (Object obj in objs)
            {
                Console.WriteLine(obj.ToString());
            }
        }
    }
  11. For caching objects, start the cache service by running the following command in the command windows:
  12. c:\> net start IndeXus.Net

    (This will run only when you have installed SharedCache in your machine).

If it started successfully, then enjoy caching with SharedCache.

Initial screen of the demo application, presented with the menu options:

scr1.png

Screen showing an entry submitted to SharedCache:

scr2.png

Screen showing the SharedCache server running status.

srvr.png

How to Configure Caching

Please follow these steps to configure SharedCache in your application:

  1. Insert entries for NLog and SharedCache into configSections. This will provide an opportunity to configure NLog and SharedCache into their specific config sections.
  2. XML
    <configSections>
    
     <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
    
     <section name="NetSharedCache" 
      type="MergeSystem.Indexus.WinServiceCommon.Configuration.
            Client.IndexusProviderSection, MergeSystem.Indexus.WinServiceCommon"/>
    
    </configSections>
  3. Next, add entries for some SharedCache specific entries in appSettings. These settings will be read by SharedCache at runtime, and they control the behavior of SharedCache.
  4. XML
    <appSettings>
        <!--Version Number of SharedCache you are using as reference in
            application -->
        <add key="SharedCacheVersionNumber" value="2.0.0.140"/>
        
        <!--This indicates whether events should be logged while caching objects by  
            SharedCache-->
    
        <add key="LoggingEnable" value="1"/>
        
        <!--This setting points to server where cache service is running-->
        <add key="ServiceCacheIpAddress" value="127.0.0.1"/>
        
        <!--This is the port number on which Cache Service is listening of cache 
        intercepts-->
    
        <add key="ServiceCacheIpPort" value="48888"/>
        
        
        <!--
            Enable or disable compression upon client before transfer starts to server.
                Enable: 1
                Disable: 0            
        -->
        <add key="CompressionEnabled" value="0"/>
        
        <!--
            This sets the minimum size required for compressing object in Bytes.
            e.g.
              1kb = 1024
             10kb = 10240
            If this set to 0, every single payload will be compressed
            If this set to negative numbers the compression will not work.
        -->
        <add key="CompressionMinSize" value="10240"/>
        
    </appSettings>
  5. Now, here comes the configuration of SharedCache:
  6. XML
    <NetSharedCache defaultProvider="IndexusSharedCacheProvider">
        <!-- This includes list of server's where cache service is running -->
        <servers>
            <add key="localhost" 
              ipaddress="127.0.0.1" port="48888"/>
        </servers>
        
        <!-- This is list of Cache Provider,By default it is 
        IndexusSharedCacheProvider -->
        
        <providers>
            <add name="IndexusSharedCacheProvider" 
    
            type="MergeSystem.Indexus.WinServiceCommon.Provider.Cache.
            IndexusSharedCacheProvider, MergeSystem.Indexus.WinServiceCommon">
            </add>
        </providers>
    </NetSharedCache>
  7. The next section is dedicated to configuring the logging feature. If you have enabled logging by setting LoggingEnable to '1', then the following section configures logging. SharedCache generates four kinds of logs: General, Traffic, Tracking, and Sync logs. The following section configures logging for all types of logs:
  8. XML
    <nlog autoReload="true" throwExceptions="true">
        <targets async="true">
            <target name="shared_cache_general" type="File" 
              layout="${longdate}|${level:uppercase=true}|${message}" 
              filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                        shared_cache_general_log.txt"/>
    
           <target name="shared_cache_traffic" type="File" 
              layout="${longdate}|${level:uppercase=true}|${message}" 
              filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                        shared_cache_traffic_log.txt"/>
    
          <target name="shared_cache_tracking" type="File" 
            layout="${longdate}|${level:uppercase=true}|${message}" 
            filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                      shared_cache_tracking_log.txt"/>
    
          <target name="shared_cache_sync" type="File" 
            layout="${longdate}|${level:uppercase=true}|${message}" 
            filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                      shared_cache_sync_log.txt"/>
      </targets>
      
      <rules>
            <logger name="General" minlevel="Debug" 
                writeTo="shared_cache_general" final="true"/>
            <logger name="Traffic" minlevel="Debug"
                writeTo="shared_cache_traffic" final="true"/>
            <logger name="Tracking" minlevel="Debug" 
                writeTo="shared_cache_tracking" final="true"/>
            <logger name="Sync" minlevel="Debug" 
                writeTo="shared_cache_sync" final="true"/>
            <logger name="*" minlevel="Debug" 
                writeTo="shared_cache_general"/>
            <logger name="*" minlevel="Info" 
                writeTo="shared_cache_general"/>
        </rules>
    </nlog>

In this article, I have described how to configure advance level caching with SharedCache. I hope this will provide some insight to the reader.

Please feel free to send your suggestions and queries :)

Credits

I would like to extend the credits of this article to SharedCache and Abhijeet:

  1. Shared Cache - For an excellent Open Source caching framework and images:
  2. Image 7

  3. Abhijeet Jana - For his superb article: Exploring Caching in ASP.NET.

DISCLAIMER

This utility is not following any standard Design Pattern and is provided by the author "AS IS" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. All images, logos, and references are copyright of their respective owners.

History

  • Initial revision: 1.0.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)