diff --git a/Core.BluePrint.Packages.sln b/Core.BluePrint.Packages.sln index 7a136ec..0c6eb67 100644 --- a/Core.BluePrint.Packages.sln +++ b/Core.BluePrint.Packages.sln @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Blueprint.KeyVault", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Mongo", "Core.Blueprint.Mongo\Core.Blueprint.Mongo.csproj", "{27A8E3E1-D613-4D5B-8105-485699409F1E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Redis", "Core.Blueprint.Redis\Core.Blueprint.Redis.csproj", "{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Caching", "Core.Blueprint.Redis\Core.Blueprint.Caching.csproj", "{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Storage", "Core.Blueprint.Storage\Core.Blueprint.Storage.csproj", "{636E4520-79F9-46C8-990D-08F2D24A151C}" EndProject diff --git a/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs b/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs index 3aeb596..7ebdcb0 100644 --- a/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs +++ b/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.Configuration; +using Core.Blueprint.Caching; +using Core.Blueprint.Caching.Contracts; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -17,23 +19,32 @@ namespace Core.Blueprint.Redis.Configuration /// The updated service collection. public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration) { - // Retrieve the Redis connection string from the configuration. - // Get Redis configuration section - var redisConnectionString = configuration.GetSection("ConnectionStrings:Redis").Value; - if (string.IsNullOrEmpty(redisConnectionString)) + // TODO for the following variable we'll need to add in the appsettings.json the following config: "UseRedisCache": true, + bool useRedis = configuration.GetValue("UseRedisCache"); + //TODO decide wheter to use appsettings or the following ENV variable + useRedis = Environment.GetEnvironmentVariable("CORE_BLUEPRINT_PACKAGES_USE_REDIS")?.ToLower() == "true"; + + if (useRedis) { - throw new InvalidOperationException("Redis connection is not configured."); + var redisConnectionString = configuration.GetSection("ConnectionStrings:Redis").Value; + if (string.IsNullOrEmpty(redisConnectionString)) + { + throw new InvalidOperationException("Redis connection is not configured."); + } + + services.AddSingleton(provider => + new RedisCacheProvider(redisConnectionString, provider.GetRequiredService>())); + } + else + { + services.AddMemoryCache(); + services.AddSingleton(); } - // Register RedisCacheProvider - services.AddSingleton(provider => - new RedisCacheProvider(redisConnectionString, provider.GetRequiredService>())); - - // Get CacheSettings and register with the ICacheSettings interface var cacheSettings = configuration.GetSection("CacheSettings").Get(); if (cacheSettings == null) { - throw new InvalidOperationException("Redis CacheSettings section is not configured."); + throw new InvalidOperationException("CacheSettings section is not configured."); } services.AddSingleton(cacheSettings); diff --git a/Core.Blueprint.Redis/Contracts/IRedisCacheProvider.cs b/Core.Blueprint.Redis/Contracts/ICacheProvider.cs similarity index 96% rename from Core.Blueprint.Redis/Contracts/IRedisCacheProvider.cs rename to Core.Blueprint.Redis/Contracts/ICacheProvider.cs index f9a7b5e..cf0a323 100644 --- a/Core.Blueprint.Redis/Contracts/IRedisCacheProvider.cs +++ b/Core.Blueprint.Redis/Contracts/ICacheProvider.cs @@ -1,9 +1,9 @@ -namespace Core.Blueprint.Redis +namespace Core.Blueprint.Caching.Contracts { /// /// Interface for managing Redis cache operations. /// - public interface IRedisCacheProvider + public interface ICacheProvider { /// /// Retrieves a cache item by its key. diff --git a/Core.Blueprint.Redis/Core.Blueprint.Redis.csproj b/Core.Blueprint.Redis/Core.Blueprint.Caching.csproj similarity index 90% rename from Core.Blueprint.Redis/Core.Blueprint.Redis.csproj rename to Core.Blueprint.Redis/Core.Blueprint.Caching.csproj index e1322ba..8d17004 100644 --- a/Core.Blueprint.Redis/Core.Blueprint.Redis.csproj +++ b/Core.Blueprint.Redis/Core.Blueprint.Caching.csproj @@ -8,6 +8,7 @@ + diff --git a/Core.Blueprint.Redis/MemoryCacheProvider.cs b/Core.Blueprint.Redis/MemoryCacheProvider.cs new file mode 100644 index 0000000..91beb2a --- /dev/null +++ b/Core.Blueprint.Redis/MemoryCacheProvider.cs @@ -0,0 +1,86 @@ +using Core.Blueprint.Caching.Contracts; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Caching.Memory; +using System.Text.Json; + +namespace Core.Blueprint.Caching +{ + public sealed class MemoryCacheProvider : ICacheProvider + { + private readonly IMemoryCache _cache; + private readonly ILogger _logger; + public MemoryCacheProvider(IMemoryCache cache, ILogger logger) + { + _cache = cache; + _logger = logger; + } + + public ValueTask GetAsync(string key) + { + if (_cache.TryGetValue(key, out var value)) + { + if (value is TEntity typedValue) + { + return ValueTask.FromResult(typedValue); + } + + try + { + var json = value?.ToString(); + var deserialized = JsonSerializer.Deserialize(json); + return ValueTask.FromResult(deserialized); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error deserializing cache value for key {Key}", key); + } + } + + return ValueTask.FromResult(default(TEntity)); + } + + public ValueTask SetAsync(string key, TEntity value, TimeSpan? expiry = null) + { + var options = new MemoryCacheEntryOptions(); + if (expiry.HasValue) + { + options.SetAbsoluteExpiration(expiry.Value); + } + + _cache.Set(key, value, options); + return ValueTask.CompletedTask; + } + + public ValueTask RemoveAsync(string key) + { + _cache.Remove(key); + return ValueTask.CompletedTask; + } + + public ValueTask ExistsAsync(string key) + { + return ValueTask.FromResult(_cache.TryGetValue(key, out _)); + } + + public ValueTask RefreshAsync(string key, TimeSpan? expiry = null) + { + // MemoryCache does not support sliding expiration refresh like Redis, + // so we must re-set the value manually if required. + + if (_cache.TryGetValue(key, out var value)) + { + _cache.Remove(key); + + var options = new MemoryCacheEntryOptions(); + if (expiry.HasValue) + { + options.SetAbsoluteExpiration(expiry.Value); + } + + _cache.Set(key, value, options); + } + + return ValueTask.CompletedTask; + } + } +} diff --git a/Core.Blueprint.Redis/RedisCacheProvider.cs b/Core.Blueprint.Redis/RedisCacheProvider.cs index 525e310..7715f44 100644 --- a/Core.Blueprint.Redis/RedisCacheProvider.cs +++ b/Core.Blueprint.Redis/RedisCacheProvider.cs @@ -1,14 +1,15 @@ using Azure.Identity; +using Core.Blueprint.Caching.Contracts; using Microsoft.Extensions.Logging; using StackExchange.Redis; using System.Text.Json; -namespace Core.Blueprint.Redis +namespace Core.Blueprint.Caching { /// /// Redis cache provider for managing cache operations. /// - public sealed class RedisCacheProvider : IRedisCacheProvider + public sealed class RedisCacheProvider : ICacheProvider { private IDatabase _cacheDatabase = null!; private readonly ILogger _logger;