diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs index d0bd31cb03..a3efc1355f 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using System; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Serialization; @@ -17,6 +18,11 @@ namespace Volo.Abp.Caching context.Services.AddDistributedMemoryCache(); context.Services.AddSingleton(typeof(IDistributedCache<>), typeof(DistributedCache<>)); + + context.Services.Configure(cacheOptions => + { + cacheOptions.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromMinutes(20); + }); } } } diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs index f9ef839785..a9dbe670bb 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using JetBrains.Annotations; namespace Volo.Abp.Caching diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs new file mode 100644 index 0000000000..ac8af06165 --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Caching.Distributed; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.Caching +{ + public class CacheOptions + { + /// + /// Global Cache entry options. + /// + public DistributedCacheEntryOptions GlobalCacheEntryOptions { get; set; } + /// + /// List of all cache configurators. + /// (func argument:Name of cache) + /// + public List> CacheConfigurators { get; set; } //TODO list item use a configurator interface instead? + public CacheOptions() + { + CacheConfigurators = new List>(); + GlobalCacheEntryOptions = new DistributedCacheEntryOptions(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index c897a20e3a..9a8aa3e443 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; using Nito.AsyncEx; using Volo.Abp.MultiTenancy; using Volo.Abp.Serialization; @@ -27,12 +28,17 @@ namespace Volo.Abp.Caching protected AsyncLock AsyncLock { get; } = new AsyncLock(); + protected DistributedCacheEntryOptions DefaultCacheOptions; + + private readonly CacheOptions _cacheOption; public DistributedCache( + IOptions cacheOption, IDistributedCache cache, ICancellationTokenProvider cancellationTokenProvider, IObjectSerializer objectSerializer, ICurrentTenant currentTenant) { + _cacheOption = cacheOption.Value; Cache = cache; CancellationTokenProvider = cancellationTokenProvider; ObjectSerializer = objectSerializer; @@ -64,7 +70,7 @@ namespace Volo.Abp.Caching } public TCacheItem GetOrAdd( - string key, + string key, Func factory, Func optionsFactory = null) { @@ -90,11 +96,12 @@ namespace Volo.Abp.Caching } public async Task GetOrAddAsync( - string key, + string key, Func> factory, - Func optionsFactory = null, + Func optionsFactory = null, CancellationToken token = default) { + token = CancellationTokenProvider.FallbackToProvider(token); var value = await GetAsync(key, token); if (value != null) { @@ -121,7 +128,7 @@ namespace Volo.Abp.Caching Cache.Set( NormalizeKey(key), ObjectSerializer.Serialize(value), - options ?? new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(20) } //TODO: implement per cache item and global defaults!!! + options ?? DefaultCacheOptions ); } @@ -130,7 +137,7 @@ namespace Volo.Abp.Caching return Cache.SetAsync( NormalizeKey(key), ObjectSerializer.Serialize(value), - options ?? new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(20) }, //TODO: implement per cache item and global defaults!!! + options ?? DefaultCacheOptions, CancellationTokenProvider.FallbackToProvider(token) ); } @@ -166,6 +173,18 @@ namespace Volo.Abp.Caching 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() { @@ -175,10 +194,13 @@ namespace Volo.Abp.Caching .OfType() .FirstOrDefault(); - CacheName = cacheNameAttribute != null ? cacheNameAttribute.Name : typeof(TCacheItem).Name; + CacheName = cacheNameAttribute != null ? cacheNameAttribute.Name : typeof(TCacheItem).FullName; //IgnoreMultiTenancy IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); + + //Configure default cache entry options + DefaultCacheOptions = GetDefaultCacheEntryOptions(); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index e036c4301c..32a954d430 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -1,10 +1,30 @@ -using Volo.Abp.Modularity; +using Microsoft.Extensions.Caching.Distributed; +using System; +using Volo.Abp.Modularity; namespace Volo.Abp.Caching { [DependsOn(typeof(AbpCachingModule))] public class AbpCachingTestModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(option => + { + option.CacheConfigurators.Add(cacheName => + { + if (cacheName == typeof(Sail.Testing.Caching.PersonCacheItem).FullName) + { + return new DistributedCacheEntryOptions() + { + AbsoluteExpiration = DateTime.Parse("2099-01-01 12:00:00") + }; + } + return null; + }); + option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20)); + }); + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs new file mode 100644 index 0000000000..9531711932 --- /dev/null +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Caching.Distributed; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Volo.Abp.Caching +{ + public class DistributedCache_ConfigureOptions_Test : AbpIntegratedTest + { + [Fact] + public async Task Configure_CacheOptions() + { + var personCache = GetRequiredService>(); + + var cacheKey = Guid.NewGuid().ToString(); + //Get (not exists yet) + var cacheItem = await personCache.GetAsync(cacheKey); + + cacheItem.ShouldBeNull(); + + GetDefaultCachingOptions(personCache).SlidingExpiration.ShouldBeNull(); + + GetDefaultCachingOptions(personCache).AbsoluteExpiration.ShouldBe(new DateTime(2099, 1, 1, 12, 0, 0)); + + } + + [Fact] + public async Task Default_CacheOptions_Should_Be_20_Mins() + { + var personCache = GetRequiredService>(); + + var cacheKey = Guid.NewGuid().ToString(); + + //Get (not exists yet) + var cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldBeNull(); + + GetDefaultCachingOptions(personCache).SlidingExpiration.ShouldBe(TimeSpan.FromMinutes(20)); + + } + private static DistributedCacheEntryOptions GetDefaultCachingOptions(object instance) + { + var defaultOptionsField = instance.GetType().GetTypeInfo().GetField("DefaultCacheOptions", BindingFlags.Instance | BindingFlags.NonPublic); + return (DistributedCacheEntryOptions)defaultOptionsField.GetValue(instance); + } + } +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 4beca1f32b..fc6b443893 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -72,5 +72,55 @@ namespace Volo.Abp.Caching factoryExecuted.ShouldBeFalse(); cacheItem.Name.ShouldBe(personName); } + + [Fact] + public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache() + { + var personCache = GetRequiredService>(); + var otherPersonCache = GetRequiredService>(); + + + var cacheKey = Guid.NewGuid().ToString(); + 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(); + + } } -} +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs index a1011bc186..b6c6f1dd0a 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs @@ -9,7 +9,25 @@ namespace Volo.Abp.Caching private PersonCacheItem() { - + + } + + public PersonCacheItem(string name) + { + Name = name; + } + } +} +namespace Sail.Testing.Caching +{ + [Serializable] + public class PersonCacheItem + { + public string Name { get; private set; } + + private PersonCacheItem() + { + } public PersonCacheItem(string name)