From ffed92e85c89e27da7378a10364d9fcbeefcb955 Mon Sep 17 00:00:00 2001 From: Efrain Marin Date: Mon, 19 May 2025 10:29:52 -0600 Subject: [PATCH 1/2] feat: updated caching support - feat: Added memory caching support - feat: refactored dependency injection methods --- Core.BluePrint.Packages.sln | 2 +- .../Configuration/RegisterBlueprint.cs | 35 +++++--- ...edisCacheProvider.cs => ICacheProvider.cs} | 4 +- ...s.csproj => Core.Blueprint.Caching.csproj} | 1 + Core.Blueprint.Redis/MemoryCacheProvider.cs | 86 +++++++++++++++++++ Core.Blueprint.Redis/RedisCacheProvider.cs | 5 +- 6 files changed, 116 insertions(+), 17 deletions(-) rename Core.Blueprint.Redis/Contracts/{IRedisCacheProvider.cs => ICacheProvider.cs} (96%) rename Core.Blueprint.Redis/{Core.Blueprint.Redis.csproj => Core.Blueprint.Caching.csproj} (90%) create mode 100644 Core.Blueprint.Redis/MemoryCacheProvider.cs 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; From 398ca3d7b6c30cddcb4da543c745121d72e26ae7 Mon Sep 17 00:00:00 2001 From: Efrain Marin Date: Mon, 19 May 2025 10:32:59 -0600 Subject: [PATCH 2/2] fix: updated namespaces - code cleanup, removed unused usings --- Core.Blueprint.Redis/Adapters/CacheSettings.cs | 8 +------- Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs | 4 ++-- Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs | 8 ++------ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Core.Blueprint.Redis/Adapters/CacheSettings.cs b/Core.Blueprint.Redis/Adapters/CacheSettings.cs index 0d357b4..2be3840 100644 --- a/Core.Blueprint.Redis/Adapters/CacheSettings.cs +++ b/Core.Blueprint.Redis/Adapters/CacheSettings.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Core.Blueprint.Redis +namespace Core.Blueprint.Caching.Adapters { public interface ICacheSettings { diff --git a/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs b/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs index 7ebdcb0..ec42cda 100644 --- a/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs +++ b/Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs @@ -1,10 +1,10 @@ -using Core.Blueprint.Caching; +using Core.Blueprint.Caching.Adapters; using Core.Blueprint.Caching.Contracts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Core.Blueprint.Redis.Configuration +namespace Core.Blueprint.Caching.Configuration { /// /// Provides extension methods for registering Redis-related services in the DI container. diff --git a/Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs b/Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs index f7f53cb..6d9e374 100644 --- a/Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs +++ b/Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace Core.Blueprint.Redis.Helpers +namespace Core.Blueprint.Caching.Helpers { /// /// Helper class for generating consistent and normalized cache keys.