Click here to Skip to main content
Click here to Skip to main content

A single line acces generic ASP.NET cache manager

By , 22 Jan 2013
 

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;
        }

        /// <summary>
        /// This is the only parameter used by this method
        /// </summary>
        int _productId;

        /// <summary>
        /// This method builds a unique string generated by the parameters set 
        /// (in this case only one)
        /// </summary>
        /// <returns>
        protected override string GetCacheKey()
        {
            return _productId.ToString();
        }

        /// <summary>
        /// This method is a concrete implementation of an abstract method and 
        /// contains the code that retrieves the data from the data source
        /// </summary>
        /// <returns>
        protected override Product LoadData()
        {
            //This call simulate a long time running query
            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>
{
    /// <summary>
    /// cache expiration in seconds
    /// </summary>
    protected int _Expiration = 60;

    /// <summary>
    /// Cache priority
    /// </summary>
    protected System.Web.Caching.CacheItemPriority _Priority = 
                   System.Web.Caching.CacheItemPriority.Normal;

    /// <summary>
    /// If true means that the cache will be automatically refreshed after 
    /// expiration
    /// </summary>
    protected bool _DoCallBack = true;

    /// <summary>
    /// If true the object is saved in cache, otherwise it's always
    /// retrieved from data source
    /// </summary>
    protected bool _UseCache = true;

    /// <summary>
    /// This property builds the cache key by using the reflected name of 
    /// the class and the GetCacheKey method implemented in the concrete 
    /// class
    /// </summary>
    private string CacheKey
    {
        get
        {
            return this.GetType().ToString() + 
                   "-" + 
                   this.GetCacheKey();
        }
    }

    /// <summary>
    /// Adds data do cache
    /// </summary>
    /// <param name=""localResult"" />
    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);
        }
    }

    /// <summary>
    /// This abstract method has to be redefined in the concrete class in 
    /// order to define a unique cache key
    /// </summary>
    /// <returns>
    protected abstract string GetCacheKey();

    /// <summary>
    /// This abstract method has to be implemented in the concrete class 
    /// and wiil contain the code that performs the query
    /// </summary>
    /// <returns>
    protected abstract T LoadData();

    /// <summary>
    /// This method calls the LoadData method and is passed to the 
    /// Cache.Insert method as a callback
    /// </summary>
    /// <param name=""cacheKey"" />
    /// <param name=""obj"" />
    /// <param name=""reason"" />
    private void LoadCache(string cacheKey, 
                           object obj, 
                           System.Web.Caching.CacheItemRemovedReason reason)
    {
        //If an object has been explicitly removed or is epired due to 
        //underusage, it is not added to cache.
        if (reason != System.Web.Caching.CacheItemRemovedReason.Removed && 
            reason != System.Web.Caching.CacheItemRemovedReason.Underused)
        {
            if (obj != null)
            {
                //Expired object is immediately added again to cache so the 
                //user doesn't have to wait till the end of the query
                CacheManager.CurrentCache.Insert(cacheKey, obj);
            }
            T localResult = LoadData();
            AddDataToCache(localResult);
        }
    }

    /// <summary>
    /// Gets the method data from data source or cache
    /// </summary>
    /// <returns>
    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 Smile | <img src=

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Paolo Costa
Software Developer (Senior)
Italy Italy
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionLoadCache method has a bug, that's why you wait 2 seconds (for every 5 seconds in your example).memberacincinar28 Jan '13 - 11:30 
GeneralWrong integration of the cachememberMember 241422717 Feb '10 - 4:30 
GeneralRe: Wrong integration of the cachememberPaolo Costa17 Feb '10 - 22:49 
GeneralRe: Wrong integration of the cachememberPaolo Costa19 Feb '10 - 21:00 
GeneralEditmemberJohn Simmons / outlaw programmer16 Feb '10 - 11:04 
GeneralRe: EditmemberPaolo Costa16 Feb '10 - 12:49 
GeneralRe:Repost updated pattern.memberJan Palmer2 Jul '10 - 6:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130513.1 | Last Updated 22 Jan 2013
Article Copyright 2010 by Paolo Costa
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid