Skip to content

Commit da44c5a

Browse files
committed
Refactor distributed cache options
Introduces a base options class for distributed caches to consolidate common settings. Updates `DistributedCache`, `MessagePackDistributedCache`, and `RedisHybridCache` to consume these options via `IOptions`, simplifying their constructors and internal logic.
1 parent 7ecc365 commit da44c5a

15 files changed

Lines changed: 476 additions & 121 deletions

File tree

Neolution.Extensions.Caching.Abstractions/DistributedCache.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Options;
78

89
/// <inheritdoc />
910
/// <summary>
@@ -26,31 +27,49 @@ public abstract class DistributedCache<TCacheId> : IDistributedCache<TCacheId>
2627
/// </value>
2728
private static string CacheIdName => typeof(TCacheId).Name;
2829

30+
private readonly bool enableKeyEncoding;
31+
private readonly bool enableKeyLengthValidation;
32+
private readonly int? version;
33+
private readonly string? environmentPrefix;
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="DistributedCache{TCacheId}"/> class.
37+
/// </summary>
38+
/// <param name="optionsAccessor">The options accessor containing cache configuration.</param>
39+
/// <exception cref="ArgumentNullException">Thrown when optionsAccessor is null.</exception>
40+
protected DistributedCache(IOptions<DistributedCacheOptionsBase> optionsAccessor)
41+
{
42+
if (optionsAccessor == null)
43+
{
44+
throw new ArgumentNullException(nameof(optionsAccessor));
45+
}
46+
47+
var options = optionsAccessor.Value;
48+
this.enableKeyEncoding = options.EnableKeyEncoding;
49+
this.enableKeyLengthValidation = options.EnableKeyLengthValidation;
50+
this.version = options.Version;
51+
this.environmentPrefix = options.EnvironmentPrefix;
52+
}
53+
2954
/// <summary>
3055
/// Gets a value indicating whether optional cache keys should be URL-encoded.
31-
/// Default is true for safe handling of special characters.
32-
/// Set to false to maintain backwards compatibility with existing cache keys.
3356
/// </summary>
34-
protected virtual bool EnableKeyEncoding => true;
57+
protected bool EnableKeyEncoding => this.enableKeyEncoding;
3558

3659
/// <summary>
3760
/// Gets a value indicating whether cache key length should be validated.
38-
/// Default is true to ensure performance and compatibility with many cache backends.
39-
/// Set to false to disable length validation if your cache backend supports longer keys.
4061
/// </summary>
41-
protected virtual bool EnableKeyLengthValidation => true;
62+
protected bool EnableKeyLengthValidation => this.enableKeyLengthValidation;
4263

4364
/// <summary>
4465
/// Gets the cache key version for invalidation purposes.
45-
/// If null, version is not included in the cache key.
4666
/// </summary>
47-
protected virtual int? Version => null;
67+
protected int? Version => this.version;
4868

4969
/// <summary>
5070
/// Gets the optional environment prefix for cache key isolation.
51-
/// If null or empty, environment prefix is not included in the cache key.
5271
/// </summary>
53-
protected virtual string? EnvironmentPrefix => null;
72+
protected string? EnvironmentPrefix => this.environmentPrefix;
5473

5574
/// <inheritdoc />
5675
public T? Get<T>(TCacheId id)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
namespace Neolution.Extensions.Caching.Abstractions
2+
{
3+
/// <summary>
4+
/// Base class for distributed cache configuration options.
5+
/// Provides common configuration properties shared across all distributed cache implementations.
6+
/// </summary>
7+
public abstract class DistributedCacheOptionsBase
8+
{
9+
/// <summary>
10+
/// Gets or sets the cache key version for invalidation purposes.
11+
/// If null, version is not included in the cache key.
12+
/// Changing this version will invalidate all existing cache entries.
13+
/// The version will be formatted as "v{number}" in the cache key (e.g., v1, v2).
14+
/// Default: null (no version in cache key).
15+
/// </summary>
16+
/// <example>
17+
/// options.Version = 2; // Cache key becomes: "MyCacheId:v2:UserProfile"
18+
/// </example>
19+
public int? Version { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the optional environment prefix for cache key isolation.
23+
/// If null or empty, environment prefix is not included in the cache key.
24+
/// Useful for multi-environment scenarios sharing the same cache backend.
25+
/// Default: null (no environment prefix in cache key).
26+
/// </summary>
27+
/// <example>
28+
/// options.EnvironmentPrefix = "staging"; // Cache key becomes: "staging:MyCacheId:UserProfile"
29+
/// </example>
30+
public string? EnvironmentPrefix { get; set; }
31+
32+
/// <summary>
33+
/// Gets or sets a value indicating whether optional cache keys should be URL-encoded.
34+
/// URL encoding ensures safe handling of special characters in distributed cache backends.
35+
/// Set to false to maintain backwards compatibility with existing cache keys.
36+
/// Default: true.
37+
/// </summary>
38+
public bool EnableKeyEncoding { get; set; } = true;
39+
40+
/// <summary>
41+
/// Gets or sets a value indicating whether cache key length should be validated.
42+
/// Default is true to ensure performance and compatibility with many cache backends.
43+
/// Set to false to disable length validation if your cache backend supports longer keys.
44+
/// Default: true.
45+
/// </summary>
46+
public bool EnableKeyLengthValidation { get; set; } = true;
47+
}
48+
}

Neolution.Extensions.Caching.Abstractions/Neolution.Extensions.Caching.Abstractions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
1011
<PackageReference Include="Neolution.CodeAnalysis" Version="2.6.1">
1112
<PrivateAssets>all</PrivateAssets>
1213
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Neolution.Extensions.Caching.Distributed/MessagePackDistributedCache.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class MessagePackDistributedCache<TCacheId> : DistributedCache<TCacheId>
3333
/// <param name="cache">The cache.</param>
3434
/// <param name="optionsAccessor">The options accessor.</param>
3535
public MessagePackDistributedCache(IDistributedCache cache, IOptions<MessagePackDistributedCacheOptions> optionsAccessor)
36+
: base(optionsAccessor)
3637
{
3738
this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
3839

@@ -42,8 +43,6 @@ public MessagePackDistributedCache(IDistributedCache cache, IOptions<MessagePack
4243
}
4344

4445
var options = optionsAccessor.Value;
45-
this.Version = options.Version;
46-
this.EnvironmentPrefix = options.EnvironmentPrefix;
4746

4847
if (options.RequireMessagePackObjectAnnotation)
4948
{
@@ -56,12 +55,6 @@ public MessagePackDistributedCache(IDistributedCache cache, IOptions<MessagePack
5655
}
5756
}
5857

59-
/// <inheritdoc />
60-
protected override int? Version { get; }
61-
62-
/// <inheritdoc />
63-
protected override string? EnvironmentPrefix { get; }
64-
6558
/// <inheritdoc />
6659
protected override T? GetCacheObject<T>(string key)
6760
where T : class
Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,25 @@
11
namespace Neolution.Extensions.Caching.Distributed
22
{
3-
using Microsoft.Extensions.Options;
3+
using Neolution.Extensions.Caching.Abstractions;
44

55
/// <summary>
6-
/// The options for the MessagePack distributed cache implementation.
6+
/// Configuration options for MessagePack distributed cache.
77
/// </summary>
8-
public class MessagePackDistributedCacheOptions : IOptions<MessagePackDistributedCacheOptions>
8+
public class MessagePackDistributedCacheOptions : DistributedCacheOptionsBase
99
{
1010
/// <summary>
11-
/// Gets or sets a value indicating whether to disable compression, to not waste CPU resources if working with an in-memory cache backend.
11+
/// Gets or sets a value indicating whether to disable compression.
12+
/// Set to true to save CPU when using in-memory cache backends.
13+
/// Default: false (compression enabled with LZ4).
1214
/// </summary>
1315
public bool DisableCompression { get; set; }
1416

1517
/// <summary>
16-
/// Gets or sets a value indicating whether to require <see cref="MessagePack.MessagePackObjectAttribute"/> annotation for serializable types.
17-
/// Doing so would result in better overall serialization performance and smaller files.
18+
/// Gets or sets a value indicating whether to require MessagePackObject attribute.
19+
/// Setting this to true improves serialization performance but requires decorating
20+
/// your classes with [MessagePackObject] attribute.
21+
/// Default: false (uses contractless serialization).
1822
/// </summary>
19-
/// <value>
20-
/// <c>true</c> to require <see cref="MessagePack.MessagePackObjectAttribute"/> annotation; otherwise, <c>false</c>.
21-
/// </value>
2223
public bool RequireMessagePackObjectAnnotation { get; set; }
23-
24-
/// <summary>
25-
/// Gets or sets the cache key version to use for cache invalidation.
26-
/// If not set, version is not included in the cache key.
27-
/// Changing this version will invalidate all existing cache keys with that version.
28-
/// The version will be formatted as "v{number}" in the cache key (e.g., v1, v2).
29-
/// </summary>
30-
/// <value>
31-
/// The cache key version. Defaults to null (no version in cache key).
32-
/// </value>
33-
public int? Version { get; set; }
34-
35-
/// <summary>
36-
/// Gets or sets the optional environment prefix for cache key isolation.
37-
/// If not set, environment prefix is not included in the cache key.
38-
/// Useful for multi-environment scenarios (e.g., "dev", "staging", "prod").
39-
/// </summary>
40-
/// <value>
41-
/// The environment prefix. Defaults to null (no environment prefix in cache key).
42-
/// </value>
43-
public string? EnvironmentPrefix { get; set; }
44-
45-
/// <inheritdoc />
46-
public MessagePackDistributedCacheOptions Value => this;
4724
}
4825
}

Neolution.Extensions.Caching.Distributed/ServiceCollectionExtensions.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,28 @@ namespace Microsoft.Extensions.DependencyInjection
1111
public static class ServiceCollectionExtensions
1212
{
1313
/// <summary>
14-
/// Adds the distributed caching implementation that uses MessagePack to serialize and deserialize the values to cache.
14+
/// Adds the distributed caching implementation that uses MessagePack for serialization.
15+
/// Requires an <see cref="Microsoft.Extensions.Caching.Distributed.IDistributedCache"/>
16+
/// provider to be registered (e.g., Redis, SQL Server, Memory).
1517
/// </summary>
16-
/// <param name="services">The services.</param>
17-
public static void AddMessagePackDistributedCache(this IServiceCollection services)
18+
/// <param name="services">The service collection.</param>
19+
/// <returns>The service collection for fluent chaining.</returns>
20+
public static IServiceCollection AddMessagePackDistributedCache(this IServiceCollection services)
1821
{
19-
services.AddMessagePackDistributedCache(_ => { });
22+
return services.AddMessagePackDistributedCache(_ => { });
2023
}
2124

2225
/// <summary>
23-
/// Adds the distributed caching implementation that uses MessagePack to serialize and deserialize the values to cache. Allows to configure options.
26+
/// Adds the distributed caching implementation that uses MessagePack for serialization,
27+
/// with custom configuration options.
28+
/// Requires an <see cref="Microsoft.Extensions.Caching.Distributed.IDistributedCache"/>
29+
/// provider to be registered (e.g., Redis, SQL Server, Memory).
2430
/// </summary>
25-
/// <param name="services">The services.</param>
26-
/// <param name="configureOptions">The setup action.</param>
27-
public static void AddMessagePackDistributedCache(this IServiceCollection services, Action<MessagePackDistributedCacheOptions> configureOptions)
31+
/// <param name="services">The service collection.</param>
32+
/// <param name="configureOptions">The action to configure cache options.</param>
33+
/// <returns>The service collection for fluent chaining.</returns>
34+
/// <exception cref="ArgumentNullException">Thrown when configureOptions is null.</exception>
35+
public static IServiceCollection AddMessagePackDistributedCache(this IServiceCollection services, Action<MessagePackDistributedCacheOptions> configureOptions)
2836
{
2937
if (configureOptions == null)
3038
{
@@ -34,6 +42,8 @@ public static void AddMessagePackDistributedCache(this IServiceCollection servic
3442
services.AddOptions();
3543
services.Configure(configureOptions);
3644
services.AddSingleton(typeof(IDistributedCache<>), typeof(MessagePackDistributedCache<>));
45+
46+
return services;
3747
}
3848
}
3949
}

Neolution.Extensions.Caching.InMemory/ServiceCollectionExtensions.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ namespace Microsoft.Extensions.DependencyInjection
1010
public static class ServiceCollectionExtensions
1111
{
1212
/// <summary>
13-
/// Adds the Neolution default non-distributed memory caching implementation.
13+
/// Adds the in-memory caching implementation.
14+
/// Uses Microsoft's <see cref="Microsoft.Extensions.Caching.Memory.IMemoryCache"/> internally.
1415
/// </summary>
15-
/// <param name="services">The services.</param>
16-
public static void AddInMemoryCache(this IServiceCollection services)
16+
/// <param name="services">The service collection.</param>
17+
/// <returns>The service collection for fluent chaining.</returns>
18+
public static IServiceCollection AddInMemoryCache(this IServiceCollection services)
1719
{
1820
services.AddMemoryCache();
1921
services.AddSingleton(typeof(IMemoryCache<>), typeof(InMemoryCache<>));
22+
return services;
2023
}
2124
}
2225
}

Neolution.Extensions.Caching.RedisHybrid/MsgPackSerializer.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,28 @@
1212
public class MsgPackSerializer : ISerializer
1313
{
1414
/// <summary>
15-
/// The options
15+
/// The MessagePack serializer options.
1616
/// </summary>
17-
private readonly MessagePackSerializerOptions options = MessagePack.Resolvers.ContractlessStandardResolver.Options
18-
.WithCompression(MessagePackCompression.Lz4BlockArray);
17+
private readonly MessagePackSerializerOptions options;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="MsgPackSerializer"/> class.
21+
/// Compression is disabled by default to save CPU for in-memory scenarios.
22+
/// </summary>
23+
public MsgPackSerializer()
24+
: this(false)
25+
{
26+
}
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="MsgPackSerializer"/> class.
30+
/// </summary>
31+
/// <param name="enableCompression">If set to <c>true</c>, enables LZ4 compression.</param>
32+
public MsgPackSerializer(bool enableCompression)
33+
{
34+
this.options = MessagePack.Resolvers.ContractlessStandardResolver.Options
35+
.WithCompression(enableCompression ? MessagePackCompression.Lz4BlockArray : MessagePackCompression.None);
36+
}
1937

2038
/// <summary>
2139
/// Deserializes the specified data.

Neolution.Extensions.Caching.RedisHybrid/RedisHybridCache.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,16 @@ public class RedisHybridCache<TCacheId> : DistributedCache<TCacheId>
2828
/// <param name="cacheClient">The cache client.</param>
2929
/// <param name="optionsAccessor">The options accessor.</param>
3030
public RedisHybridCache(ICacheClient cacheClient, IOptions<RedisHybridCacheOptions> optionsAccessor)
31+
: base(optionsAccessor)
3132
{
3233
this.cacheClient = cacheClient ?? throw new ArgumentNullException(nameof(cacheClient));
3334

3435
if (optionsAccessor == null)
3536
{
3637
throw new ArgumentNullException(nameof(optionsAccessor));
3738
}
38-
39-
var options = optionsAccessor.Value;
40-
this.Version = options.Version;
41-
this.EnvironmentPrefix = options.EnvironmentPrefix;
4239
}
4340

44-
/// <inheritdoc />
45-
protected override int? Version { get; }
46-
47-
/// <inheritdoc />
48-
protected override string? EnvironmentPrefix { get; }
49-
5041
/// <inheritdoc />
5142
protected override T? GetCacheObject<T>(string key)
5243
where T : class
Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,17 @@
11
namespace Neolution.Extensions.Caching.RedisHybrid
22
{
3-
using Microsoft.Extensions.Options;
3+
using Neolution.Extensions.Caching.Abstractions;
44

55
/// <summary>
6-
/// The options for the Redis hybrid cache implementation.
6+
/// Configuration options for Redis hybrid cache (L1 + L2 caching).
77
/// </summary>
8-
public class RedisHybridCacheOptions : IOptions<RedisHybridCacheOptions>
8+
public class RedisHybridCacheOptions : DistributedCacheOptionsBase
99
{
1010
/// <summary>
11-
/// Gets or sets the cache key version to use for cache invalidation.
12-
/// If not set, version is not included in the cache key.
13-
/// Changing this version will invalidate all existing cache keys with that version.
14-
/// The version will be formatted as "v{number}" in the cache key (e.g., v1, v2).
11+
/// Gets or sets a value indicating whether to enable compression for serialization.
12+
/// Set to true to enable compression (LZ4) when bandwidth is a concern.
13+
/// Default: false (compression disabled to save CPU cycles).
1514
/// </summary>
16-
/// <value>
17-
/// The cache key version. Defaults to null (no version in cache key).
18-
/// </value>
19-
public int? Version { get; set; }
20-
21-
/// <summary>
22-
/// Gets or sets the optional environment prefix for cache key isolation.
23-
/// If not set, environment prefix is not included in the cache key.
24-
/// Useful for multi-environment scenarios (e.g., "dev", "staging", "prod").
25-
/// </summary>
26-
/// <value>
27-
/// The environment prefix. Defaults to null (no environment prefix in cache key).
28-
/// </value>
29-
public string? EnvironmentPrefix { get; set; }
30-
31-
/// <inheritdoc />
32-
public RedisHybridCacheOptions Value => this;
15+
public bool EnableCompression { get; set; }
3316
}
3417
}

0 commit comments

Comments
 (0)