diff --git a/Cnblogs.Architecture.sln b/Cnblogs.Architecture.sln
index 2826a7b..520af13 100644
--- a/Cnblogs.Architecture.sln
+++ b/Cnblogs.Architecture.sln
@@ -62,6 +62,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.In
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss", "src\Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss\Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss.csproj", "{9C76E136-1D79-408C-A17F-FD63632B00A9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis", "src\Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis\Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj", "{1FF58B65-6C83-4F0C-909A-6606B4C754B8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -94,6 +96,7 @@ Global
{73665E32-3D10-4F71-B893-4C65F36332D0} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
{9C76E136-1D79-408C-A17F-FD63632B00A9} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
+ {1FF58B65-6C83-4F0C-909A-6606B4C754B8} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{54D9D850-1CFC-485E-97FE-87F41C220523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -200,5 +203,9 @@ Global
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj
new file mode 100644
index 0000000..245d038
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+ Provides remote cache provider that implemented with Redis
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Injectors.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Injectors.cs
new file mode 100644
index 0000000..7b405cb
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Injectors.cs
@@ -0,0 +1,80 @@
+using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
+using Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection;
+using Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using StackExchange.Redis;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection;
+
+///
+/// Injectors for redis cache provider.
+///
+public static class Injectors
+{
+ ///
+ /// Add redis cache as remote cache.
+ ///
+ /// The injector.
+ /// The root configuration.
+ /// The configuration section name for redis, defaults to Redis.
+ /// The optional configuration.
+ ///
+ public static CqrsInjector AddRedisCache(
+ this CqrsInjector injector,
+ IConfiguration configuration,
+ string sectionName = "Redis",
+ Action? configure = null)
+ {
+ return AddRedisCache(injector, configuration.GetSection(sectionName), configure);
+ }
+
+ ///
+ /// Add redis cache as remote cache.
+ ///
+ /// The injector.
+ /// The configuration section for redis.
+ /// The optional configuration.
+ ///
+ public static CqrsInjector AddRedisCache(
+ this CqrsInjector injector,
+ IConfigurationSection section,
+ Action? configure = null)
+ {
+ injector.Services.Configure(section);
+ return AddRedisCache(injector, configure);
+ }
+
+ ///
+ /// Add redis cache as remote cache.
+ ///
+ /// The injector.
+ /// The connection string.
+ /// Optional configuration for redis options.
+ /// The configure for cacheable request options.
+ ///
+ public static CqrsInjector AddRedisCache(
+ this CqrsInjector injector,
+ string connectionString,
+ Action? redisConfigure = null,
+ Action? configure = null)
+ {
+ var options = ConfigurationOptions.Parse(connectionString, true);
+ injector.Services.Configure(o =>
+ {
+ o.Configure = options;
+ redisConfigure?.Invoke(o);
+ });
+ return AddRedisCache(injector, configure);
+ }
+
+ private static CqrsInjector AddRedisCache(
+ this CqrsInjector injector,
+ Action? configure = null)
+ {
+ injector.Services.AddSingleton(
+ sp => ConnectionMultiplexer.Connect(sp.GetRequiredService>().Value.Configure));
+ return injector.AddRemoteQueryCache(configure);
+ }
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisCacheProvider.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisCacheProvider.cs
new file mode 100644
index 0000000..d17eae5
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisCacheProvider.cs
@@ -0,0 +1,84 @@
+using System.Text.Json;
+using Cnblogs.Architecture.Ddd.Domain.Abstractions;
+using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
+using Microsoft.Extensions.Options;
+using StackExchange.Redis;
+
+namespace Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis;
+
+///
+/// Remote cache provider implemented with Redis.
+///
+public class RedisCacheProvider
+ : IRemoteCacheProvider
+{
+ private readonly RedisOptions _options;
+ private readonly IDatabaseAsync _database;
+ private readonly IDateTimeProvider _dateTimeProvider;
+
+ ///
+ /// Remote cache provider implemented with Redis.
+ ///
+ /// The underlying multiplexer.
+ /// The options for this provider.
+ /// The datetime provider.
+ public RedisCacheProvider(
+ ConnectionMultiplexer multiplexer,
+ IOptions options,
+ IDateTimeProvider dateTimeProvider)
+ {
+ _dateTimeProvider = dateTimeProvider;
+ _options = options.Value;
+ _database = multiplexer.GetDatabase(_options.Database);
+ }
+
+ ///
+ public Task AddAsync(string cacheKey, TResult value)
+ {
+ return _database.StringSetAsync(GetCacheKey(cacheKey), Serialize(value));
+ }
+
+ ///
+ public Task AddAsync(string cacheKey, TimeSpan expires, TResult value)
+ {
+ return _database.StringSetAsync(GetCacheKey(cacheKey), Serialize(value), expires);
+ }
+
+ ///
+ public async Task?> GetAsync(string cacheKey)
+ {
+ var json = await _database.StringGetAsync(GetCacheKey(cacheKey));
+ if (json.IsNullOrEmpty)
+ {
+ return null;
+ }
+
+ return DeSerialize(json!);
+ }
+
+ ///
+ public Task RemoveAsync(string cacheKey)
+ {
+ return _database.KeyDeleteAsync(GetCacheKey(cacheKey));
+ }
+
+ ///
+ public Task UpdateAsync(string cacheKey, TResult value)
+ {
+ return AddAsync(cacheKey, value);
+ }
+
+ ///
+ public Task UpdateAsync(string cacheKey, TResult value, TimeSpan expires)
+ {
+ return AddAsync(cacheKey, expires, value);
+ }
+
+ private string GetCacheKey(string key) => $"{_options.Prefix}{key}";
+
+ private string Serialize(TResult result)
+ => JsonSerializer.Serialize(new CacheEntry(result, _dateTimeProvider.UnixSeconds()));
+
+ private static CacheEntry? DeSerialize(string json)
+ => JsonSerializer.Deserialize>(json);
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisOptions.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisOptions.cs
new file mode 100644
index 0000000..e021af3
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisOptions.cs
@@ -0,0 +1,24 @@
+using StackExchange.Redis;
+
+namespace Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis;
+
+///
+/// Options for redis connection.
+///
+public class RedisOptions
+{
+ ///
+ /// Prefix for all redis keys.
+ ///
+ public string Prefix { get; set; } = "cache_";
+
+ ///
+ /// The number of database to use.
+ ///
+ public int Database { get; set; } = -1;
+
+ ///
+ /// The redis configuration, https://stackexchange.github.io/StackExchange.Redis/Configuration
+ ///
+ public ConfigurationOptions Configure { get; set; } = new();
+}