Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle`1.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,21 +35,37 @@ public class MemoryCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public MemoryCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory)
: this(managerConfiguration, configuration, loggerFactory, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MemoryCacheHandle{TCacheValue}"/> class.
/// </summary>
/// <param name="managerConfiguration">The manager configuration.</param>
/// <param name="configuration">The cache handle configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="memoryCacheOptions">The vendor specific options.</param>
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();
Expand DownExpand Up@@ -397,4 +413,4 @@ private static void ParseKeyParts(int instanceKeyLength, string fullKey, out boo
}
}
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,5 +39,45 @@ public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(t
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource = false)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, RuntimeMemoryCacheOptions options)
=> WithSystemRuntimeCacheHandle(part, instanceName, false, options);

/// <summary>
/// Adds a <see cref="MemoryCacheHandle{TCacheValue}" /> using a <see cref="System.Runtime.Caching.MemoryCache"/> instance with the given <paramref name="instanceName"/>.
/// The named cache instance can be configured via <paramref name="options"/>.
/// </summary>
/// <param name="part">The builder part.</param>
/// <param name="instanceName">The name to be used for the cache instance.</param>
/// <param name="isBackplaneSource">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.</param>
/// <param name="options">
/// The <see cref="RuntimeMemoryCacheOptions"/> which should be used to initiate this cache.
/// If <c>Null</c>, default options will be used.
/// </param>
/// <returns>
/// The builder part.
/// </returns>
/// <exception cref="System.ArgumentNullException">If part is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="instanceName"/> is null.</exception>
public static ConfigurationBuilderCacheHandlePart WithSystemRuntimeCacheHandle(
this ConfigurationBuilderCachePart part, string instanceName, bool isBackplaneSource, RuntimeMemoryCacheOptions options)
=> part?.WithHandle(typeof(MemoryCacheHandle<>), instanceName, isBackplaneSource, options);
}
}
}
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// <see cref="System.Runtime.Caching.MemoryCache"/> configuration options
/// </summary>
public class RuntimeMemoryCacheOptions
{
/// <summary>
/// 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.
/// </summary>
public int CacheMemoryLimitMegabytes { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public int PhysicalMemoryLimitPercentage { get; set; } = 0;

/// <summary>
/// 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.
/// </summary>
public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(2);

/// <summary>
/// Gets the configuration as a <see cref="NameValueCollection"/>
/// </summary>
/// <returns>A <see cref="NameValueCollection"/> with the current configuration.</returns>
public NameValueCollection AsNameValueCollection()
{
return new NameValueCollection(3)
{
{ nameof(CacheMemoryLimitMegabytes), CacheMemoryLimitMegabytes.ToString(CultureInfo.InvariantCulture) },
{ nameof(PhysicalMemoryLimitPercentage), PhysicalMemoryLimitPercentage.ToString(CultureInfo.InvariantCulture) },
{ nameof(PollingInterval), PollingInterval.ToString("c") }
};
}
}
}
60 changes: 60 additions & 0 deletions test/CacheManager.Tests/MemoryCacheTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
Expand DownExpand Up@@ -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<object>)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<InvalidOperationException>().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

Expand DownExpand Up@@ -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<object>)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
Expand Down
2 changes: 1 addition & 1 deletion test/CacheManager.Tests/TestCacheManagers.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,7 +338,7 @@ public static ICacheManager<object> 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()
Expand Down