Question

My project is mostly using the .net MemoryCache but I do have a component that's using the HTTPCache. This is making cross dependencies much harder to deal with.

Is there anyway I can get the two caches to take dependencies on each other? eg an HTTPCacheChangeMonitor I can give to MemoryCache and a MemoryCacheDependency I can give to HTTPCache.

Was it helpful?

Solution

Since posting this question I've been working on implementing some cross-cache dependencies. Not sure if they are taking the best approach though.

HttpCacheChangeMonitor

Allows ObjectCache items to take a dependency on HTTPCache

public class HttpCacheChangeMonitor : ChangeMonitor
{
    private readonly string _uniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
    private readonly string[] _httpCacheKeys;

    public override string UniqueId
    {
        get { return _uniqueId; }
    }

    public HttpCacheChangeMonitor(string httpCacheKey)
        : this(new[] { httpCacheKey }) { }

    public HttpCacheChangeMonitor(string[] httpCacheKeys)
    {
        _httpCacheKeys = httpCacheKeys;
        Initialise();
    }

    private void Initialise()
    {
        HttpRuntime.Cache.Add(_uniqueId, _uniqueId, new CacheDependency(null, _httpCacheKeys), DateTime.MaxValue, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, Callback);
        InitializationComplete();
    }

    private void Callback(string key, object value, CacheItemRemovedReason reason)
    {
        OnChanged(null);
    }

    protected override void Dispose(bool disposing)
    {
        Debug.WriteLine(
                _uniqueId + " notifying cache of change.", "HttpCacheChangeMonitor");
        HttpRuntime.Cache.Remove(_uniqueId);
    }
}

Test

public class HttpCacheChangeMonitorTests
{
    [Fact]
    public void ChangeMonitorTest()
    {
        HttpRuntime.Cache.Add("ChangeMonitorTest1", "", null, Cache.NoAbsoluteExpiration, new TimeSpan(0,10,0), CacheItemPriority.Normal, null);
        HttpRuntime.Cache.Add("ChangeMonitorTest2", "", null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 10, 0), CacheItemPriority.Normal, null);
        using (MemoryCache cache = new MemoryCache("TestCache", new NameValueCollection()))
        {

            // Add data to cache
            for (int idx = 0; idx < 10; idx++)
            {
                cache.Add("Key" + idx, "Value" + idx, GetPolicy(idx));
            }

            long middleCount = cache.GetCount();

            // Flush cached items associated with "NamedData" change monitors
            HttpRuntime.Cache.Remove("ChangeMonitorTest1");

            long finalCount = cache.GetCount();

            Assert.Equal(10, middleCount);
            Assert.Equal(5, middleCount - finalCount);
            HttpRuntime.Cache.Remove("ChangeMonitorTest2");
        }
    }

    private static CacheItemPolicy GetPolicy(int idx)
    {
        string name = (idx % 2 == 0) ? "ChangeMonitorTest1" : "ChangeMonitorTest2";

        CacheItemPolicy cip = new CacheItemPolicy();
        cip.AbsoluteExpiration = System.DateTimeOffset.UtcNow.AddHours(1);
        cip.ChangeMonitors.Add(new HttpCacheChangeMonitor(name));
        return cip;
    }
}

MemoryCacheDependency

Allows HttpCache items to take a dependency on MemoryCache

public class MemoryCacheDependency : CacheDependency
{
    private readonly string _uniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
    private readonly IEnumerable<string> _cacheKeys;
    private readonly MemoryCache _cache;

    public override string GetUniqueID()
    {
        return _uniqueId;
    }

    public MemoryCacheDependency(MemoryCache cache, string cacheKey)
        : this(cache, new[] { cacheKey }) { }
    public MemoryCacheDependency(MemoryCache cache, IEnumerable<string> cacheKeys)
    {
        _cache = cache;
        _cacheKeys = cacheKeys;
        Initialise();
    }

    private void Initialise()
    {
        var monitor = _cache.CreateCacheEntryChangeMonitor(_cacheKeys);
        CacheItemPolicy pol = new CacheItemPolicy{AbsoluteExpiration = DateTime.MaxValue, Priority = CacheItemPriority.NotRemovable};
        pol.ChangeMonitors.Add(monitor);
        pol.RemovedCallback = Callback;
        _cache.Add(_uniqueId, _uniqueId, pol);
        FinishInit();
    }

    private void Callback(CacheEntryRemovedArguments arguments)
    {
        NotifyDependencyChanged(arguments.Source, EventArgs.Empty);
    }

    protected override void DependencyDispose()
    {
        Debug.WriteLine(
                   _uniqueId + " notifying cache of change.", "ObjectCacheDependency");
        _cache.Remove(_uniqueId);
        base.DependencyDispose();
    }
}

Test

public class MemoryCacheDependencyTests
{
    [Fact]
    public void CacheDependencyTest()
    {
        using (MemoryCache cache = new MemoryCache("TestCache", new NameValueCollection()))
        {
            cache.Add("HttpCacheTest1", DateTime.Now, new CacheItemPolicy {SlidingExpiration = new TimeSpan(0, 10, 0)});
            cache.Add("HttpCacheTest2", DateTime.Now, new CacheItemPolicy {SlidingExpiration = new TimeSpan(0, 10, 0)});

            // Add data to cache
            for (int idx = 0; idx < 10; idx++)
            {
                HttpRuntime.Cache.Add("Key" + idx, "Value" + idx, GetDependency(cache, idx), Cache.NoAbsoluteExpiration, new TimeSpan(0,10,0), CacheItemPriority.NotRemovable, null);
            }

            int middleCount = HttpRuntime.Cache.Count;

            // Flush cached items associated with "NamedData" change monitors
            cache.Remove("HttpCacheTest1");

            int finalCount = HttpRuntime.Cache.Count;

            Assert.Equal(10, middleCount);
            Assert.Equal(5, middleCount - finalCount);
        }
    }

    private static CacheDependency GetDependency(MemoryCache cache, int idx)
    {
        string name = (idx % 2 == 0) ? "HttpCacheTest1" : "HttpCacheTest2";

        return new MemoryCacheDependency(cache, name);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top