Skip to content

Commit 2e0e11a

Browse files
committed
Enhance Redis hybrid cache configuration
1 parent b5ce60e commit 2e0e11a

2 files changed

Lines changed: 148 additions & 21 deletions

File tree

Neolution.Extensions.Caching.RedisHybrid/ServiceCollectionExtensions.cs

Lines changed: 137 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ namespace Microsoft.Extensions.DependencyInjection
33
{
44
using System;
55
using Foundatio.Caching;
6+
using Foundatio.Serializer;
67
using Microsoft.Extensions.Logging;
8+
using Microsoft.Extensions.Options;
79
using Neolution.Extensions.Caching.Abstractions;
810
using Neolution.Extensions.Caching.RedisHybrid;
911
using StackExchange.Redis;
@@ -19,8 +21,20 @@ public static class ServiceCollectionExtensions
1921
/// <param name="services">The service collection.</param>
2022
/// <param name="redisConnectionString">The Redis connection string.</param>
2123
/// <returns>The service collection for fluent chaining.</returns>
24+
/// <exception cref="ArgumentNullException">Thrown when services or redisConnectionString is null.</exception>
25+
/// <exception cref="ArgumentException">Thrown when redisConnectionString is empty or whitespace.</exception>
2226
public static IServiceCollection AddRedisHybridCache(this IServiceCollection services, string redisConnectionString)
2327
{
28+
if (services == null)
29+
{
30+
throw new ArgumentNullException(nameof(services));
31+
}
32+
33+
if (string.IsNullOrWhiteSpace(redisConnectionString))
34+
{
35+
throw new ArgumentException("Redis connection string cannot be null or empty.", nameof(redisConnectionString));
36+
}
37+
2438
return services.AddRedisHybridCache(redisConnectionString, _ => { });
2539
}
2640

@@ -32,32 +46,144 @@ public static IServiceCollection AddRedisHybridCache(this IServiceCollection ser
3246
/// <param name="redisConnectionString">The Redis connection string.</param>
3347
/// <param name="configureOptions">The action to configure cache options.</param>
3448
/// <returns>The service collection for fluent chaining.</returns>
35-
/// <exception cref="ArgumentNullException">Thrown when configureOptions is null.</exception>
49+
/// <exception cref="ArgumentNullException">Thrown when services, redisConnectionString, or configureOptions is null.</exception>
50+
/// <exception cref="ArgumentException">Thrown when redisConnectionString is empty or whitespace.</exception>
3651
public static IServiceCollection AddRedisHybridCache(this IServiceCollection services, string redisConnectionString, Action<RedisHybridCacheOptions> configureOptions)
3752
{
53+
if (services == null)
54+
{
55+
throw new ArgumentNullException(nameof(services));
56+
}
57+
58+
if (string.IsNullOrWhiteSpace(redisConnectionString))
59+
{
60+
throw new ArgumentException("Redis connection string cannot be null or empty.", nameof(redisConnectionString));
61+
}
62+
3863
if (configureOptions == null)
3964
{
4065
throw new ArgumentNullException(nameof(configureOptions));
4166
}
4267

4368
services.AddSingleton<IConnectionMultiplexer>(sp => ConnectionMultiplexer.Connect(redisConnectionString));
4469

70+
return services.AddRedisHybridCacheCore(configureOptions);
71+
}
72+
73+
/// <summary>
74+
/// Adds the Redis hybrid cache implementation using an existing <see cref="IConnectionMultiplexer"/> instance.
75+
/// </summary>
76+
/// <param name="services">The service collection.</param>
77+
/// <param name="multiplexer">The pre-created <see cref="IConnectionMultiplexer"/> instance.</param>
78+
/// <returns>The service collection for fluent chaining.</returns>
79+
/// <exception cref="ArgumentNullException">Thrown when services or multiplexer is null.</exception>
80+
/// <remarks>
81+
/// This overload is recommended when sharing the same Redis connection across multiple services
82+
/// (e.g., Data Protection, Session State, SignalR backplane) to avoid creating multiple connections.
83+
/// </remarks>
84+
public static IServiceCollection AddRedisHybridCache(this IServiceCollection services, IConnectionMultiplexer multiplexer)
85+
{
86+
if (services == null)
87+
{
88+
throw new ArgumentNullException(nameof(services));
89+
}
90+
91+
if (multiplexer == null)
92+
{
93+
throw new ArgumentNullException(nameof(multiplexer));
94+
}
95+
96+
return services.AddRedisHybridCache(multiplexer, _ => { });
97+
}
98+
99+
/// <summary>
100+
/// Adds the Redis hybrid cache implementation using an existing <see cref="IConnectionMultiplexer"/> instance.
101+
/// </summary>
102+
/// <param name="services">The service collection.</param>
103+
/// <param name="multiplexer">The pre-created <see cref="IConnectionMultiplexer"/> instance.</param>
104+
/// <param name="configureOptions">The action to configure cache options.</param>
105+
/// <returns>The service collection for fluent chaining.</returns>
106+
/// <exception cref="ArgumentNullException">Thrown when services, multiplexer, or configureOptions is null.</exception>
107+
/// <remarks>
108+
/// This overload is recommended when sharing the same Redis connection across multiple services
109+
/// (e.g., Data Protection, Session State, SignalR backplane) to avoid creating multiple connections.
110+
/// </remarks>
111+
public static IServiceCollection AddRedisHybridCache(this IServiceCollection services, IConnectionMultiplexer multiplexer, Action<RedisHybridCacheOptions> configureOptions)
112+
{
113+
if (services == null)
114+
{
115+
throw new ArgumentNullException(nameof(services));
116+
}
117+
118+
if (multiplexer == null)
119+
{
120+
throw new ArgumentNullException(nameof(multiplexer));
121+
}
122+
123+
if (configureOptions == null)
124+
{
125+
throw new ArgumentNullException(nameof(configureOptions));
126+
}
127+
128+
// Register the provided multiplexer instance so other libraries (e.g. DataProtection)
129+
// can reuse the same connection, then configure the cache.
130+
services.AddSingleton<IConnectionMultiplexer>(multiplexer);
131+
return services.AddRedisHybridCacheCore(configureOptions);
132+
}
133+
134+
/// <summary>
135+
/// Adds the Redis hybrid cache implementation (L1 + L2 caching with message broker synchronization),
136+
/// using an already registered IConnectionMultiplexer.
137+
/// </summary>
138+
/// <param name="services">The service collection.</param>
139+
/// <param name="configureOptions">The action to configure cache options.</param>
140+
/// <returns>The service collection for fluent chaining.</returns>
141+
/// <exception cref="ArgumentNullException">Thrown when services or configureOptions is null.</exception>
142+
/// <remarks>
143+
/// Use this overload when you need to share the same IConnectionMultiplexer instance
144+
/// for other purposes (e.g., Data Protection keys). Register IConnectionMultiplexer before calling this method.
145+
/// </remarks>
146+
public static IServiceCollection AddRedisHybridCache(this IServiceCollection services, Action<RedisHybridCacheOptions> configureOptions)
147+
{
148+
if (services == null)
149+
{
150+
throw new ArgumentNullException(nameof(services));
151+
}
152+
153+
if (configureOptions == null)
154+
{
155+
throw new ArgumentNullException(nameof(configureOptions));
156+
}
157+
158+
return services.AddRedisHybridCacheCore(configureOptions);
159+
}
160+
161+
/// <summary>
162+
/// Adds the Redis hybrid cache implementation core services and configuration.
163+
/// </summary>
164+
/// <param name="services">The service collection.</param>
165+
/// <param name="configureOptions">The action to configure cache options.</param>
166+
/// <returns>The service collection for fluent chaining.</returns>
167+
private static IServiceCollection AddRedisHybridCacheCore(this IServiceCollection services, Action<RedisHybridCacheOptions> configureOptions)
168+
{
45169
services.AddOptions();
46170
services.Configure(configureOptions);
47171

48-
services.AddSingleton<ICacheClient>(sp =>
172+
services.AddSingleton(typeof(IDistributedCache<>), typeof(RedisHybridCache<>));
173+
174+
services.AddSingleton<ISerializer>(sp =>
49175
{
50-
var options = sp.GetService<Microsoft.Extensions.Options.IOptions<RedisHybridCacheOptions>>();
176+
var options = sp.GetService<IOptions<RedisHybridCacheOptions>>();
51177
var enableCompression = options?.Value?.EnableCompression ?? false;
52-
53-
return new RedisHybridCacheClient(new RedisHybridCacheClientOptions
54-
{
55-
ConnectionMultiplexer = sp.GetService<IConnectionMultiplexer>(),
56-
LoggerFactory = sp.GetService<ILoggerFactory>(),
57-
Serializer = new MsgPackSerializer(enableCompression),
58-
});
178+
return new MsgPackSerializer(enableCompression);
59179
});
60-
services.AddSingleton(typeof(IDistributedCache<>), typeof(RedisHybridCache<>));
180+
181+
services.AddSingleton<ICacheClient>(sp => new RedisHybridCacheClient(new RedisHybridCacheClientOptions
182+
{
183+
ConnectionMultiplexer = sp.GetRequiredService<IConnectionMultiplexer>(),
184+
LoggerFactory = sp.GetService<ILoggerFactory>(),
185+
Serializer = sp.GetRequiredService<ISerializer>(),
186+
}));
61187

62188
return services;
63189
}

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,19 @@ public void ConfigureServices(IServiceCollection services)
8585
```csharp
8686
public void ConfigureServices(IServiceCollection services)
8787
{
88-
// Basic configuration
88+
// Basic configuration with connection string
8989
services.AddRedisHybridCache("localhost:6379");
9090

91-
// OR with options
92-
services.AddRedisHybridCache("localhost:6379", options =>
93-
{
94-
options.EnableCompression = false; // Disable compression for in-memory optimization (default: false)
95-
options.Version = 1; // Optional: Cache key version (default: null)
96-
options.EnvironmentPrefix = "prod"; // Optional: Environment prefix (default: null)
97-
options.EnableKeyEncoding = true; // URL-encode optional keys (default: true)
98-
options.EnableKeyLengthValidation = true; // Validate key length (default: true)
99-
});
91+
// OR: Share connection with other Redis services (Data Protection, Session, SignalR, etc.)
92+
var multiplexer = ConnectionMultiplexer.Connect("localhost:6379");
93+
services.AddSingleton<IConnectionMultiplexer>(multiplexer);
94+
95+
// Add cache using the shared connection
96+
services.AddRedisHybridCache(multiplexer);
97+
98+
// Use same shared connection for Data Protection Keys
99+
services.AddDataProtection()
100+
.PersistKeysToStackExchangeRedis(multiplexer, "DataProtection-Keys");
100101
}
101102
```
102103

0 commit comments

Comments
 (0)