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; } } }