Introduction
This article provides a simple architecture that allows a single line access to all cached methods configured in a web site.
Using the code
The provided source code contains a Visual Studio 2008 web site.
This is the syntax to use in order to call a cached method:
Product product = new LoadProductMethod(
1,
5,
System.Web.Caching.CacheItemPriority.Normal,
true,
true).GetData();
This method tries to retrieve a product (key = 1) from the database (simulated in the code), and asks to put the result in cache with a 5 seconds expiration time and a normal priority. It also tells to automatic refresh the data when the cached object expires. Of course, this method retrieves the object from the cache if it contains it.
The LoadProductMethod class was created in order to implement the cached LoadProductMethod. For any new cached method implemented, a new class like this has to be created. Of course, you can choose to expose a complete constructor (as in the example), or to limit the parameters that the end consumer can control.
Let's take a look at it:
public class LoadProductMethod : BaseCachedMethod<Product>
{
public LoadProductMethod(int productId,
int expiration,
System.Web.Caching.CacheItemPriority priority,
bool useCache,
bool doCallBack)
{
_productId = productId;
_Expiration = expiration;
_Priority = priority;
_UseCache = useCache;
_DoCallBack = doCallBack;
}
int _productId;
protected override string GetCacheKey()
{
return _productId.ToString();
}
protected override Product LoadData()
{
System.Threading.Thread.Sleep(2000);
Product product = new Product(_productId);
return product;
}
}
}
This cached method implements the abstract BaseCachedMethod<T> class. The GetCacheKey() method returns a unique string that identifies the query parameters. The LoadData() method contains the actual code that retrieves data from the database (this is simulated by means of a Sleep call).
What is written so far is the only custom code that has to be written in order to implement a new cached method.
Let's now have a look at the BaseCachedMethod<T> class.
public abstract class BaseCachedMethod<T>
{
protected int _Expiration = 60;
protected System.Web.Caching.CacheItemPriority _Priority =
System.Web.Caching.CacheItemPriority.Normal;
protected bool _DoCallBack = true;
protected bool _UseCache = true;
private string CacheKey
{
get
{
return this.GetType().ToString() +
"-" +
this.GetCacheKey();
}
}
private void AddDataToCache(T localResult)
{
if (_DoCallBack)
{
CacheManager.CurrentCache.Insert(CacheKey,
localResult,
null,
DateTime.Now.AddSeconds(_Expiration),
System.Web.Caching.Cache.NoSlidingExpiration,
_Priority,
new CacheItemRemovedCallback(LoadCache));
}
else
{
CacheManager.CurrentCache.Insert(CacheKey,
localResult,
null,
DateTime.Now.AddSeconds(_Expiration),
System.Web.Caching.Cache.NoSlidingExpiration,
_Priority,
null);
}
}
protected abstract string GetCacheKey();
protected abstract T LoadData();
private void LoadCache(string cacheKey,
object obj,
System.Web.Caching.CacheItemRemovedReason reason)
{
if (reason != System.Web.Caching.CacheItemRemovedReason.Removed &&
reason != System.Web.Caching.CacheItemRemovedReason.Underused)
{
if (obj != null)
{
CacheManager.CurrentCache.Insert(cacheKey, obj);
}
T localResult = LoadData();
AddDataToCache(localResult);
}
}
public T GetData()
{
T result = default(T);
if (_UseCache)
{
object objInCache = CacheManager.CurrentCache.Get(CacheKey);
if (objInCache == null)
{
result = LoadData();
AddDataToCache(result);
}
else
{
result = (T)objInCache;
}
}
else
{
result = LoadData();
}
return result;
}
}
This class has one public method (GetData) that contains the logic needed to check whether an object is contained in the Cache or has to be retrieved from the data source.
LoadCache is passed as a callback method (when DoCallBack == true) to the Cache.Insert method so the LoadData method is automatically called when the object expires.
The LoadCache method, before calling the LoadData method, inserts in Cache the expired object so the user will never experience a direct query on the data source as data will be always available in the Cache (of course, in order to preserve scalability, this doesn't happen when the object is explicitly removed by the ASP.NET Cache or by code).
Running the demo website on my local machine, I experience that sometimes a
two second delay occurs when requesting the page. To be honest, I don't understand why this happens, as debugging the code I can see the object in the Cache dictionary. Any suggestion to solve the mystery will be appreciated
Paolo Costa is a software developer with long experience on any kind of .NET application. He lives in Italy and works in Switzerland for a credit card payment acquiring company.