diff --git a/README.md b/README.md index baadeee..440c7c5 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,20 @@ public interface ICacheAside The Add and remove methods are implemented with fire and forget, hence it does not need to be Async as this is handled by the StackExchange.Redis client. DoubleCache comes with the following implementations of this interface -* LocalCache.MemCache - using System.Runtime.Memory -* Redis.RedisCache - using StackExchange.Redis client -* SubscribingCache - a decorator supporting push notifications of cache updates -* PublishingCache - a decorator publishing cache changes -* DoubleCache - a decorator wrapping a local and a remote cache +* *[obsolete]* LocalCache.MemCache - using System.Runtime.Memory, does not support null items. +* LocalCache.WrappingMemoryCache - Allows storage of null items in Memory cache.* +* SystemWebCaching.HttpCache - An in memory cache using HttpContext.Cache. +* Redis.RedisCache - using StackExchange.Redis client. +* Redis.RedisStaleCache - Use redis as a stale cache in order to mitigate cache stampede. +* SubscribingCache - a decorator supporting push notifications of cache updates. +* ExistingItemSubscribingCache - a decorator supporting push notifications, which will only update cache if the item already exists. +* PublishingCache - a decorator publishing cache changes. +* DoubleCache - a decorator wrapping a local and a remote cache. + +\* using a custom proxy object holding the cache items. This is transparent to the client. Depending on your cache need, you can combine these implementations and decorators as you need. The most complete example would be a DoubleCache which takes a local cache decorated with a SubscribingCache and a RedisCache decorated with a PublishingCache. This will result in a local cache that will be in sync with the other local caches; if a value isn't found the value will be retrieved from Redis before ultimately being resolved using the func provided to the cache. + +###Redis Stale Cache +Extends TTL for all objects stored in Redis. Verifies TTL when reading data from redis. If this is less than the default TTL on the redis cache, the cached item will be returned and a new fetch from the dataretriever will be extecuted on a separate thread. Storing the result in the cache. + diff --git a/build.fsx b/build.fsx index 9a6b6c3..bae54b2 100644 --- a/build.fsx +++ b/build.fsx @@ -13,7 +13,7 @@ let references = !! "source/DoubleCache/*.csproj" ++ "source/DoubleCache.SystemWebCaching/DoubleCache.SystemWebCaching.csproj" let testReferences = !! "source/DoubleCacheTests/*.csproj" -let version = "1.6.1" +let version = "2.0.0-beta8" let commitHash = Information.getCurrentSHA1(".") let projectName = "DoubleCache" @@ -67,8 +67,8 @@ Target "CreateNuget" (fun _ -> Version = version Publish = false Dependencies = [ - "StackExchange.Redis", "1.0.488" - "MsgPack.Cli", "0.6.5" + "StackExchange.Redis", "1.2.3" + "MsgPack.Cli", "0.8.1" ] Files = [ (@"DoubleCache.dll", Some @"lib\net45", None) diff --git a/source/DoubleCache.SystemWebCaching/HttpCache.cs b/source/DoubleCache.SystemWebCaching/HttpCache.cs index cf333e7..e1a76f4 100644 --- a/source/DoubleCache.SystemWebCaching/HttpCache.cs +++ b/source/DoubleCache.SystemWebCaching/HttpCache.cs @@ -1,14 +1,21 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Web.Caching; -namespace DoubleCache.SystemWebHttpCache +namespace DoubleCache.SystemWebCaching { public class HttpCache : ICacheAside { + internal class CacheItemWrapper + { + internal object Item { get; } + + internal CacheItemWrapper(object item) + { + Item = item; + } + } + private readonly Cache _cache; private readonly TimeSpan? _defaultTtl; @@ -20,12 +27,12 @@ public HttpCache(Cache cache, TimeSpan? defaultTtl = null) public void Add(string key, T item) { - _cache.Add(key, item, null, CalculateExpire(_defaultTtl), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + Add(key,item,_defaultTtl); } public void Add(string key, T item, TimeSpan? timeToLive) { - _cache.Add(key, item, null, CalculateExpire(timeToLive), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + _cache.Add(key, new CacheItemWrapper(item), null, CalculateExpire(timeToLive), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } public T Get(string key, Func dataRetriever) where T : class @@ -35,13 +42,14 @@ public T Get(string key, Func dataRetriever) where T : class public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T : class { - var item = _cache.Get(key) as T; - if (item != null) - return item; - { - item = dataRetriever.Invoke(); - Add(key, item, timeToLive); - } + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = dataRetriever.Invoke(); + + Add(key, item, timeToLive); + return item; } @@ -52,13 +60,15 @@ public object Get(string key, Type type, Func dataRetriever) public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) { - var item = _cache.Get(key); - if (item != null && item.GetType() == type) - return item; + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + var item = dataRetriever.Invoke(); - item = dataRetriever.Invoke(); Add(key, item, timeToLive); - return item.GetType() == type ? item : null; + + return item; } public Task GetAsync(string key, Func> dataRetriever) where T : class @@ -68,13 +78,14 @@ public Task GetAsync(string key, Func> dataRetriever) where T : cl public async Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class { - var item = _cache.Get(key) as T; - if (item != null) - return item; - { - item = await dataRetriever.Invoke(); - Add(key, item, timeToLive); - } + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = await dataRetriever.Invoke(); + + Add(key, item, timeToLive); + return item; } @@ -85,13 +96,17 @@ public Task GetAsync(string key, Type type, Func> dataRetri public async Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) { - var item = _cache.Get(key); - if (item != null && item.GetType() == type) - return item; + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + + var item = await dataRetriever.Invoke(); + item = item.GetType() == type ? item : null; - item = await dataRetriever.Invoke(); Add(key, item, timeToLive); - return item.GetType() == type ? item : null; + + return item; } public void Remove(string key) @@ -107,5 +122,10 @@ private DateTime CalculateExpire(TimeSpan? ttl) ? DateTime.Now.Add(ttl.Value) : DateTime.MaxValue; } + + public bool Exists(string key) + { + return _cache.Get(key) != null; + } } } diff --git a/source/DoubleCache/CacheFactory.cs b/source/DoubleCache/CacheFactory.cs index 53999d8..177580b 100644 --- a/source/DoubleCache/CacheFactory.cs +++ b/source/DoubleCache/CacheFactory.cs @@ -11,8 +11,9 @@ public static ICacheAside CreatePubSubDoubleCache(IConnectionMultiplexer redisCo { var remoteCache = new RedisCache(redisConnection.GetDatabase(), itemSerializer, defaultTtl); return new DoubleCache( - new SubscribingCache(new LocalCache.MemCache(defaultTtl), new RedisSubscriber(redisConnection, remoteCache, itemSerializer)), - new PublishingCache(remoteCache, new RedisPublisher(redisConnection, itemSerializer))); + new SubscribingCache(new LocalCache.WrappingMemoryCache(defaultTtl), new RedisSubscriber(redisConnection, remoteCache, itemSerializer)), + new PublishingCache(remoteCache, new RedisPublisher(redisConnection, itemSerializer)), + remoteCache); } } } diff --git a/source/DoubleCache/DoubleCache.cs b/source/DoubleCache/DoubleCache.cs index 60e85ac..9ddba89 100644 --- a/source/DoubleCache/DoubleCache.cs +++ b/source/DoubleCache/DoubleCache.cs @@ -1,85 +1,93 @@ -using System; -using System.Threading.Tasks; - -namespace DoubleCache -{ - public class DoubleCache : ICacheAside - { - private readonly ICacheAside _localCache; - private readonly ICacheAside _remoteCache; - - public DoubleCache(ICacheAside localCache,ICacheAside remoteCache) - { - _localCache = localCache; - _remoteCache = remoteCache; - } - - public void Add(string key, T item) - { - _localCache.Add(key, item); - _remoteCache.Add(key, item); - } - - public void Add(string key, T item, TimeSpan? timeToLive) - { - _localCache.Add(key, item, timeToLive); - _remoteCache.Add(key, item, timeToLive); +using System; +using System.Threading.Tasks; +using DoubleCache.Redis; + +namespace DoubleCache +{ + public class DoubleCache : ICacheAside + { + private readonly ICacheAside _localCache; + private readonly ICacheAside _remoteCache; + private readonly IKeyTimeToLive _remoteTimeToLive; + + public DoubleCache(ICacheAside localCache,ICacheAside remoteCache, IKeyTimeToLive remoteTimeToLive) + { + _localCache = localCache; + _remoteCache = remoteCache; + _remoteTimeToLive = remoteTimeToLive; + } + + public void Add(string key, T item) + { + _localCache.Add(key, item); + _remoteCache.Add(key, item); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + _localCache.Add(key, item, timeToLive); + _remoteCache.Add(key, item, timeToLive); } public T Get(string key, Func dataRetriever) where T : class { - return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever)); + return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); } public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T : class { - return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever, timeToLive), timeToLive); + return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever, timeToLive), _remoteTimeToLive.KeyTimeToLive(key)); } public object Get(string key, Type type, Func dataRetriever) { - return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever)); + return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); } public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) { - return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever, timeToLive), timeToLive); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever) - { - return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever)); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) - { - return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), timeToLive); - } - - public Task GetAsync(string key, Func> dataRetriever) where T : class - { - return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever)); - } - - public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class - { - return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever),timeToLive); + return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever, timeToLive), _remoteTimeToLive.KeyTimeToLive(key)); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); } - public void Remove(string key) - { - _localCache.Remove(key); - _remoteCache.Remove(key); - } - - public TimeSpan? DefaultTtl { get - { - return _localCache.DefaultTtl > _remoteCache.DefaultTtl - ? _localCache.DefaultTtl - : _remoteCache.DefaultTtl; - } } - + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); + } + + public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever, timeToLive), _remoteTimeToLive.KeyTimeToLive(key)); + } + + public void Remove(string key) + { + _localCache.Remove(key); + _remoteCache.Remove(key); + } + + public bool Exists(string key) + { + return _localCache.Exists(key) && _remoteCache.Exists(key); + } + + public TimeSpan? DefaultTtl { get + { + return _localCache.DefaultTtl > _remoteCache.DefaultTtl + ? _localCache.DefaultTtl + : _remoteCache.DefaultTtl; + } } + - } -} + } +} diff --git a/source/DoubleCache/DoubleCache.csproj b/source/DoubleCache/DoubleCache.csproj index fc6c56d..48923bf 100644 --- a/source/DoubleCache/DoubleCache.csproj +++ b/source/DoubleCache/DoubleCache.csproj @@ -30,16 +30,15 @@ 4 - - ..\packages\MsgPack.Cli.0.6.5\lib\net45\MsgPack.dll - True + + ..\packages\MsgPack.Cli.0.8.1\lib\net45\MsgPack.dll - - ..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll - True + + ..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll + @@ -52,12 +51,15 @@ + + + diff --git a/source/DoubleCache/ExistingItemSubscribingCache.cs b/source/DoubleCache/ExistingItemSubscribingCache.cs new file mode 100644 index 0000000..755b0e9 --- /dev/null +++ b/source/DoubleCache/ExistingItemSubscribingCache.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace DoubleCache +{ + public class ExistingItemsSubscribingCache : ICacheAside + { + private readonly ICacheAside _cache; + private readonly ICacheSubscriber _cacheSubscriber; + private readonly ConcurrentDictionary _knownTypes; + + public ExistingItemsSubscribingCache(ICacheAside cache, ICacheSubscriber cacheSubscriber) + { + _knownTypes = new ConcurrentDictionary(); + _cache = cache; + _cacheSubscriber = cacheSubscriber; + + _cacheSubscriber.CacheUpdate += OnCacheUpdate; + _cacheSubscriber.CacheDelete += OnCacheDelete; + } + + public void Add(string key, T item) + { + _cache.Add(key, item); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + _cache.Add(key, item, timeToLive); + } + + public T Get(string key, Func dataRetriever) where T : class + { + return _cache.Get(key, dataRetriever); + } + + public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T : class + { + return _cache.Get(key, dataRetriever, timeToLive); + } + + public object Get(string key, Type type, Func dataRetriever) + { + return _cache.Get(key, type, dataRetriever); + } + + public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) + { + return _cache.Get(key, type, dataRetriever, timeToLive); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return _cache.GetAsync(key, type, dataRetriever); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + return _cache.GetAsync(key, type, dataRetriever, timeToLive); + } + + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return _cache.GetAsync(key, dataRetriever); + } + + public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + return _cache.GetAsync(key, dataRetriever, timeToLive); + } + + private async void OnCacheUpdate(object sender, CacheUpdateNotificationArgs e) + { + await CacheUpdateAction(sender, e).ConfigureAwait(false); + } + private async Task CacheUpdateAction(object sender, CacheUpdateNotificationArgs e) + { + + var type = _knownTypes.GetOrAdd(e.Type, Type.GetType(e.Type)); + if (_cache.Exists(e.Key)) + { + var remoteItem = await _cacheSubscriber.GetAsync(e.Key, type).ConfigureAwait(false); ; + + if (remoteItem != null) + { + if (e.SpecificTimeToLive != null) + Add(e.Key, remoteItem, e.SpecificTimeToLive._timeToLive); + else + Add(e.Key, remoteItem); + } + } + } + + private void OnCacheDelete(object sender, CacheUpdateNotificationArgs e) + { + Remove(e.Key); + } + + public void Remove(string key) + { + _cache.Remove(key); + } + + public bool Exists(string key) + { + return _cache.Exists(key); + } + + public TimeSpan? DefaultTtl { get { return _cache.DefaultTtl; } } + } +} diff --git a/source/DoubleCache/ICacheAside.cs b/source/DoubleCache/ICacheAside.cs index c5da507..a48dfda 100644 --- a/source/DoubleCache/ICacheAside.cs +++ b/source/DoubleCache/ICacheAside.cs @@ -22,6 +22,7 @@ public interface ICacheAside void Remove(string key); + bool Exists(string key); TimeSpan? DefaultTtl { get; } } } diff --git a/source/DoubleCache/LocalCache/MemCache.cs b/source/DoubleCache/LocalCache/MemCache.cs index 731acc4..dcf74f7 100644 --- a/source/DoubleCache/LocalCache/MemCache.cs +++ b/source/DoubleCache/LocalCache/MemCache.cs @@ -4,10 +4,11 @@ namespace DoubleCache.LocalCache { + [Obsolete("This implementation does not accept caching of null values, consider using WrappingMemoryCache instead")] public class MemCache : ICacheAside { private readonly TimeSpan? _defaultTtl; - + public MemCache(TimeSpan? defaultTtl = null) { _defaultTtl = defaultTtl; @@ -74,7 +75,7 @@ public async Task GetAsync(string key, Type type, Func> dat if (item != null && item.GetType() == type) return item; - item = await dataRetriever.Invoke(); + item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); return item.GetType() == type ? item : null; } @@ -90,7 +91,7 @@ public async Task GetAsync(string key, Func> dataRetriever, TimeSp if (item != null) return item; { - item = await dataRetriever.Invoke(); + item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); } return item; @@ -101,6 +102,11 @@ public void Remove(string key) MemoryCache.Default.Remove(key); } + public bool Exists(string key) + { + return MemoryCache.Default.Get(key) != null; + } + public TimeSpan? DefaultTtl { get { return _defaultTtl; } } } } diff --git a/source/DoubleCache/LocalCache/WrappingMemoryCache.cs b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs new file mode 100644 index 0000000..66d18a1 --- /dev/null +++ b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs @@ -0,0 +1,117 @@ +using System; +using System.Runtime.Caching; +using System.Threading.Tasks; + +namespace DoubleCache.LocalCache +{ + public class WrappingMemoryCache : ICacheAside + { + internal class CacheItemWrapper + { + internal object Item { get; } + + internal CacheItemWrapper(object item) + { + Item = item; + } + } + + private readonly TimeSpan? _defaultTtl; + + public WrappingMemoryCache(TimeSpan? defaultTtl = null) + { + _defaultTtl = defaultTtl; + } + public void Add(string key, T item) + { + Add(key,item,_defaultTtl); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + var policy = new CacheItemPolicy(); + + if (timeToLive.HasValue) + policy.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(timeToLive.Value); + MemoryCache.Default.Set(key, new CacheItemWrapper(item), policy); + } + + public T Get(string key, Func dataRetriever) where T : class + { + return Get(key, dataRetriever, _defaultTtl); + } + + public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T : class + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = dataRetriever.Invoke(); + Add(key, item, timeToLive); + + return item; + } + + public object Get(string key, Type type, Func dataRetriever) + { + return Get(key, type, dataRetriever, _defaultTtl); + } + + public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + var item = dataRetriever.Invoke(); + Add(key, item, timeToLive); + return item.GetType() == type ? item : null; + } + + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return GetAsync(key, type, dataRetriever, _defaultTtl); + } + + public async Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + var item = await dataRetriever.Invoke().ConfigureAwait(false); + Add(key, item, timeToLive); + return item == null || item.GetType() == type ? item : null; + } + + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return GetAsync(key, dataRetriever, _defaultTtl); + } + + public async Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = await dataRetriever.Invoke().ConfigureAwait(false); + Add(key, item, timeToLive); + + return item; + } + + public void Remove(string key) + { + MemoryCache.Default.Remove(key); + } + + public bool Exists(string key) + { + return MemoryCache.Default.Get(key) != null; + } + + public TimeSpan? DefaultTtl { get { return _defaultTtl; } } + } +} diff --git a/source/DoubleCache/Properties/AssemblyInfo.cs b/source/DoubleCache/Properties/AssemblyInfo.cs index 113fc4f..6ff7bcc 100644 --- a/source/DoubleCache/Properties/AssemblyInfo.cs +++ b/source/DoubleCache/Properties/AssemblyInfo.cs @@ -6,19 +6,19 @@ [assembly: AssemblyDescriptionAttribute("Layered distributed cache-aside implementation")] [assembly: GuidAttribute("505f87a8-3062-4070-af1f-cd7358ccd06a")] [assembly: AssemblyProductAttribute("DoubleCache")] -[assembly: AssemblyVersionAttribute("1.6.0")] -[assembly: AssemblyInformationalVersionAttribute("1.6.0")] -[assembly: AssemblyFileVersionAttribute("1.6.0")] -[assembly: AssemblyMetadataAttribute("githash","ac30f57cc769194a07f4112cf91eddc153a50b4a")] +[assembly: AssemblyVersionAttribute("2.0.0")] +[assembly: AssemblyInformationalVersionAttribute("2.0.0-beta7")] +[assembly: AssemblyFileVersionAttribute("2.0.0")] +[assembly: AssemblyMetadataAttribute("githash","be4ce4a22172039a115715b1b3d1741b5fff3ba3")] namespace System { internal static class AssemblyVersionInformation { internal const System.String AssemblyTitle = "DoubleCache"; internal const System.String AssemblyDescription = "Layered distributed cache-aside implementation"; internal const System.String Guid = "505f87a8-3062-4070-af1f-cd7358ccd06a"; internal const System.String AssemblyProduct = "DoubleCache"; - internal const System.String AssemblyVersion = "1.6.0"; - internal const System.String AssemblyInformationalVersion = "1.6.0"; - internal const System.String AssemblyFileVersion = "1.6.0"; - internal const System.String AssemblyMetadata_githash = "ac30f57cc769194a07f4112cf91eddc153a50b4a"; + internal const System.String AssemblyVersion = "2.0.0"; + internal const System.String AssemblyInformationalVersion = "2.0.0-beta7"; + internal const System.String AssemblyFileVersion = "2.0.0"; + internal const System.String AssemblyMetadata_githash = "be4ce4a22172039a115715b1b3d1741b5fff3ba3"; } } diff --git a/source/DoubleCache/PublishingCache.cs b/source/DoubleCache/PublishingCache.cs index 1f94970..a8993ef 100644 --- a/source/DoubleCache/PublishingCache.cs +++ b/source/DoubleCache/PublishingCache.cs @@ -17,13 +17,13 @@ public PublishingCache(ICacheAside cache, ICachePublisher cachePublisher) public void Add(string key, T item) { _cache.Add(key, item); - _cachePublisher.NotifyUpdate(key, item.GetType().AssemblyQualifiedName); + _cachePublisher.NotifyUpdate(key, typeof(T).AssemblyQualifiedName); } public void Add(string key, T item, TimeSpan? timeToLive) { _cache.Add(key, item, timeToLive); - _cachePublisher.NotifyUpdate(key, item.GetType().AssemblyQualifiedName); + _cachePublisher.NotifyUpdate(key, typeof(T).AssemblyQualifiedName, timeToLive); } public T Get(string key, Func dataRetriever) where T : class @@ -171,6 +171,11 @@ public void Remove(string key) _cachePublisher.NotifyDelete(key); } + public bool Exists(string key) + { + return _cache.Exists(key); + } + public TimeSpan? DefaultTtl { get { return _cache.DefaultTtl; } } } } diff --git a/source/DoubleCache/Redis/IKeyTimeToLive.cs b/source/DoubleCache/Redis/IKeyTimeToLive.cs new file mode 100644 index 0000000..591bbee --- /dev/null +++ b/source/DoubleCache/Redis/IKeyTimeToLive.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace DoubleCache.Redis +{ + public interface IKeyTimeToLive + { + Task KeyTimeToLiveAsync(string key); + TimeSpan? KeyTimeToLive(string key); + } +} diff --git a/source/DoubleCache/Redis/RedisCache.cs b/source/DoubleCache/Redis/RedisCache.cs index 2064cc9..af34a98 100644 --- a/source/DoubleCache/Redis/RedisCache.cs +++ b/source/DoubleCache/Redis/RedisCache.cs @@ -5,7 +5,7 @@ namespace DoubleCache.Redis { - public class RedisCache : ICacheAside + public class RedisCache : ICacheAside, IKeyTimeToLive { private readonly IDatabase _database; private readonly IItemSerializer _itemSerializer; @@ -82,11 +82,11 @@ public Task GetAsync(string key, Type type, Func> dataRetri public async Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) { - var packedBytes = await _database.StringGetAsync(key); + var packedBytes = await _database.StringGetAsync(key).ConfigureAwait(false); if (!packedBytes.IsNull) return _itemSerializer.Deserialize(packedBytes, type); - - var item = await dataRetriever.Invoke(); + + var item = await dataRetriever.Invoke().ConfigureAwait(false); if (item != null && item.GetType() == type) { Add(key, item, timeToLive); @@ -103,11 +103,11 @@ public Task GetAsync(string key, Func> dataRetriever) where T : cl public async Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class { - var packedBytes = await _database.StringGetAsync(key); + var packedBytes = await _database.StringGetAsync(key).ConfigureAwait(false); if (!packedBytes.IsNull) return _itemSerializer.Deserialize(packedBytes); - var item = await dataRetriever.Invoke(); + var item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); return item; } @@ -117,6 +117,21 @@ public void Remove(string key) _database.KeyDelete(key); } + public bool Exists(string key) + { + return _database.KeyExists(key); + } + + public Task KeyTimeToLiveAsync(string key) + { + return _database.KeyTimeToLiveAsync(key); + } + + public TimeSpan? KeyTimeToLive(string key) + { + return _database.KeyTimeToLive(key); + } + public TimeSpan? DefaultTtl { get { return _defaultTtl; } } } } diff --git a/source/DoubleCache/Redis/RedisStaleCache.cs b/source/DoubleCache/Redis/RedisStaleCache.cs index 955fc5e..32a0c05 100644 --- a/source/DoubleCache/Redis/RedisStaleCache.cs +++ b/source/DoubleCache/Redis/RedisStaleCache.cs @@ -119,21 +119,21 @@ public async Task GetAsync(string key, Func> dataRetriever, TimeSp if (timeToLive.HasValue) staleTtl = timeToLive.Value.Add(_staleDuration); - var item = await _redisCache.GetAsync(key, dataRetriever, staleTtl); - var ttl = await _database.KeyTimeToLiveAsync(key); + var item = await _redisCache.GetAsync(key, dataRetriever, staleTtl).ConfigureAwait(false); + var ttl = await _database.KeyTimeToLiveAsync(key).ConfigureAwait(false); if (!ttl.HasValue || ttl.Value < _staleDuration) { ttl = ttl == null ? _staleDuration.Add(_staleDuration) : ttl.Value.Add(_staleDuration); - await _database.KeyExpireAsync(key, ttl); + await _database.KeyExpireAsync(key, ttl).ConfigureAwait(false); ThreadPool.QueueUserWorkItem(async o => { try { - _redisCache.Add(key, await dataRetriever.Invoke(), staleTtl); + _redisCache.Add(key, await dataRetriever.Invoke().ConfigureAwait(false), staleTtl); } catch { //make sure we do not crash. @@ -154,21 +154,21 @@ public async Task GetAsync(string key, Type type, Func> dat if (timeToLive.HasValue) staleTtl = timeToLive.Value.Add(_staleDuration); - var item = await _redisCache.GetAsync(key, type, dataRetriever, staleTtl); - var ttl = await _database.KeyTimeToLiveAsync(key); + var item = await _redisCache.GetAsync(key, type, dataRetriever, staleTtl).ConfigureAwait(false); + var ttl = await _database.KeyTimeToLiveAsync(key).ConfigureAwait(false); if (!ttl.HasValue || ttl.Value < _staleDuration) { ttl = ttl == null ? _staleDuration.Add(_staleDuration) : ttl.Value.Add(_staleDuration); - await _database.KeyExpireAsync(key, ttl); + await _database.KeyExpireAsync(key, ttl).ConfigureAwait(false); ThreadPool.QueueUserWorkItem(async o => { try { - _redisCache.Add(key, await dataRetriever.Invoke(), timeToLive); + _redisCache.Add(key, await dataRetriever.Invoke().ConfigureAwait(false), timeToLive); } catch { //make sure we do not crash. @@ -183,6 +183,11 @@ public void Remove(string key) _redisCache.Remove(key); } + public bool Exists(string key) + { + return _redisCache.Exists(key); + } + public TimeSpan? DefaultTtl { get { return _redisCache.DefaultTtl; } } } } diff --git a/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs b/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs index 77b54bc..a90a55a 100644 --- a/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs +++ b/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs @@ -1,13 +1,21 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; namespace DoubleCache.Serialization { public class BinaryFormatterItemSerializer : IItemSerializer { + private static ConcurrentDictionary _typeCache = new ConcurrentDictionary(); + public byte[] Serialize(T item) { + if (item == null) + return new byte[0]; + var formatter = new BinaryFormatter(); byte[] itemBytes; @@ -23,6 +31,9 @@ public byte[] Serialize(T item) public object Deserialize(byte[] bytes, Type type) { + if (bytes.Length == 0) + return type.IsValueType ? Activator.CreateInstance(type) : null; + var formatter = new BinaryFormatter(); object item; @@ -36,6 +47,9 @@ public object Deserialize(byte[] bytes, Type type) public T Deserialize(Stream stream) { + if (stream.Length == 0) + return default(T); + var formatter = new BinaryFormatter(); return (T)formatter.Deserialize(stream); @@ -44,6 +58,9 @@ public T Deserialize(Stream stream) public T Deserialize(byte[] bytes) { + if (bytes.Length == 0) + return default(T); + var formatter = new BinaryFormatter(); object item; @@ -54,6 +71,10 @@ public T Deserialize(byte[] bytes) return (T)item; } + private static T GetDefault() + { + return default(T); + } } } diff --git a/source/DoubleCache/SubscribingCache.cs b/source/DoubleCache/SubscribingCache.cs index e155559..903f4ef 100644 --- a/source/DoubleCache/SubscribingCache.cs +++ b/source/DoubleCache/SubscribingCache.cs @@ -1,33 +1,33 @@ -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace DoubleCache -{ - public class SubscribingCache : ICacheAside - { - private readonly ICacheAside _cache; - private readonly ICacheSubscriber _cacheSubscriber; - private readonly ConcurrentDictionary _knownTypes; - - public SubscribingCache(ICacheAside cache, ICacheSubscriber cacheSubscriber) - { - _knownTypes = new ConcurrentDictionary(); - _cache = cache; - _cacheSubscriber = cacheSubscriber; - - _cacheSubscriber.CacheUpdate += OnCacheUpdate; - _cacheSubscriber.CacheDelete += OnCacheDelete; - } - - public void Add(string key, T item) - { - _cache.Add(key, item); - } - - public void Add(string key, T item, TimeSpan? timeToLive) - { - _cache.Add(key, item, timeToLive); +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace DoubleCache +{ + public class SubscribingCache : ICacheAside + { + private readonly ICacheAside _cache; + private readonly ICacheSubscriber _cacheSubscriber; + private readonly ConcurrentDictionary _knownTypes; + + public SubscribingCache(ICacheAside cache, ICacheSubscriber cacheSubscriber) + { + _knownTypes = new ConcurrentDictionary(); + _cache = cache; + _cacheSubscriber = cacheSubscriber; + + _cacheSubscriber.CacheUpdate += OnCacheUpdate; + _cacheSubscriber.CacheDelete += OnCacheDelete; + } + + public void Add(string key, T item) + { + _cache.Add(key, item); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + _cache.Add(key, item, timeToLive); } public T Get(string key, Func dataRetriever) where T : class @@ -48,43 +48,43 @@ public object Get(string key, Type type, Func dataRetriever) public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) { return _cache.Get(key, type, dataRetriever, timeToLive); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever) - { - return _cache.GetAsync(key, type, dataRetriever); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) - { - return _cache.GetAsync(key, type, dataRetriever, timeToLive); - } - - public Task GetAsync(string key, Func> dataRetriever) where T : class - { - return _cache.GetAsync(key, dataRetriever); - } - - public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class - { - return _cache.GetAsync(key, dataRetriever, timeToLive); - } - - private async void OnCacheUpdate(object sender, CacheUpdateNotificationArgs e) - { - await CacheUpdateAction(sender, e); - } - private async Task CacheUpdateAction(object sender, CacheUpdateNotificationArgs e) - { - var remoteItem = await _cacheSubscriber.GetAsync(e.Key, _knownTypes.GetOrAdd(e.Type, Type.GetType(e.Type))); - - if (remoteItem != null) - { - if (e.SpecificTimeToLive != null) - Add(e.Key, remoteItem, e.SpecificTimeToLive._timeToLive); - else - Add(e.Key, remoteItem); - } + } + + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return _cache.GetAsync(key, type, dataRetriever); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + return _cache.GetAsync(key, type, dataRetriever, timeToLive); + } + + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return _cache.GetAsync(key, dataRetriever); + } + + public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + return _cache.GetAsync(key, dataRetriever, timeToLive); + } + + private async void OnCacheUpdate(object sender, CacheUpdateNotificationArgs e) + { + await CacheUpdateAction(sender, e).ConfigureAwait(false); + } + private async Task CacheUpdateAction(object sender, CacheUpdateNotificationArgs e) + { + var remoteItem = await _cacheSubscriber.GetAsync(e.Key, _knownTypes.GetOrAdd(e.Type, Type.GetType(e.Type))).ConfigureAwait(false); ; + + if (remoteItem != null) + { + if (e.SpecificTimeToLive != null) + Add(e.Key, remoteItem, e.SpecificTimeToLive._timeToLive); + else + Add(e.Key, remoteItem); + } } private void OnCacheDelete(object sender, CacheUpdateNotificationArgs e) @@ -92,11 +92,16 @@ private void OnCacheDelete(object sender, CacheUpdateNotificationArgs e) Remove(e.Key); } - public void Remove(string key) - { - _cache.Remove(key); - } - - public TimeSpan? DefaultTtl { get { return _cache.DefaultTtl; } } - } -} + public void Remove(string key) + { + _cache.Remove(key); + } + + public bool Exists(string key) + { + return _cache.Exists(key); + } + + public TimeSpan? DefaultTtl { get { return _cache.DefaultTtl; } } + } +} diff --git a/source/DoubleCache/packages.config b/source/DoubleCache/packages.config index 87e9ca3..3ccd8b3 100644 --- a/source/DoubleCache/packages.config +++ b/source/DoubleCache/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/source/DoubleCacheTests/DoubleCacheTests.cs b/source/DoubleCacheTests/DoubleCacheTests.cs index 2740b76..508c5e1 100644 --- a/source/DoubleCacheTests/DoubleCacheTests.cs +++ b/source/DoubleCacheTests/DoubleCacheTests.cs @@ -3,6 +3,7 @@ using Xunit; using FakeItEasy; using DoubleCache; +using DoubleCache.Redis; namespace DoubleCacheTests { @@ -10,6 +11,7 @@ public class DoubleCacheTests { private readonly ICacheAside _local; private readonly ICacheAside _remote; + private readonly IKeyTimeToLive _remoteTTL; private readonly DoubleCache.DoubleCache _doubleCache; @@ -17,11 +19,13 @@ public DoubleCacheTests() { _local = A.Fake(); _remote = A.Fake(); + _remoteTTL = A.Fake(); _doubleCache = new DoubleCache.DoubleCache( _local, - _remote); + _remote, + _remoteTTL); } [Fact] @@ -47,16 +51,18 @@ public void Get_CalledOnLocal() { _doubleCache.Get("A", typeof(string), null); - A.CallTo(() => _local.Get("A", A.Ignored, A>.Ignored)).MustHaveHappened(Repeated.Exactly.Once); + A.CallTo(() => _local.Get("A", A.Ignored, A>.Ignored, A._)).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.Get("A", A.Ignored, A>.Ignored)).MustNotHaveHappened(); } [Fact] public void Get_WithTimeToLive_CalledOnLocal() { - _doubleCache.Get("A", typeof(string), null, TimeSpan.FromSeconds(1)); + A.CallTo(() => _remoteTTL.KeyTimeToLive("A")).Returns(TimeSpan.FromMilliseconds(800)); - A.CallTo(() => _local.Get("A", A.Ignored, A>.Ignored, TimeSpan.FromSeconds(1))).MustHaveHappened(Repeated.Exactly.Once); + _doubleCache.Get("A", typeof(string), null, TimeSpan.FromSeconds(1)); + + A.CallTo(() => _local.Get("A", A.Ignored, A>.Ignored, TimeSpan.FromMilliseconds(800))).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.Get("A", A.Ignored, A>.Ignored, TimeSpan.FromSeconds(1))).MustNotHaveHappened(); } @@ -65,13 +71,15 @@ public void GetGeneric_CalledOnLocal() { _doubleCache.Get("A", null); - A.CallTo(() => _local.Get("A", A>.Ignored)).MustHaveHappened(Repeated.Exactly.Once); + A.CallTo(() => _local.Get("A", A>.Ignored,A._)).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.Get("A", A>.Ignored)).MustNotHaveHappened(); } [Fact] public void GetGeneric_WithTimeToLive_CalledOnLocal() { + A.CallTo(() => _remoteTTL.KeyTimeToLive("A")).Returns(TimeSpan.FromSeconds(1)); + _doubleCache.Get("A", null, TimeSpan.FromSeconds(1)); A.CallTo(() => _local.Get("A", A>.Ignored, TimeSpan.FromSeconds(1))).MustHaveHappened(Repeated.Exactly.Once); @@ -81,18 +89,23 @@ public void GetGeneric_WithTimeToLive_CalledOnLocal() [Fact] public async Task GetAsync_CalledOnLocal() { - await _doubleCache.GetAsync("A", typeof(string), null); + A.CallTo(() => _remoteTTL.KeyTimeToLive("A")).Returns(TimeSpan.FromSeconds(1)); + - A.CallTo(() => _local.GetAsync("A", A.Ignored, A>>.Ignored)).MustHaveHappened(Repeated.Exactly.Once); + await _doubleCache.GetAsync("A", typeof(string), null).ConfigureAwait(false); + + A.CallTo(() => _local.GetAsync("A", A.Ignored, A>>.Ignored, A._)).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.GetAsync("A", A.Ignored, A>>.Ignored)).MustNotHaveHappened(); } [Fact] public async Task GetAsync_WithTimeToLive_CalledOnLocal() { + A.CallTo(() => _remoteTTL.KeyTimeToLive("A")).Returns(TimeSpan.FromMilliseconds(800)); + await _doubleCache.GetAsync("A", typeof(string), null, TimeSpan.FromSeconds(1)); - A.CallTo(() => _local.GetAsync("A", A.Ignored, A>>.Ignored, TimeSpan.FromSeconds(1))).MustHaveHappened(Repeated.Exactly.Once); + A.CallTo(() => _local.GetAsync("A", A.Ignored, A>>.Ignored, TimeSpan.FromMilliseconds(800))).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.GetAsync("A", A.Ignored, A>>.Ignored, TimeSpan.FromSeconds(1))).MustNotHaveHappened(); } @@ -101,13 +114,15 @@ public async Task GetAsyncGeneric_CalledOnLocal() { await _doubleCache.GetAsync("A", null); - A.CallTo(() => _local.GetAsync("A", A>>.Ignored)).MustHaveHappened(Repeated.Exactly.Once); + A.CallTo(() => _local.GetAsync("A", A>>.Ignored, A._)).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.GetAsync("A", A>>.Ignored)).MustNotHaveHappened(); } [Fact] public async Task GetAsyncGeneric_WithTimeToLive_CalledOnLocal() { + A.CallTo(() => _remoteTTL.KeyTimeToLive("A")).Returns(TimeSpan.FromSeconds(1)); + await _doubleCache.GetAsync("A", null, TimeSpan.FromSeconds(1)); A.CallTo(() => _local.GetAsync("A", A>>.Ignored, TimeSpan.FromSeconds(1))).MustHaveHappened(Repeated.Exactly.Once); diff --git a/source/DoubleCacheTests/DoubleCacheTests.csproj b/source/DoubleCacheTests/DoubleCacheTests.csproj index 07f9f37..8570c35 100644 --- a/source/DoubleCacheTests/DoubleCacheTests.csproj +++ b/source/DoubleCacheTests/DoubleCacheTests.csproj @@ -43,12 +43,12 @@ ..\packages\Shouldly.2.8.2\lib\net40\Shouldly.dll - - ..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll - True + + ..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll + @@ -71,12 +71,15 @@ + + + diff --git a/source/DoubleCacheTests/ExistingSubscribingCacheTests.cs b/source/DoubleCacheTests/ExistingSubscribingCacheTests.cs new file mode 100644 index 0000000..ff6088e --- /dev/null +++ b/source/DoubleCacheTests/ExistingSubscribingCacheTests.cs @@ -0,0 +1,180 @@ +using DoubleCache; +using FakeItEasy; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace DoubleCacheTests +{ + public class ExistingSubscribingCacheTests + { + private readonly ICacheAside _decoratedCache; + private readonly ICacheSubscriber _subscriber; + private readonly ICacheAside _subscribingCache; + + public ExistingSubscribingCacheTests() + { + _decoratedCache = A.Fake(); + _subscriber = A.Fake(); + _subscribingCache = new ExistingItemsSubscribingCache(_decoratedCache, _subscriber); + } + + + [Fact] + public void Add_CallsThrough() + { + _subscribingCache.Add("a", "b"); + + A.CallTo(() => _decoratedCache.Add("a", "b")).MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Add_WithTtl_CallsThrough() + { + _subscribingCache.Add("a", "b", TimeSpan.FromMinutes(1)); + + A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))).MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Get_CallsThrough() + { + _subscribingCache.Get("a", typeof(string), null); + A.CallTo(() => _decoratedCache.Get("a", typeof(string), A>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Get_WithTimeToLive_CallsThrough() + { + _subscribingCache.Get("a", typeof(string), null, TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Get("a", typeof(string), A>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void GetGeneric_CallsThrough() + { + _subscribingCache.Get("a", A.Fake>()); + A.CallTo(() => _decoratedCache.Get("a", A>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void GetGeneric_WithTimeToLive_CallsThrough() + { + _subscribingCache.Get("a", A.Fake>(), TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Get("a", A>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsync_CallsThrough() + { + await _subscribingCache.GetAsync("a", typeof(string), null); + A.CallTo(() => _decoratedCache.GetAsync("a", typeof(string), A>>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsync_WithTimeToLive_CallsThrough() + { + await _subscribingCache.GetAsync("a", typeof(string), null, TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.GetAsync("a", typeof(string), A>>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsyncGeneric_CallsThrough() + { + await _subscribingCache.GetAsync("a", A.Fake>>()); + A.CallTo(() => _decoratedCache.GetAsync("a", A>>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsyncGeneric_WithTimeToLive_CallsThrough() + { + await _subscribingCache.GetAsync("a", A.Fake>>(), TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.GetAsync("a", A>>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Exists_CallsThrough() + { + _subscribingCache.Exists("a"); + A.CallTo(() => _decoratedCache.Exists("a")) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdate_ExistingItem_ItemAdded() + { + A.CallTo(() => _decoratedCache.Exists("a")).Returns(true); + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b")) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdate_UknownItem_NotAdded() + { + A.CallTo(() => _decoratedCache.Exists("a")).Returns(false); + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b")) + .MustNotHaveHappened(); + } + + [Fact] + public async Task SubscriberUpdate_WithSpecificTimeToLive_null_ItemAddedWithTimeToLive() + { + A.CallTo(() => _decoratedCache.Exists("a")).Returns(true); + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(null) }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b", null)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdate_WithSpecificTimeToLive_value_ItemAddedWithTimeToLive() + { + A.CallTo(() => _decoratedCache.Exists("a")).Returns(true); + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(TimeSpan.FromMinutes(1)) }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdater_NullReturned_DoesNotAddToCache() + { + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns(null); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(TimeSpan.FromMinutes(1)) }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))) + .MustNotHaveHappened(); + } + + [Fact] + public async Task SubscriberDelete_ItemDelete() + { + _subscriber.CacheDelete += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a" }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Remove("a")) + .MustHaveHappened(Repeated.Exactly.Once); + } + } +} diff --git a/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs b/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs index de14215..122a350 100644 --- a/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs @@ -12,7 +12,6 @@ public abstract class CacheImplementationTests protected string _key; protected ICacheAside _cacheImplementation; - [Fact] public void Get_ExistingValue_ReturnsValue() { @@ -252,5 +251,46 @@ public async Task Remove_ExistingKey_DeletesValue() A.CallTo(() => func.Invoke()).MustHaveHappened(Repeated.Exactly.Once); result.ShouldBe("B"); } + + [Fact] + public virtual void Cache_Null_Returns_Null() + { + var key = Guid.NewGuid().ToString(); + + _cacheImplementation.Add(key, null); + + var result = _cacheImplementation.Get(key, () => "a"); + + result.ShouldBeNull(); + } + + [Fact] + public virtual void CacheWithTTL_Null_Returns_Null() + { + var key = Guid.NewGuid().ToString(); + + _cacheImplementation.Add(key, null, TimeSpan.FromMinutes(1)); + + var result = _cacheImplementation.Get(key, () => "a"); + + result.ShouldBeNull(); + } + + [Fact] + public void Exists_ItemDoesExsists_ReturnsTrue() + { + var key = Guid.NewGuid().ToString(); + _cacheImplementation.Add(key, "A"); + + _cacheImplementation.Exists(key).ShouldBeTrue(); + } + + [Fact] + public void Exists_ItemDoesNotExsists_ReturnsFalse() + { + var key = Guid.NewGuid().ToString(); + + _cacheImplementation.Exists(key).ShouldBeFalse(); + } } } diff --git a/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs b/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs index ae91650..f9b5eeb 100644 --- a/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs @@ -1,7 +1,8 @@ using System; using System.IO; using System.Web; -using DoubleCache.SystemWebHttpCache; +using DoubleCache.SystemWebCaching; +using Shouldly; using Xunit; namespace DoubleCacheTests.IntegrationTests diff --git a/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs b/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs index 2d6bb58..f8dd6fa 100644 --- a/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs @@ -1,5 +1,7 @@ using System; using DoubleCache.LocalCache; +using FakeItEasy; +using Shouldly; using Xunit; namespace DoubleCacheTests.IntegrationTests @@ -12,5 +14,15 @@ public MemoryCacheIntegrationTests() _key = Guid.NewGuid().ToString(); _cacheImplementation = new MemCache(TimeSpan.FromMinutes(1)); } + + public override void Cache_Null_Returns_Null() + { + Should.Throw(() => base.Cache_Null_Returns_Null()); + } + + public override void CacheWithTTL_Null_Returns_Null() + { + Should.Throw(() => base.CacheWithTTL_Null_Returns_Null()); + } } } diff --git a/source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs b/source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs new file mode 100644 index 0000000..454dffa --- /dev/null +++ b/source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs @@ -0,0 +1,19 @@ +using System; +using DoubleCache; +using DoubleCache.Serialization; +using Xunit; + +namespace DoubleCacheTests.IntegrationTests +{ + + [Trait("Category", "Integration")] + public class PubSubDoubleCacheTest : CacheImplementationTests, IClassFixture + { + public PubSubDoubleCacheTest(RedisFixture fixture) + { + _key = Guid.NewGuid().ToString(); + _cacheImplementation = CacheFactory.CreatePubSubDoubleCache(fixture.ConnectionMultiplexer, + new BinaryFormatterItemSerializer(),TimeSpan.FromMinutes(1)); + } + } +} diff --git a/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs b/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs new file mode 100644 index 0000000..8c34626 --- /dev/null +++ b/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs @@ -0,0 +1,17 @@ +using System; +using DoubleCache.LocalCache; +using Shouldly; +using Xunit; + +namespace DoubleCacheTests.IntegrationTests +{ + [Trait("Category", "Integration")] + public class WrappingMemoryCacheTests : CacheImplementationTests + { + public WrappingMemoryCacheTests() + { + _key = Guid.NewGuid().ToString(); + _cacheImplementation = new WrappingMemoryCache(TimeSpan.FromMinutes(1)); + } + } +} diff --git a/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs b/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs index 35fac8c..c42c5a6 100644 --- a/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs +++ b/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs @@ -7,40 +7,45 @@ namespace DoubleCacheTests.Serialization { - public abstract class ItemSerializerTests - { + public abstract class ItemSerializerTests + { protected IItemSerializer serializer; - [Theory] + [Theory] [InlineData("a")] - public void RoundtripSerializeGeneric(T input) - { - var result = serializer.Deserialize(serializer.Serialize(input)); - - result.ShouldBe(input); - } - [Theory] - [InlineData("a")] - [CacheNotificationData("test","test",1)] - public void RoundtripSerialize(T input) - { - var result = serializer.Deserialize(serializer.Serialize(input), typeof(T)); - - result.ShouldBeOfType(); - - if (result is string) - result.ShouldBe(input); - } - - [Theory] - [InlineData("a")] - public void RoundtripDeserializeStream(T input) - { - using (var ms = new MemoryStream(serializer.Serialize(input))) - { - var result = serializer.Deserialize(ms); - result.ShouldBe(input); - } - } + public void RoundtripSerializeGeneric(T input) + { + var result = serializer.Deserialize(serializer.Serialize(input)); + + result.ShouldBe(input); + } + [Theory] + [InlineData("a")] + [InlineData(null)] + [CacheNotificationData("test","test",1)] + public void RoundtripSerialize(T input) + { + var result = serializer.Deserialize(serializer.Serialize(input), typeof(T)); + + if (input == null) + result.ShouldBeNull(); + else + { + result.ShouldBeOfType(); + if (result is string) + result.ShouldBe(input); + } + } + + [Theory] + [InlineData("a")] + public void RoundtripDeserializeStream(T input) + { + using (var ms = new MemoryStream(serializer.Serialize(input))) + { + var result = serializer.Deserialize(ms); + result.ShouldBe(input); + } + } } } diff --git a/source/DoubleCacheTests/SubscribingCacheTests.cs b/source/DoubleCacheTests/SubscribingCacheTests.cs index 9034957..eafdd7d 100644 --- a/source/DoubleCacheTests/SubscribingCacheTests.cs +++ b/source/DoubleCacheTests/SubscribingCacheTests.cs @@ -1,135 +1,135 @@ -using DoubleCache; -using FakeItEasy; -using System; -using System.Threading.Tasks; -using Xunit; - -namespace DoubleCacheTests -{ - public class SubscribingCacheTests - { - private readonly ICacheAside _decoratedCache; - private readonly ICacheSubscriber _subscriber; - private readonly ICacheAside _subscribingCache; - - public SubscribingCacheTests() - { - _decoratedCache = A.Fake(); - _subscriber = A.Fake(); - _subscribingCache = new SubscribingCache(_decoratedCache, _subscriber); - } - - [Fact] - public void Add_CallsThrough() - { - _subscribingCache.Add("a", "b"); - - A.CallTo(() => _decoratedCache.Add("a", "b")).MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void Add_WithTtl_CallsThrough() - { - _subscribingCache.Add("a", "b", TimeSpan.FromMinutes(1)); - - A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))).MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void Get_CallsThrough() - { - _subscribingCache.Get("a", typeof(string), null); - A.CallTo(() => _decoratedCache.Get("a", typeof(string), A>._)) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void Get_WithTimeToLive_CallsThrough() - { - _subscribingCache.Get("a", typeof(string), null, TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.Get("a", typeof(string), A>._, TimeSpan.FromSeconds(1))) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void GetGeneric_CallsThrough() - { - _subscribingCache.Get("a", A.Fake>()); - A.CallTo(() => _decoratedCache.Get("a", A>._)) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void GetGeneric_WithTimeToLive_CallsThrough() - { - _subscribingCache.Get("a", A.Fake>(), TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.Get("a", A>._, TimeSpan.FromSeconds(1))) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task GetAsync_CallsThrough() - { - await _subscribingCache.GetAsync("a", typeof(string), null); - A.CallTo(() => _decoratedCache.GetAsync("a", typeof(string), A>>._)) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task GetAsync_WithTimeToLive_CallsThrough() - { - await _subscribingCache.GetAsync("a", typeof(string), null, TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.GetAsync("a", typeof(string), A>>._, TimeSpan.FromSeconds(1))) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task GetAsyncGeneric_CallsThrough() - { - await _subscribingCache.GetAsync("a", A.Fake>>()); - A.CallTo(() => _decoratedCache.GetAsync("a", A>>._)) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task GetAsyncGeneric_WithTimeToLive_CallsThrough() - { - await _subscribingCache.GetAsync("a", A.Fake>>(), TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.GetAsync("a", A>>._, TimeSpan.FromSeconds(1))) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task SubscriberUpdate_ItemAdded() - { - A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); - _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName }); - - await Task.Delay(TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.Add("a", "b")) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task SubscriberUpdate_WithSpecificTimeToLive_null_ItemAddedWithTimeToLive() - { - A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); - _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(null) }); - - await Task.Delay(TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.Add("a", "b", null)) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public async Task SubscriberUpdate_WithSpecificTimeToLive_value_ItemAddedWithTimeToLive() - { - A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); - _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(TimeSpan.FromMinutes(1)) }); - - await Task.Delay(TimeSpan.FromSeconds(1)); - A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))) - .MustHaveHappened(Repeated.Exactly.Once); +using DoubleCache; +using FakeItEasy; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace DoubleCacheTests +{ + public class SubscribingCacheTests + { + private readonly ICacheAside _decoratedCache; + private readonly ICacheSubscriber _subscriber; + private readonly ICacheAside _subscribingCache; + + public SubscribingCacheTests() + { + _decoratedCache = A.Fake(); + _subscriber = A.Fake(); + _subscribingCache = new SubscribingCache(_decoratedCache, _subscriber); + } + + [Fact] + public void Add_CallsThrough() + { + _subscribingCache.Add("a", "b"); + + A.CallTo(() => _decoratedCache.Add("a", "b")).MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Add_WithTtl_CallsThrough() + { + _subscribingCache.Add("a", "b", TimeSpan.FromMinutes(1)); + + A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))).MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Get_CallsThrough() + { + _subscribingCache.Get("a", typeof(string), null); + A.CallTo(() => _decoratedCache.Get("a", typeof(string), A>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void Get_WithTimeToLive_CallsThrough() + { + _subscribingCache.Get("a", typeof(string), null, TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Get("a", typeof(string), A>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void GetGeneric_CallsThrough() + { + _subscribingCache.Get("a", A.Fake>()); + A.CallTo(() => _decoratedCache.Get("a", A>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public void GetGeneric_WithTimeToLive_CallsThrough() + { + _subscribingCache.Get("a", A.Fake>(), TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Get("a", A>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsync_CallsThrough() + { + await _subscribingCache.GetAsync("a", typeof(string), null); + A.CallTo(() => _decoratedCache.GetAsync("a", typeof(string), A>>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsync_WithTimeToLive_CallsThrough() + { + await _subscribingCache.GetAsync("a", typeof(string), null, TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.GetAsync("a", typeof(string), A>>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsyncGeneric_CallsThrough() + { + await _subscribingCache.GetAsync("a", A.Fake>>()); + A.CallTo(() => _decoratedCache.GetAsync("a", A>>._)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task GetAsyncGeneric_WithTimeToLive_CallsThrough() + { + await _subscribingCache.GetAsync("a", A.Fake>>(), TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.GetAsync("a", A>>._, TimeSpan.FromSeconds(1))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdate_ItemAdded() + { + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b")) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdate_WithSpecificTimeToLive_null_ItemAddedWithTimeToLive() + { + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(null) }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b", null)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Fact] + public async Task SubscriberUpdate_WithSpecificTimeToLive_value_ItemAddedWithTimeToLive() + { + A.CallTo(() => _subscriber.GetAsync("a", A.Ignored)).Returns("b"); + _subscriber.CacheUpdate += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a", Type = typeof(string).AssemblyQualifiedName, SpecificTimeToLive = new TimeToLive(TimeSpan.FromMinutes(1)) }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + A.CallTo(() => _decoratedCache.Add("a", "b", TimeSpan.FromMinutes(1))) + .MustHaveHappened(Repeated.Exactly.Once); } [Fact] @@ -143,7 +143,7 @@ public async Task SubscriberUpdater_NullReturned_DoesNotAddToCache() .MustNotHaveHappened(); } - [Fact] + [Fact] public async Task SubscriberDelete_ItemDelete() { _subscriber.CacheDelete += Raise.With(this, new CacheUpdateNotificationArgs { Key = "a" }); @@ -151,6 +151,6 @@ public async Task SubscriberDelete_ItemDelete() await Task.Delay(TimeSpan.FromSeconds(1)); A.CallTo(() => _decoratedCache.Remove("a")) .MustHaveHappened(Repeated.Exactly.Once); - } - } -} + } + } +} diff --git a/source/DoubleCacheTests/packages.config b/source/DoubleCacheTests/packages.config index a84df33..a2c07fd 100644 --- a/source/DoubleCacheTests/packages.config +++ b/source/DoubleCacheTests/packages.config @@ -4,7 +4,7 @@ - + diff --git a/source/Sample/RandomUser/RandomUser.csproj b/source/Sample/RandomUser/RandomUser.csproj index 996782f..4536bc0 100644 --- a/source/Sample/RandomUser/RandomUser.csproj +++ b/source/Sample/RandomUser/RandomUser.csproj @@ -30,16 +30,15 @@ 4 - - ..\..\packages\Newtonsoft.Json.8.0.1\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll - True + + ..\..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll + diff --git a/source/Sample/RandomUser/packages.config b/source/Sample/RandomUser/packages.config index a693d9b..b33593b 100644 --- a/source/Sample/RandomUser/packages.config +++ b/source/Sample/RandomUser/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/source/Sample/SampleApiCacheUpdateConsole/SampleApiCacheUpdateConsole.csproj b/source/Sample/SampleApiCacheUpdateConsole/SampleApiCacheUpdateConsole.csproj index ab85a26..4d42c53 100644 --- a/source/Sample/SampleApiCacheUpdateConsole/SampleApiCacheUpdateConsole.csproj +++ b/source/Sample/SampleApiCacheUpdateConsole/SampleApiCacheUpdateConsole.csproj @@ -33,16 +33,15 @@ 4 - - ..\..\packages\Newtonsoft.Json.8.0.1\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll - True + + ..\..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll + diff --git a/source/Sample/SampleApiCacheUpdateConsole/packages.config b/source/Sample/SampleApiCacheUpdateConsole/packages.config index a693d9b..b33593b 100644 --- a/source/Sample/SampleApiCacheUpdateConsole/packages.config +++ b/source/Sample/SampleApiCacheUpdateConsole/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/source/Sample/SampleApiHostConsole/App.config b/source/Sample/SampleApiHostConsole/App.config index f9062de..447915d 100644 --- a/source/Sample/SampleApiHostConsole/App.config +++ b/source/Sample/SampleApiHostConsole/App.config @@ -7,7 +7,7 @@ - + diff --git a/source/Sample/SampleApiHostConsole/SampleApiHostConsole.csproj b/source/Sample/SampleApiHostConsole/SampleApiHostConsole.csproj index 4ca8725..6e65c7a 100644 --- a/source/Sample/SampleApiHostConsole/SampleApiHostConsole.csproj +++ b/source/Sample/SampleApiHostConsole/SampleApiHostConsole.csproj @@ -45,20 +45,19 @@ ..\..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll True - - ..\..\packages\Newtonsoft.Json.8.0.1\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Owin.1.0\lib\net40\Owin.dll True - - ..\..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll - True + + ..\..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True diff --git a/source/Sample/SampleApiHostConsole/packages.config b/source/Sample/SampleApiHostConsole/packages.config index e24fe00..f4b0b0c 100644 --- a/source/Sample/SampleApiHostConsole/packages.config +++ b/source/Sample/SampleApiHostConsole/packages.config @@ -9,7 +9,7 @@ - + - + \ No newline at end of file diff --git a/source/Sample/SampleApiOwin/DoubleCacheController.cs b/source/Sample/SampleApiOwin/DoubleCacheController.cs index 7dcbac1..ca3718d 100644 --- a/source/Sample/SampleApiOwin/DoubleCacheController.cs +++ b/source/Sample/SampleApiOwin/DoubleCacheController.cs @@ -1,54 +1,56 @@ -using DoubleCache; -using DoubleCache.Redis; -using DoubleCache.Serialization; -using RandomUser; -using StackExchange.Redis; -using System.Threading.Tasks; -using System.Web.Http; -using System.Net.Http; -using System.Net; - -namespace CacheSample -{ - [RoutePrefix("doublecache")] - public class DoubleCacheController : ApiController, IUserController - { - private static ICacheAside _doubleCache; - private RandomUserRepository _repo; - - static DoubleCacheController() - { - _doubleCache = new DoubleCache.DoubleCache( - new DoubleCache.LocalCache.MemCache(), - new RedisCache(ConnectionMultiplexer.Connect("localhost").GetDatabase(), new MsgPackItemSerializer())); - } - - public DoubleCacheController() - { - _repo = new RandomUserRepository(); - } - - [Route("single")] - public async Task GetSingle() - { - return Ok(await _doubleCache.GetAsync(Request.RequestUri.PathAndQuery, () => _repo.GetSingleDummyUser())); - } - - [Route("many")] - public async Task GetMany() - { - return Ok(await _doubleCache.GetAsync(Request.RequestUri.PathAndQuery, () => _repo.GetManyDummyUser(100))); - } - - [HttpDelete] - [Route("single")] - [Route("many")] +using DoubleCache; +using DoubleCache.Redis; +using DoubleCache.Serialization; +using RandomUser; +using StackExchange.Redis; +using System.Threading.Tasks; +using System.Web.Http; +using System.Net.Http; +using System.Net; + +namespace CacheSample +{ + [RoutePrefix("doublecache")] + public class DoubleCacheController : ApiController, IUserController + { + private static ICacheAside _doubleCache; + private RandomUserRepository _repo; + + static DoubleCacheController() + { + var remoteCache = new RedisCache(ConnectionMultiplexer.Connect("localhost").GetDatabase(), new MsgPackItemSerializer()); + _doubleCache = new DoubleCache.DoubleCache( + new DoubleCache.LocalCache.WrappingMemoryCache(), + remoteCache, + remoteCache); + } + + public DoubleCacheController() + { + _repo = new RandomUserRepository(); + } + + [Route("single")] + public async Task GetSingle() + { + return Ok(await _doubleCache.GetAsync(Request.RequestUri.PathAndQuery, () => _repo.GetSingleDummyUser())); + } + + [Route("many")] + public async Task GetMany() + { + return Ok(await _doubleCache.GetAsync(Request.RequestUri.PathAndQuery, () => _repo.GetManyDummyUser(100))); + } + + [HttpDelete] + [Route("single")] + [Route("many")] public IHttpActionResult Remove() { _doubleCache.Remove(Request.RequestUri.PathAndQuery); return ResponseMessage(new HttpResponseMessage(HttpStatusCode.NoContent)); - } - } -} - + } + } +} + diff --git a/source/Sample/SampleApiOwin/SampleApiOwin.csproj b/source/Sample/SampleApiOwin/SampleApiOwin.csproj index e95186b..317c43d 100644 --- a/source/Sample/SampleApiOwin/SampleApiOwin.csproj +++ b/source/Sample/SampleApiOwin/SampleApiOwin.csproj @@ -46,13 +46,11 @@ ..\..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll True - - ..\..\packages\MsgPack.Cli.0.6.5\lib\net45\MsgPack.dll - True + + ..\..\packages\MsgPack.Cli.0.8.1\lib\net45\MsgPack.dll - - ..\..\packages\Newtonsoft.Json.8.0.1\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Owin.1.0\lib\net40\Owin.dll @@ -62,12 +60,12 @@ ..\..\packages\Sigil.4.5.0\lib\net45\Sigil.dll True - - ..\..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll - True + + ..\..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True diff --git a/source/Sample/SampleApiOwin/app.config b/source/Sample/SampleApiOwin/app.config index 0f9899d..40b3291 100644 --- a/source/Sample/SampleApiOwin/app.config +++ b/source/Sample/SampleApiOwin/app.config @@ -8,7 +8,7 @@ - + diff --git a/source/Sample/SampleApiOwin/packages.config b/source/Sample/SampleApiOwin/packages.config index 14e2983..74e4887 100644 --- a/source/Sample/SampleApiOwin/packages.config +++ b/source/Sample/SampleApiOwin/packages.config @@ -8,9 +8,9 @@ - - + + - + \ No newline at end of file