Browse Source

closes #1900

Implementation of IDistributedCache<TCacheItem, TCacheKey>
pull/1913/head
Galip Tolga Erdem 7 years ago
parent
commit
a9ffe66032
  1. 1
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs
  2. 323
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  3. 144
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
  4. 145
      framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs
  5. 7
      framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs

1
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs

@ -21,6 +21,7 @@ namespace Volo.Abp.Caching
context.Services.AddDistributedMemoryCache();
context.Services.AddSingleton(typeof(IDistributedCache<>), typeof(DistributedCache<>));
context.Services.AddSingleton(typeof(IDistributedCache<,>), typeof(DistributedCache<,>));
context.Services.Configure<CacheOptions>(cacheOptions =>
{

323
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -439,4 +439,327 @@ namespace Volo.Abp.Caching
DefaultCacheOptions = GetDefaultCacheEntryOptions();
}
}
/// <summary>
/// Represents a distributed cache of <typeparamref name="TCacheItem" /> type.
/// Uses a generic cache key type of <typeparamref name="TCacheKey" /> type.
/// </summary>
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
/// <typeparam name="TCacheKey">The type of cache key being used.</typeparam>
public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheItem, TCacheKey>
where TCacheItem : class
{
public ILogger<DistributedCache<TCacheItem, TCacheKey>> Logger { get; set; }
protected string CacheName { get; set; }
protected bool IgnoreMultiTenancy { get; set; }
protected IDistributedCache Cache { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected IDistributedCacheSerializer Serializer { get; }
protected ICurrentTenant CurrentTenant { get; }
protected SemaphoreSlim SyncSemaphore { get; }
protected DistributedCacheEntryOptions DefaultCacheOptions;
private readonly CacheOptions _cacheOption;
private readonly DistributedCacheOptions _distributedCacheOption;
public DistributedCache(
IOptions<CacheOptions> cacheOption,
IOptions<DistributedCacheOptions> distributedCacheOption,
IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer,
ICurrentTenant currentTenant)
{
_distributedCacheOption = distributedCacheOption.Value;
_cacheOption = cacheOption.Value;
Cache = cache;
CancellationTokenProvider = cancellationTokenProvider;
Logger = NullLogger<DistributedCache<TCacheItem, TCacheKey>>.Instance;
Serializer = serializer;
CurrentTenant = currentTenant;
SyncSemaphore = new SemaphoreSlim(1, 1);
SetDefaultOptions();
}
protected virtual string NormalizeKey(string key)
{
var normalizedKey = "c:" + CacheName + ",k:" + _cacheOption.KeyPrefix + key;
if (!IgnoreMultiTenancy && CurrentTenant.Id.HasValue)
{
normalizedKey = "t:" + CurrentTenant.Id.Value + "," + normalizedKey;
}
return normalizedKey;
}
protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions()
{
foreach (var configure in _cacheOption.CacheConfigurators)
{
var options = configure.Invoke(CacheName);
if (options != null)
{
return options;
}
}
return _cacheOption.GlobalCacheEntryOptions;
}
protected virtual void SetDefaultOptions()
{
CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem));
//IgnoreMultiTenancy
IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true);
//Configure default cache entry options
DefaultCacheOptions = GetDefaultCacheEntryOptions();
}
public virtual TCacheItem Get(TCacheKey key, bool? hideErrors = null)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[] cachedBytes;
try
{
cachedBytes = Cache.Get(NormalizeKey(key?.ToString()));
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return null;
}
throw;
}
if (cachedBytes == null)
{
return null;
}
return Serializer.Deserialize<TCacheItem>(cachedBytes);
}
public virtual async Task<TCacheItem> GetAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[] cachedBytes;
try
{
cachedBytes = await Cache.GetAsync(
NormalizeKey(key.ToString()),
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return null;
}
throw;
}
if (cachedBytes == null)
{
return null;
}
return Serializer.Deserialize<TCacheItem>(cachedBytes);
}
public virtual TCacheItem GetOrAdd(TCacheKey key, Func<TCacheItem> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null)
{
var value = Get(key, hideErrors);
if (value != null)
{
return value;
}
using (SyncSemaphore.Lock())
{
value = Get(key, hideErrors);
if (value != null)
{
return value;
}
value = factory();
Set(key, value, optionsFactory?.Invoke(), hideErrors);
}
return value;
}
public virtual async Task<TCacheItem> GetOrAddAsync(TCacheKey key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, CancellationToken token = default)
{
token = CancellationTokenProvider.FallbackToProvider(token);
var value = await GetAsync(key, hideErrors, token);
if (value != null)
{
return value;
}
using (await SyncSemaphore.LockAsync(token))
{
value = await GetAsync(key, hideErrors, token);
if (value != null)
{
return value;
}
value = await factory();
await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, token);
}
return value;
}
public virtual void Set(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
Cache.Set(
NormalizeKey(key.ToString()),
Serializer.Serialize(value),
options ?? DefaultCacheOptions
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return;
}
throw;
}
}
public virtual async Task SetAsync(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await Cache.SetAsync(
NormalizeKey(key.ToString()),
Serializer.Serialize(value),
options ?? DefaultCacheOptions,
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return;
}
throw;
}
}
public virtual void Refresh(TCacheKey key, bool? hideErrors = null)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
Cache.Refresh(NormalizeKey(key.ToString()));
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return;
}
throw;
}
}
public virtual async Task RefreshAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await Cache.RefreshAsync(NormalizeKey(key.ToString()), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return;
}
throw;
}
}
public virtual void Remove(TCacheKey key, bool? hideErrors = null)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
Cache.Remove(NormalizeKey(key.ToString()));
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
}
throw;
}
}
public virtual async Task RemoveAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await Cache.RemoveAsync(NormalizeKey(key.ToString()), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
return;
}
throw;
}
}
}
}

144
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs

@ -148,4 +148,148 @@ namespace Volo.Abp.Caching
CancellationToken token = default
);
}
/// <summary>
/// Represents a distributed cache of <typeparamref name="TCacheItem" /> type.
/// Uses a generic cache key type of <typeparamref name="TCacheKey" /> type.
/// </summary>
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
/// <typeparam name="TCacheKey">The type of cache key being used.</typeparam>
public interface IDistributedCache<TCacheItem, TCacheKey>
where TCacheItem : class
{
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item, or null.</returns>
TCacheItem Get(
TCacheKey key,
bool? hideErrors = null
);
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
Task<TCacheItem> GetAsync(
[NotNull] TCacheKey key,
bool? hideErrors = null,
CancellationToken token = default
);
/// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item.</returns>
TCacheItem GetOrAdd(
TCacheKey key,
Func<TCacheItem> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool? hideErrors = null
);
/// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
Task<TCacheItem> GetOrAddAsync(
[NotNull] TCacheKey key,
Func<Task<TCacheItem>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool? hideErrors = null,
CancellationToken token = default
);
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Set(
TCacheKey key,
TCacheItem value,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null
);
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task SetAsync(
[NotNull] TCacheKey key,
[NotNull] TCacheItem value,
[CanBeNull] DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
CancellationToken token = default
);
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Refresh(
TCacheKey key,
bool? hideErrors = null
);
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RefreshAsync(
TCacheKey key,
bool? hideErrors = null,
CancellationToken token = default
);
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Remove(
TCacheKey key,
bool? hideErrors = null
);
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RemoveAsync(
TCacheKey key,
bool? hideErrors = null,
CancellationToken token = default
);
}
}

145
framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs

@ -122,5 +122,150 @@ namespace Volo.Abp.Caching
cacheItem1.ShouldNotBeNull();
}
[Fact]
public async Task Should_Set_Get_And_Remove_Cache_Items_With_Integer_Type_CacheKey()
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem, int>>();
var cacheKey = 42;
const string personName = "john nash";
//Get (not exists yet)
var cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldBeNull();
//Set
cacheItem = new PersonCacheItem(personName);
await personCache.SetAsync(cacheKey, cacheItem);
//Get (it should be available now
cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldNotBeNull();
cacheItem.Name.ShouldBe(personName);
//Remove
await personCache.RemoveAsync(cacheKey);
//Get (not exists since removed)
cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldBeNull();
}
[Fact]
public async Task GetOrAddAsync_With_Integer_Type_CacheKey()
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem, int>>();
var cacheKey = 42;
const string personName = "john nash";
//Will execute the factory method to create the cache item
bool factoryExecuted = false;
var cacheItem = await personCache.GetOrAddAsync(cacheKey,
async () =>
{
factoryExecuted = true;
return new PersonCacheItem(personName);
});
factoryExecuted.ShouldBeTrue();
cacheItem.Name.ShouldBe(personName);
//This time, it will not execute the factory
factoryExecuted = false;
cacheItem = await personCache.GetOrAddAsync(cacheKey,
async () =>
{
factoryExecuted = true;
return new PersonCacheItem(personName);
});
factoryExecuted.ShouldBeFalse();
cacheItem.Name.ShouldBe(personName);
}
[Fact]
public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache_With_Integer_CacheKey()
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem, int>>();
var otherPersonCache = GetRequiredService<IDistributedCache<Sail.Testing.Caching.PersonCacheItem, int>>();
var cacheKey = 42;
const string personName = "john nash";
//Get (not exists yet)
var cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldBeNull();
var cacheItem1 = await otherPersonCache.GetAsync(cacheKey);
cacheItem1.ShouldBeNull();
//Set
cacheItem = new PersonCacheItem(personName);
await personCache.SetAsync(cacheKey, cacheItem);
//Get (it should be available now, but otherPersonCache not exists now.
cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldNotBeNull();
cacheItem.Name.ShouldBe(personName);
cacheItem1 = await otherPersonCache.GetAsync(cacheKey);
cacheItem1.ShouldBeNull();
//set other person cache
cacheItem1 = new Sail.Testing.Caching.PersonCacheItem(personName);
await otherPersonCache.SetAsync(cacheKey, cacheItem1);
cacheItem1 = await otherPersonCache.GetAsync(cacheKey);
cacheItem1.ShouldNotBeNull();
cacheItem1.Name.ShouldBe(personName);
//Remove
await personCache.RemoveAsync(cacheKey);
//Get (not exists since removed)
cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldBeNull();
cacheItem1 = await otherPersonCache.GetAsync(cacheKey);
cacheItem1.ShouldNotBeNull();
}
[Fact]
public async Task Should_Set_Get_And_Remove_Cache_Items_With_Object_Type_CacheKey()
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem, DummyObjectAsCacheKey>>();
var cacheKey = new DummyObjectAsCacheKey { DummyData = "DummyData", DummyInt = 42 };
const string personName = "john nash";
//Get (not exists yet)
var cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldBeNull();
//Set
cacheItem = new PersonCacheItem(personName);
await personCache.SetAsync(cacheKey, cacheItem);
//Get (it should be available now
cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldNotBeNull();
cacheItem.Name.ShouldBe(personName);
//Remove
await personCache.RemoveAsync(cacheKey);
//Get (not exists since removed)
cacheItem = await personCache.GetAsync(cacheKey);
cacheItem.ShouldBeNull();
}
}
}

7
framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs

@ -17,7 +17,14 @@ namespace Volo.Abp.Caching
Name = name;
}
}
public class DummyObjectAsCacheKey
{
public string DummyData { get; set; }
public int DummyInt { get; set; }
}
}
namespace Sail.Testing.Caching
{
[Serializable]

Loading…
Cancel
Save