Skip to content

Commit

Permalink
Merge pull request #8 from catcherwong/dev
Browse files Browse the repository at this point in the history
Memcached Caching Provider Update.
  • Loading branch information
catcherwong authored Feb 3, 2018
2 parents 864c16a + 26f5ded commit d44a610
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 16 deletions.
90 changes: 74 additions & 16 deletions src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using EasyCaching.Core.Internal;
using Enyim.Caching;
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

/// <summary>
Expand Down Expand Up @@ -44,7 +46,7 @@ public CacheValue<T> Get<T>(string cacheKey, Func<T> dataRetriever, TimeSpan exp
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));
ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration));

var result = _memcachedClient.Get(cacheKey) as T;
var result = _memcachedClient.Get(this.HandleCacheKey(cacheKey)) as T;
if (result != null)
{
return new CacheValue<T>(result, true);
Expand All @@ -53,7 +55,7 @@ public CacheValue<T> Get<T>(string cacheKey, Func<T> dataRetriever, TimeSpan exp
var item = dataRetriever?.Invoke();
if (item != null)
{
Set(cacheKey, item, expiration);
this.Set(cacheKey, item, expiration);
return new CacheValue<T>(item, true);
}
else
Expand All @@ -75,7 +77,7 @@ public async Task<CacheValue<T>> GetAsync<T>(string cacheKey, Func<Task<T>> data
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));
ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration));

var result = await _memcachedClient.GetValueAsync<T>(cacheKey);
var result = await _memcachedClient.GetValueAsync<T>(this.HandleCacheKey(cacheKey));
if (result != null)
{
return new CacheValue<T>(result, true);
Expand All @@ -84,7 +86,7 @@ public async Task<CacheValue<T>> GetAsync<T>(string cacheKey, Func<Task<T>> data
var item = await dataRetriever?.Invoke();
if (item != null)
{
await SetAsync(cacheKey, item, expiration);
await this.SetAsync(cacheKey, item, expiration);
return new CacheValue<T>(item, true);
}
else
Expand All @@ -103,7 +105,7 @@ public CacheValue<T> Get<T>(string cacheKey) where T : class
{
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));

var result = _memcachedClient.Get(cacheKey) as T;
var result = _memcachedClient.Get(this.HandleCacheKey(cacheKey)) as T;
if (result != null)
{
return new CacheValue<T>(result, true);
Expand All @@ -122,9 +124,9 @@ public CacheValue<T> Get<T>(string cacheKey) where T : class
/// <typeparam name="T">The 1st type parameter.</typeparam>
public async Task<CacheValue<T>> GetAsync<T>(string cacheKey) where T : class
{
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));

var result = await _memcachedClient.GetValueAsync<T>(cacheKey);
var result = await _memcachedClient.GetValueAsync<T>(this.HandleCacheKey(cacheKey));
if (result != null)
{
return new CacheValue<T>(result, true);
Expand All @@ -144,7 +146,7 @@ public void Remove(string cacheKey)
{
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));

_memcachedClient.Remove(cacheKey);
_memcachedClient.Remove(this.HandleCacheKey(cacheKey));
}

/// <summary>
Expand All @@ -156,7 +158,7 @@ public async Task RemoveAsync(string cacheKey)
{
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));

await _memcachedClient.RemoveAsync(cacheKey);
await _memcachedClient.RemoveAsync(this.HandleCacheKey(cacheKey));
}

/// <summary>
Expand All @@ -173,7 +175,7 @@ public void Set<T>(string cacheKey, T cacheValue, TimeSpan expiration) where T :
ArgumentCheck.NotNull(cacheValue, nameof(cacheValue));
ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration));

_memcachedClient.Add(cacheKey, cacheValue, expiration.Seconds);
_memcachedClient.Store(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(cacheKey), cacheValue, expiration);
}

/// <summary>
Expand All @@ -190,7 +192,7 @@ public async Task SetAsync<T>(string cacheKey, T cacheValue, TimeSpan expiration
ArgumentCheck.NotNull(cacheValue, nameof(cacheValue));
ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration));

await _memcachedClient.AddAsync(cacheKey, cacheValue, expiration.Seconds);
await _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(cacheKey), cacheValue, expiration);
}

/// <summary>
Expand All @@ -202,7 +204,7 @@ public bool Exists(string cacheKey)
{
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));

return _memcachedClient.TryGet(cacheKey, out object obj);
return _memcachedClient.TryGet(this.HandleCacheKey(cacheKey), out object obj);
}

/// <summary>
Expand All @@ -214,7 +216,7 @@ public Task<bool> ExistsAsync(string cacheKey)
{
ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey));

return Task.Run(() => { return _memcachedClient.TryGet(cacheKey, out object obj); });
return Task.Run(() => { return _memcachedClient.TryGet(this.HandleCacheKey(cacheKey), out object obj); });
}

/// <summary>
Expand Down Expand Up @@ -252,14 +254,70 @@ public async Task RefreshAsync<T>(string cacheKey, T cacheValue, TimeSpan expira
await this.SetAsync(cacheKey, cacheValue, expiration);
}

/// <summary>
/// Removes cached item by cachekey's prefix.
/// </summary>
/// <remarks>
/// Before using the method , you should follow this link
/// https://github.com/memcached/memcached/wiki/ProgrammingTricks#namespacing
/// and confirm that you use the namespacing when you set and get the cache.
/// </remarks>
/// <param name="prefix">Prefix of CacheKey.</param>
public void RemoveByPrefix(string prefix)
{
throw new NotImplementedException();
var oldPrefixKey = _memcachedClient.Get(prefix)?.ToString();

var newValue = DateTime.UtcNow.Ticks.ToString();

if (oldPrefixKey.Equals(newValue))
{
newValue = string.Concat(newValue, new Random().Next(9).ToString());
}
_memcachedClient.Store(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(prefix), newValue, new TimeSpan(0, 0, 0));
}

public Task RemoveByPrefixAsync(string prefix)
/// <summary>
/// Removes cached item by cachekey's prefix async.
/// </summary>
/// <remarks>
/// Before using the method , you should follow this link
/// https://github.com/memcached/memcached/wiki/ProgrammingTricks#namespacing
/// and confirm that you use the namespacing when you set and get the cache.
/// </remarks>
/// <param name="prefix">Prefix of CacheKey.</param>
/// <returns></returns>
public async Task RemoveByPrefixAsync(string prefix)
{
throw new NotImplementedException();
var oldPrefixKey = _memcachedClient.Get(prefix)?.ToString();

var newValue = DateTime.UtcNow.Ticks.ToString();

if (oldPrefixKey.Equals(newValue))
{
newValue = string.Concat(newValue, new Random().Next(9).ToString());
}
await _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(prefix), newValue, new TimeSpan(0, 0, 0));
}

/// <summary>
/// Handle the cache key of memcached limititaion
/// </summary>
/// <param name="cacheKey">Cache Key</param>
/// <returns></returns>
private string HandleCacheKey(string cacheKey)
{
// Memcached has a 250 character limit
// Following memcached.h in https://github.com/memcached/memcached/
if (cacheKey.Length >= 250)
{
using (SHA1 sha1 = SHA1.Create())
{
byte[] data = sha1.ComputeHash(Encoding.UTF8.GetBytes(cacheKey));
return Convert.ToBase64String(data, Base64FormattingOptions.None);
}
}

return cacheKey;
}
}
}
72 changes: 72 additions & 0 deletions test/EasyCaching.UnitTests/CachingTests/MemcachedProviderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,77 @@ public async Task Refresh_Async_Should_Succeed()

Assert.Equal("NewValue", act.Value);
}

[Fact]
public void RemoveByPrefix_Should_Succeed()
{
string prefixKey = "demo";
string prefixValue = "abc";

_provider.Set(prefixKey, prefixValue, TimeSpan.FromSeconds(120));


SetCacheItem("1", "1", prefixKey);
SetCacheItem("2", "2", prefixKey);
SetCacheItem("3", "3", prefixKey);
SetCacheItem("4", "4", prefixKey);

_provider.RemoveByPrefix(prefixKey);

GetCacheItem("1", prefixKey);
GetCacheItem("2", prefixKey);
GetCacheItem("3", prefixKey);
GetCacheItem("4", prefixKey);

var afterPrefixValue = _provider.Get<string>(prefixKey);
Assert.NotEqual(prefixValue, afterPrefixValue.Value);
}

[Fact]
public async Task RemoveByPrefix_Async_Should_Succeed()
{
string prefixKey = "demo";
string prefixValue = "abc";

_provider.Set("demo", prefixValue, TimeSpan.FromSeconds(120));

SetCacheItem("1", "1", prefixKey);
SetCacheItem("2", "2", prefixKey);
SetCacheItem("3", "3", prefixKey);
SetCacheItem("4", "4", prefixKey);

await _provider.RemoveByPrefixAsync(prefixKey);

GetCacheItem("1", prefixKey);
GetCacheItem("2", prefixKey);
GetCacheItem("3", prefixKey);
GetCacheItem("4", prefixKey);

var afterPrefixValue = _provider.Get<string>(prefixKey);
Assert.NotEqual(prefixValue, afterPrefixValue.Value);
}

private void SetCacheItem(string cacheKey, string cacheValue, string prefix)
{
var pre = _provider.Get<string>(prefix);

cacheKey = string.Concat(pre, cacheKey);

_provider.Set(cacheKey, cacheValue, _defaultTs);

var val = _provider.Get<string>(cacheKey);
Assert.Equal(cacheValue, val.Value);
}


private void GetCacheItem(string cacheKey, string prefix)
{
var pre = _provider.Get<string>(prefix);

cacheKey = string.Concat(pre, cacheKey);

var val = _provider.Get<string>(cacheKey);
Assert.False(val.HasValue);
}
}
}

0 comments on commit d44a610

Please sign in to comment.