using Microsoft.Extensions.Caching.Memory; using System.Collections.Concurrent; using System.Text.RegularExpressions; namespace BpsRwApp.Services; /// /// Implementation of cache service dengan enhanced functionality /// public class CacheService : ICacheService { private readonly IMemoryCache _cache; private readonly ILogger _logger; private readonly ConcurrentDictionary _cacheKeys; public CacheService(IMemoryCache cache, ILogger logger) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _cacheKeys = new ConcurrentDictionary(); } /// public async Task GetOrSetAsync(string key, Func> provider, TimeSpan expiration, CacheItemPriority priority = CacheItemPriority.Normal) { if (_cache.TryGetValue(key, out T? cachedValue)) { _logger.LogDebug("Cache hit for key: {CacheKey}", key); return cachedValue!; } _logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", key); var data = await provider(); Set(key, data, expiration, priority); return data; } /// public async Task GetOrSetWithSlidingAsync(string key, Func> provider, TimeSpan slidingExpiration, TimeSpan absoluteExpiration, CacheItemPriority priority = CacheItemPriority.Normal) { if (_cache.TryGetValue(key, out T? cachedValue)) { _logger.LogDebug("Cache hit for key: {CacheKey}", key); return cachedValue!; } _logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", key); var data = await provider(); var cacheOptions = new MemoryCacheEntryOptions { SlidingExpiration = slidingExpiration, AbsoluteExpirationRelativeToNow = absoluteExpiration, Priority = priority, PostEvictionCallbacks = { new PostEvictionCallbackRegistration { EvictionCallback = (key, value, reason, state) => { _cacheKeys.TryRemove(key.ToString()!, out _); _logger.LogDebug("Cache entry evicted: {Key}, Reason: {Reason}", key, reason); } }} }; _cache.Set(key, data, cacheOptions); _cacheKeys.TryAdd(key, true); _logger.LogDebug("Cached data with sliding expiration for key: {CacheKey}", key); return data; } /// public T? Get(string key) { if (_cache.TryGetValue(key, out T? value)) { _logger.LogDebug("Retrieved cached value for key: {CacheKey}", key); return value; } _logger.LogDebug("Cache miss for key: {CacheKey}", key); return default; } /// public void Set(string key, T value, TimeSpan expiration, CacheItemPriority priority = CacheItemPriority.Normal) { var cacheOptions = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiration, Priority = priority, PostEvictionCallbacks = { new PostEvictionCallbackRegistration { EvictionCallback = (key, value, reason, state) => { _cacheKeys.TryRemove(key.ToString()!, out _); _logger.LogDebug("Cache entry evicted: {Key}, Reason: {Reason}", key, reason); } }} }; _cache.Set(key, value, cacheOptions); _cacheKeys.TryAdd(key, true); _logger.LogDebug("Cached value for key: {CacheKey}, Expiration: {Expiration}", key, expiration); } /// public void Remove(string key) { _cache.Remove(key); _cacheKeys.TryRemove(key, out _); _logger.LogDebug("Removed cache entry for key: {CacheKey}", key); } /// public void RemoveByPattern(string pattern) { var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); var keysToRemove = _cacheKeys.Keys.Where(key => regex.IsMatch(key)).ToList(); foreach (var key in keysToRemove) { Remove(key); } _logger.LogDebug("Removed {Count} cache entries matching pattern: {Pattern}", keysToRemove.Count, pattern); } /// public bool Exists(string key) { return _cacheKeys.ContainsKey(key); } }