Thursday, 26 November 2009

Deep Copy Data Caching in Asp.Net

Data stored in the cache is shared; it is not thread-safe. If you modify the data then the next request will see your changes. Sometimes developers are under the illusion that because they have assigned the data in the cache to a new object variable or performed a shallow copy of the cached data that they can make change without consequence. Not true. Here are a few classes to help protect you. However, you still need to ensure that your cacheable objects are deep copied (derive from my IDeepCopy and implement).

Combine this code for request-safe data caching.

The contents of CacheArgs.cs
using System;
using System.Web.Caching;

namespace ProtectedCache {

    // This is a purely a data object without behavior that plays
    // a bigger part in a framework that I have written.  I'll
    // continue to use it for this example even though it doesn't
    // have an obvious purpose.

    public sealed class CacheArgs {

        private DateTime _absoluteExpiration = Cache.NoAbsoluteExpiration;
        private TimeSpan _slidingExpiration = Cache.NoSlidingExpiration;

        public CacheDependency GetCacheDependency() {
            return null; // Implement when required
        }

        public DateTime GetAbsoluteExpiration() {
            return _absoluteExpiration;
        }

        public void SetAbsoluteExpiration(DateTime dt) {
            _absoluteExpiration = dt;
        }

        public TimeSpan GetSlidingExpiration() {
            return _slidingExpiration;
        }

        public void SetSlidingExpiration(TimeSpan ts) {
            _slidingExpiration = ts;
        }
    }
}
The contents of IDeepCopy.cs
namespace ProtectedCache {

    // Simple interface to indicate intent.  I was thinking
    // about using the existing ICloneable interface and
    // documenting it for developers to ensure deep copies
    // are performed.  I decided not to and opted for a new
    // self-documenting interface.

    public interface IDeepCloneable {

        object DeepClone();
    }
}
The contents of Cacher.cs
using System;
using System.Web;
using System.Collections.Generic;
using System.Threading;

namespace ProtectedCache {

    public static class Cacher {

        public static T Load<T>(string key)
            where T : IDeepCloneable {            

            // Perform a thread-safe read of the cache

            object item = HttpRuntime.Cache[key];
            if (item != null) {

                // Treat an item that is not of expected type
                // as exceptional cases. What you're requesting
                // should be in the cache.

                if (!(item is T)) {
                    throw new InvalidCastException(
                        "Object is not of type " + typeof(T));
                }                
                return (T)((IDeepCloneable)item).DeepClone();
            }
            return default(T);            
        }
        
        public static void Save<T>(T item, string key,
            CacheArgs args) where T : IDeepCloneable {
            
            // Perform thread-safe write to cache, deep cloning
            // the item

            HttpRuntime.Cache.Insert(key,
                item.DeepClone(),
                args.GetCacheDependency(),
                args.GetAbsoluteExpiration(),
                args.GetSlidingExpiration());
        }
    }
}

0 comments:

Post a Comment

My Posts Go Here

Asp.Net Tips

Avoiding Redirects

C#

CSS Tips

Design Patterns

Registry Pattern

Google Ads

SQL & Database Tips

.Net Config