using Azure.Identity; using Core.Blueprint.Caching.Contracts; using Microsoft.Extensions.Logging; using StackExchange.Redis; using System.Text.Json; namespace Core.Blueprint.Caching { /// /// Redis cache provider for managing cache operations. /// public sealed class RedisCacheProvider : ICacheProvider { private IDatabase _cacheDatabase = null!; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The Redis connection string. /// The logger instance for logging operations. /// Thrown when connection string is null or empty. public RedisCacheProvider(string connectionString, ILogger logger) { if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString), "Redis connection string cannot be null or empty."); _logger = logger; _cacheDatabase = InitializeRedisAsync(connectionString).GetAwaiter().GetResult(); } /// /// Initializes and establishes a connection to Redis using the provided connection string. /// /// The Redis connection string. /// An instance representing the Redis cache database. /// Thrown when the connection to Redis fails. InitializeRedisAsync(string connectionString) { try { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty; ConfigurationOptions configurationOptions; if (environment.Equals("Local", StringComparison.OrdinalIgnoreCase)) { // Use simple local Redis config configurationOptions = ConfigurationOptions.Parse(connectionString); } else { // Use Azure Redis config configurationOptions = await ConfigurationOptions .Parse(connectionString) .ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); } configurationOptions.AbortOnConnectFail = false; var connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions); _logger.LogInformation("Successfully connected to Redis."); return connectionMultiplexer.GetDatabase(); } catch (Exception ex) { _logger.LogError(ex, "Error establishing Redis connection."); throw; } } /// /// Retrieves a cache item by its key. /// /// The type of the cached item. /// The cache key. /// The cached item of type , or default if not found. public async ValueTask GetAsync(string key) { try { var value = await _cacheDatabase.StringGetAsync(key); if (value.IsNullOrEmpty) { _logger.LogInformation($"Cache miss for key: {key}"); return default; } _logger.LogInformation($"Cache hit for key: {key}"); return JsonSerializer.Deserialize(value); } catch (Exception ex) { _logger.LogError(ex, $"Error getting cache item with key {key}"); throw; } } /// /// Sets a cache item with the specified key and value. /// /// The type of the item to cache. /// The cache key. /// The item to cache. /// The optional expiration time for the cache item. public async ValueTask SetAsync(string key, TEntity value, TimeSpan? expiry = null) { try { var json = JsonSerializer.Serialize(value); await _cacheDatabase.StringSetAsync(key, json, expiry); _logger.LogInformation($"Cache item set with key: {key}"); } catch (Exception ex) { _logger.LogError(ex, $"Error setting cache item with key {key}"); throw; } } /// /// Removes a cache item by its key. /// /// The cache key. public async ValueTask RemoveAsync(string key) { try { await _cacheDatabase.KeyDeleteAsync(key); _logger.LogInformation($"Cache item removed with key: {key}"); } catch (Exception ex) { _logger.LogError(ex, $"Error removing cache item with key {key}"); throw; } } /// /// Checks if a cache item exists for the specified key. /// /// The cache key. /// True if the cache item exists; otherwise, false. public async ValueTask ExistsAsync(string key) { try { var exists = await _cacheDatabase.KeyExistsAsync(key); _logger.LogInformation($"Cache item exists check for key: {key} - {exists}"); return exists; } catch (Exception ex) { _logger.LogError(ex, $"Error checking existence of cache item with key {key}"); throw; } } /// /// Refreshes the expiration time of a cache item if it exists. /// /// The cache key. /// The new expiration time for the cache item. public async ValueTask RefreshAsync(string key, TimeSpan? expiry = null) { try { var value = await _cacheDatabase.StringGetAsync(key); if (!value.IsNullOrEmpty) { await _cacheDatabase.StringSetAsync(key, value, expiry); _logger.LogInformation($"Cache item refreshed with key: {key}"); } else { _logger.LogWarning($"Cache item with key: {key} does not exist, cannot refresh"); } } catch (Exception ex) { _logger.LogError(ex, $"Error refreshing cache item with key {key}"); throw; } } } }