Click here to Skip to main content
Licence CPOL
First Posted 16 Feb 2010
Views 10,252
Downloads 44
Bookmarked 19 times

A single line acces generic ASP.NET cache manager

By | 16 Feb 2010 | Article
Provides a simple architecture that allows fast and easy access to cached methods.

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 give 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)
            {
                if (CacheManager.CurrentCache.Get(CacheKey) == null)
                {
                    result = LoadData();
                    AddDataToCache(result);
                }
                else
                {
                    result = (T)CacheManager.CurrentCache.Get(CacheKey);
                }
            }
            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 web site on my local machine, I experience that sometimes a 2 seconds 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 :-)

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

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralWrong integration of the cache PinmemberMember 24142274:30 17 Feb '10  
GeneralRe: Wrong integration of the cache PinmemberPaolo Costa22:49 17 Feb '10  
GeneralRe: Wrong integration of the cache PinmemberPaolo Costa21:00 19 Feb '10  
GeneralEdit PinmemberJohn Simmons / outlaw programmer11:04 16 Feb '10  
GeneralRe: Edit PinmemberPaolo Costa12:49 16 Feb '10  
GeneralRe:Repost updated pattern. PinmemberJan Palmer6:48 2 Jul '10  

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120528.1 | Last Updated 16 Feb 2010
Article Copyright 2010 by Paolo Costa
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid