Introduction
Let's imagine you've populated an object in memory and then written the object to the ASP.NET runtime cache. Do you know that when you read your object from the cache...
MyClass myObject = ( MyClass )HttpRuntime.Cache[ "mykey" ];
Fig. 1
... you will have a reference to the same object instance that the cache has access to? This means that any changes you make in your object (Fig. 2) will be seen by all future requests for the same cached object.
myObject.Description = "My very own description";
Fig. 2
Now this isn't necessarily a good thing. In fact, this could be very bad for you because maybe, you would like to cache an object, and then preserve the object until it expires from the cache. At the same time, new requests for this cached object might want to make slight modifications to it for their own use, so in essence, if we need to preserve the cache yet allow request modifications - we have to give each request its very own copy of the object.
So How Do We Protect the Data in the ASP.NET Runtime Cache?
Answer: By cloning the object both ways (when inserting into the cache and reading from the cache), using a deep copy, and let me stress "DEEP".
Let me show you some code examples I created for this article. I have created a generic CacheList
class that will allow a user to add a generic type to a list and provide caching and loading from the cache. I started by creating a helper class and added a method that will allow me to clone a list.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;
public static class GenericsHelper
{
public static IList<T> CloneList<T>( IList<T> source ) where T : ICloneable
{
IList<T> clone = new List<T>( source.Count );
foreach ( T t in source )
{
clone.Add( ( T )t.Clone( ) );
}
return clone;
}
}
Then I implemented a simple generic list that will allow me to:
-
Add(T item) : void
-
CacheIt(string cacheKey, DateTime absoluteExpiration) : void
-
LoadCached(string cacheKey) : bool The generic type must implement ICloneable
I have placed a constraint on my list which specifies that we can only stored types that implement the ICloneable
interface. Let me point out that I'm assuming the implementations of this interface are performing deep copies and not shallow ones.
Use a search engine to search for ICloneable
and you will no-doubt find all sorts of discussions about how good or bad it is, and to be honest I can see the arguments against using this interface. In a nutshell, developers are suggesting this method isn't clear enough; it doesn't indicate whether a object that implements it is performing a deep or a shallow copy. However, if you agree with the reasons against using it, then you don't really have a problem; just your own cloneable interface to write... and maybe you could make it generic.
public sealed class CacheList<T> where T : ICloneable
{
private IList<T> _dataList = new List<T>( );
public void Add( T item )
{
_dataList.Add( item );
}
public void CacheIt( string cacheKey, DateTime absoluteExpiration )
{
IList<T> clone = GenericsHelper.CloneList<T>( _dataList );
HttpRuntime.Cache.Insert( cacheKey, clone, null,
absoluteExpiration, Cache.NoSlidingExpiration );
}
public bool LoadCached( string cacheKey )
{
object obj = HttpRuntime.Cache[ cacheKey ];
if ( obj != null && obj is IList<T> )
{
_dataList = GenericsHelper.CloneList<T>( ( IList<T> )obj );
return true;
}
return false;
}
public IList<T> DataList
{
get { return _dataList; }
}
}
Well, that's it for the code that preserves our cached data using cloning. This is a pretty simple example and a very simplistic implementation of a list. However, I didn't want to detract from purpose of the article. I'm going to extend this article in future and show you how this sort of cache access can be wrapped in a business/domain object.
I hope you enjoyed the article, there will be many more to come. I will be writing many more articles about C#, ASP.NET and design on my blog dotnet notepad.
Useful Links
History
- 13th February, 2009: Initial post