diff --git a/src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs b/src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs index 9babda4d..ec48653c 100644 --- a/src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs +++ b/src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs @@ -35,21 +35,37 @@ public class MemoryCacheHandle : BaseCacheHandle /// The cache handle configuration. /// The logger factory. public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory) + : this(managerConfiguration, configuration, loggerFactory, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The manager configuration. + /// The cache handle configuration. + /// The logger factory. + /// The vendor specific options. + public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory, RuntimeMemoryCacheOptions memoryCacheOptions) : base(managerConfiguration, configuration) { NotNull(configuration, nameof(configuration)); NotNull(loggerFactory, nameof(loggerFactory)); - Logger = loggerFactory.CreateLogger(this); _cacheName = configuration.Name; - if (_cacheName.ToUpper(CultureInfo.InvariantCulture).Equals(DefaultName.ToUpper(CultureInfo.InvariantCulture))) + + //if (_cacheName.ToUpper(CultureInfo.InvariantCulture).Equals(DefaultName.ToUpper(CultureInfo.InvariantCulture))) + if (DefaultName.Equals(_cacheName, StringComparison.InvariantCultureIgnoreCase)) { + //we can't change default cache configuration by code, can we? + Ensure(memoryCacheOptions == null, "MemoryCache Default instance can only be configured through app/web.config."); + _cache = MemoryCache.Default; } else { - _cache = new MemoryCache(_cacheName); + _cache = new MemoryCache(_cacheName, memoryCacheOptions?.AsNameValueCollection()); } _instanceKey = Guid.NewGuid().ToString(); @@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo } } } -} \ No newline at end of file +} diff --git a/src/CacheManager.SystemRuntimeCaching/RuntimeCachingBuilderExtensions.cs b/src/CacheManager.SystemRuntimeCaching/RuntimeCachingBuilderExtensions.cs index ce767ad5..ee8ee370 100644 --- a/src/CacheManager.SystemRuntimeCaching/RuntimeCachingBuilderExtensions.cs +++ b/src/CacheManager.SystemRuntimeCaching/RuntimeCachingBuilderExtensions.cs @@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t /// Thrown if is null. public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false) => part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource); + + /// + /// Adds a using a instance with the given . + /// The named cache instance can be configured via . + /// + /// The builder part. + /// The name to be used for the cache instance. + /// + /// The which should be used to initiate this cache. + /// If Null, default options will be used. + /// + /// + /// The builder part. + /// + /// If part is null. + /// Thrown if is null. + public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle( + this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options) + => WithSystemRuntimeCacheHandle(part, instanceName, false, options); + + /// + /// Adds a using a instance with the given . + /// The named cache instance can be configured via . + /// + /// The builder part. + /// The name to be used for the cache instance. + /// Set this to true if this cache handle should be the source of the backplane. + /// This setting will be ignored if no backplane is configured. + /// + /// The which should be used to initiate this cache. + /// If Null, default options will be used. + /// + /// + /// The builder part. + /// + /// If part is null. + /// Thrown if is null. + public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle( + this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options) + => part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options); } -} \ No newline at end of file +} diff --git a/src/CacheManager.SystemRuntimeCaching/RuntimeMemoryCacheOptions.cs b/src/CacheManager.SystemRuntimeCaching/RuntimeMemoryCacheOptions.cs new file mode 100644 index 00000000..9168d4ae --- /dev/null +++ b/src/CacheManager.SystemRuntimeCaching/RuntimeMemoryCacheOptions.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace CacheManager.SystemRuntimeCaching +{ + /// + /// configuration options + /// + public class RuntimeMemoryCacheOptions + { + /// + /// An integer value that specifies the maximum allowable size, in megabytes, that an instance of a MemoryCache can grow to. The default value is 0, which means that the autosizing heuristics of the MemoryCache class are used by default. + /// + public int CacheMemoryLimitMegabytes { get; set; } = 0; + + /// + /// An integer value between 0 and 100 that specifies the maximum percentage of physically installed computer memory that can be consumed by the cache. The default value is 0, which means that the autosizing heuristics of the MemoryCache class are used by default. + /// + public int PhysicalMemoryLimitPercentage { get; set; } = 0; + + /// + /// A value that indicates the time interval after which the cache implementation compares the current memory load against the absolute and percentage-based memory limits that are set for the cache instance. + /// + public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2); + + /// + /// Gets the configuration as a + /// + /// A with the current configuration. + public NameValueCollection AsNameValueCollection() + { + return new NameValueCollection(3) + { + { nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) }, + { nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) }, + { nameof(PollingInterval), PollingInterval.ToString("c") } + }; + } + } +} diff --git a/test/CacheManager.Tests/MemoryCacheTests.cs b/test/CacheManager.Tests/MemoryCacheTests.cs index 86397b71..c1bab4a6 100644 --- a/test/CacheManager.Tests/MemoryCacheTests.cs +++ b/test/CacheManager.Tests/MemoryCacheTests.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using CacheManager.Core; @@ -186,6 +187,43 @@ public void SysRuntime_Extensions_NamedB() cache.CacheHandles.Count().Should().Be(1); } + [Fact] + public void SysRuntime_Extensions_NamedWithCodeCfg() + { + var expectedCacheOptions = new CacheManager.SystemRuntimeCaching.RuntimeMemoryCacheOptions() + { + CacheMemoryLimitMegabytes = 13, + PhysicalMemoryLimitPercentage = 24, + PollingInterval = TimeSpan.FromMinutes(3) + }; + + using (var act = CacheFactory.Build(_ => _.WithSystemRuntimeCacheHandle("NamedTestWithCfg", expectedCacheOptions))) + { + // arrange + var settings = ((CacheManager.SystemRuntimeCaching.MemoryCacheHandle)act.CacheHandles.ElementAt(0)).CacheSettings; + + // act assert + settings["CacheMemoryLimitMegabytes"].Should().Be(expectedCacheOptions.CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture)); + settings["PhysicalMemoryLimitPercentage"].Should().Be(expectedCacheOptions.PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture)); + settings["PollingInterval"].Should().Be(expectedCacheOptions.PollingInterval.ToString("c")); + } + } + + [Fact] + public void SysRuntime_Extensions_DefaultWithCodeCfg() + { + var expectedCacheOptions = new CacheManager.SystemRuntimeCaching.RuntimeMemoryCacheOptions() + { + CacheMemoryLimitMegabytes = 13, + PhysicalMemoryLimitPercentage = 24, + PollingInterval = TimeSpan.FromMinutes(3) + }; + + Action act = () => CacheFactory.Build(_ => _.WithSystemRuntimeCacheHandle("default", expectedCacheOptions)); + + act.Should().Throw().WithMessage("*Default*app/web.config*"); + } + // disabling for netstandard 2 as it doesn't seem to read the "default" configuration from app.config. Might be an xunit/runner issue as the configuration stuff has been ported #if !NETCOREAPP2 @@ -221,6 +259,28 @@ public void SysRuntime_CreateNamedCache() } } + [Fact] + [Trait("category", "NotOnMono")] + public void SysRuntime_CreateNamedCacheOverrideWithCodeCfg() + { + var expectedCacheOptions = new CacheManager.SystemRuntimeCaching.RuntimeMemoryCacheOptions() + { + CacheMemoryLimitMegabytes = 11, + PhysicalMemoryLimitPercentage = 22, + PollingInterval = TimeSpan.FromMinutes(4) + }; + + using (var act = CacheFactory.Build(_ => _.WithSystemRuntimeCacheHandle("NamedTest", expectedCacheOptions))) + { + // arrange + var settings = ((CacheManager.SystemRuntimeCaching.MemoryCacheHandle)act.CacheHandles.ElementAt(0)).CacheSettings; + + // act assert + settings["CacheMemoryLimitMegabytes"].Should().Be(expectedCacheOptions.CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture)); + settings["PhysicalMemoryLimitPercentage"].Should().Be(expectedCacheOptions.PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture)); + settings["PollingInterval"].Should().Be(expectedCacheOptions.PollingInterval.ToString("c")); + } + } #endif #endregion System Runtime Caching diff --git a/test/CacheManager.Tests/TestCacheManagers.cs b/test/CacheManager.Tests/TestCacheManagers.cs index 2cff4649..84999ec7 100644 --- a/test/CacheManager.Tests/TestCacheManagers.cs +++ b/test/CacheManager.Tests/TestCacheManagers.cs @@ -338,7 +338,7 @@ public static ICacheManager WithMemoryAndDictionaryHandles .Builder .WithSystemRuntimeCacheHandle() .EnableStatistics() - .And.WithSystemRuntimeCacheHandle() + .And.WithSystemRuntimeCacheHandle("LimitedCacheHandle", new SystemRuntimeCaching.RuntimeMemoryCacheOptions() { PhysicalMemoryLimitPercentage = 20, CacheMemoryLimitMegabytes = 200 }) .EnableStatistics() .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromSeconds(1000)) .And.WithDictionaryHandle()