Cache Loading Pattern

Yesterday was my first delivery of an MSDN Webcast.  My topic - Caching Strategies for ASP.NET.  One of the topics I demonstrated was a Cache Loading Pattern.  This blog is to provide a code-walkthrough of that discussion

First, declare a delegate to represent the cache loading method

1: public delegate object CacheLoader(string cacheItemKey);
Next, create a public static (Shared in VB.NET) method to contain the logic for determining whether an item exists in the cache or not.

1: public static object GetCacheItem( 
2: string cacheItemKey,
3: CacheLoader anyCacheLoader)
4: {
5: HttpContext context = HttpContext.Current;
6: // acquire local reference of cache item
7: object cacheItem = context.Cache[cacheItemKey];
8:
9: // if local reference is null, load the cache
10: if (cacheItem==null)
11: {
12: cacheItem = anyCacheLoader(cacheItemKey);
13: // trace
14: context.Trace.Warn(cacheItemKey + " loaded from resource");
15: } else {
16: // trace
17: context.Trace.Warn(cacheItemKey + " loaded from cache");
18: }// if else
19:
20: // return
21: return cacheItem;
22:
23: }// GetCacheItem(string, CacheLoader);
As seen above, if the item does not exist, then it invokes the delegate to do the loading.  
Here is an example of a method which implements the cache loading delegate signature:

1: // same signature as delegate above 
2: public static object GetEmployeesXml(string cacheItemKey)
3: {
4: HttpContext context = HttpContext.Current;
5:
6: string pathToEmployeesXML =
7: context.Request.MapPath("Employees.xml");
8:
9: DataSet dataSetToReturn = new DataSet();
10: dataSetToReturn.ReadXml(pathToEmployeesXML);
11:
12: context.Cache.Insert(
13: cacheItemKey,
14: dataSetToReturn,
15: new System.Web.Caching.CacheDependency(pathToEmployeesXML));
16:
17: return dataSetToReturn;
18:
19: }// object GetEmployeesXml(string)
Finally, here is an example of how to use the cache loading method:

1: anyDataGrid.DataSource = 
2: CacheLoaders.GetCacheItem("Employees.Xml",
3: new CacheLoader(CacheLoaders.GetEmployeesXml));
The benefit to all of this is the caching behaviour is contained in the various methods which match the delegate signature.  
Thus, one method may load from an underlying resource and place the content in the cache based on a dependency,
whereas another method may load from the resource and put in an absolute expiration. 
But the logic to determine if the cache exists or not is in only one location!

I would really like some feedback on this...

6 Comments

  • I understand the intent, but your delegate has to perform two tasks: create an instance of cached item and insert that item into cache. Since these are two separate and semantically independent tasks, I would highly recommend keeping them separate to avoid such "oopses" as the user of the pattern forgetting to insert the item after creation, or, worse yet, performing more cache manipulation than needed.



    For instance, make your delegate a factory, which only creates an instance of the object to cache and let the "brains" of your pattern handle both insertion and retrieval.



    If you are concerned about flexibility in terms of adding dependencies to the cache item, why not have your delegate return an instance of a CacheItemInfo class, which will contain dependencies and the object to cache. Something like that:



    public delegate CacheItemInfo CacheItemFactory(string cacheItemKey);



    public class CacheItemInfo

    {

    public CacheItemInfo(object cacheItem, params CacheDependency dependencies)

    {

    //...

    }

    //...

    }



    Also, in your example, what is the significance of passing a cacheItemKey to a delegate? You are managing a file with a specific (hard-coded even) name -- why would there be a need to cache more than one instance of it?



    If anything, I would pass an object collection (think XsltArgumentList type of thing), which would serve as a way to parametrize the factory.

  • I actually like the simplicity of it, from the cache users perspective. I think it is probably a matter of tradeoff between your approach and what Dimitri suggested. I would be curious to see a more complete example from Dimitri.



    Another approach that might solve the issue of requiring the implementing delegates from performing the cache inserts would be to push that back into the static GetCacheItem() and then declare the delegate method cache parameters as custom Attributes. This way, the GetCacheItem() could inspect those attributes and it insert to cache itself, while still allowing flexibility by the implementing delegates in terms of cache parameters. A single attribute might suffice which could allow the GetCacheItem() to inspect a config file for the settings for that particular item.



    I haven't tried this, but it seems like it should be possible. Great webcast by the way.



    kfkyle

  • I actually do not understand the issue from Dimitri.



    First of all, according to Dimitri's code suggestion, it implies the cached item will always have a dependency. What if the cache is removed by expiration? How is this set using the CacheItemInfo (overloaded ctors?)



    Secondly, to retrieve ANYTHING from the cache, it requires a key to identify what is needed. So... That is why I pass in a string - to identify what I want.



    Honestly, I do not understand the "problem" perceived by Dimitri. I am not saying he is wrong, I just don't see the problem he sees.

  • I would be interested in a more thourough follow up by Dimitri as well.

  • FWIW, I contacted Dimitri, and he said he'd try to post a follow up.

  • Dimitri,



    DON'T APLOGIZE! This is the reason why I love blogging. Great developers always strive to be better developers. My feedback to your post was that I was unable to determine what the business problem was. Your follow up feedback (THANK YOU) enlightened me very much... so much that I want to create a follow up blog to highlight our discussion.



    We should all be "picky" and share our information freely. I applaud you for doing so.

Comments have been disabled for this content.