Table of Contents
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.
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:
Cache["keyString"] = "Any kind of object, that can be serialized";
For retrieving:
object cachedValue = Cache["keyString"];
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:
- It increases Web Server memory usage.
- 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:
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:
- Download the installer package from http://www.sharedcache.com/releases and install it on your system.
- Open or create an application in which you want to implement this caching.
- Add a reference to MergeSystem.Indexus.WinServiceCommon.dll, which is available in the installation directory of the shared cache.
- Add the configuration file into the application (discussed later).
- 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.
[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);
}
}
- 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.
class CacheUtil
{
public static void Add(string key, object obj)
{
IndexusDistributionCache.SharedCache.Add(key, obj);
}
public static object Get(string key)
{
return IndexusDistributionCache.SharedCache.Get(key);
}
public static bool Remove(string key)
{
return IndexusDistributionCache.SharedCache.Remove(key);
}
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;
}
}
- 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.
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.
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());
}
}
}
- For caching objects, start the cache service by running the following command in the command windows:
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:
Screen showing an entry submitted to SharedCache:
Screen showing the SharedCache server running status.
Please follow these steps to configure SharedCache in your application:
- Insert entries for NLog and SharedCache into
configSections
. This will provide an opportunity to configure NLog and SharedCache into their specific config sections.
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
<section name="NetSharedCache"
type="MergeSystem.Indexus.WinServiceCommon.Configuration.
Client.IndexusProviderSection, MergeSystem.Indexus.WinServiceCommon"/>
</configSections>
- 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.
<appSettings>
<add key="SharedCacheVersionNumber" value="2.0.0.140"/>
<add key="LoggingEnable" value="1"/>
<add key="ServiceCacheIpAddress" value="127.0.0.1"/>
<add key="ServiceCacheIpPort" value="48888"/>
<add key="CompressionEnabled" value="0"/>
<add key="CompressionMinSize" value="10240"/>
</appSettings>
- Now, here comes the configuration of SharedCache:
<NetSharedCache defaultProvider="IndexusSharedCacheProvider">
<servers>
<add key="localhost"
ipaddress="127.0.0.1" port="48888"/>
</servers>
<providers>
<add name="IndexusSharedCacheProvider"
type="MergeSystem.Indexus.WinServiceCommon.Provider.Cache.
IndexusSharedCacheProvider, MergeSystem.Indexus.WinServiceCommon">
</add>
</providers>
</NetSharedCache>
- 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:
<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:
- Shared Cache - For an excellent Open Source caching framework and images:
- 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