using OnlineSalesAutoCrop.CoreAPI.Models; using OnlineSalesAutoCrop.CoreAPI.Models.Global; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Newtonsoft.Json; using StackExchange.Redis; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; namespace OnlineSalesAutoCrop.CoreAPI { /// /// Customized cache, it may be "Distributed Cache (Redis)" or "Memory Cache", /// it depends on value in appsettings.json file. /// If the value of "RedisCacheUrl" field is present it will use "Distributed Cache (Redis)." /// If the value of "SqlServerCacheUrl" field is present it will use "Distributed Cache (Sql Server)" /// otherwise it will use "Memory Cache". /// public interface IEaseCache : IEnumerable> { /// /// Get cached object /// /// Any type of object /// Key to retrieve data /// 2nd part of Key to retrieve data /// Instance of an object type T /// True if successful otherwise false bool TryGetValue(string key, string key2, out T value); /// /// Set object to cache /// /// Any type of object /// Key to set data to cache /// 2nd part of Key to retrieve data /// Instance of an object type T /// How many time to keep in cache /// True if successful otherwise false bool Set(string key, string key2, T value, DateTimeOffset options); /// /// Delete caches of matched pattern /// /// Pattern to search void Clear(string pattern); } /// /// /// public class EaseCache : IEaseCache { private readonly IDatabase _db; private readonly IServer _server; private readonly EnumCacheType _cacheType; private readonly IMemoryCache _memoryCache; private readonly IDistributedCache _distributedCache; private readonly ConcurrentBag _distributedKeys = []; private readonly ConcurrentDictionary _cacheEntries = new(); /// /// /// /// /// /// public EaseCache(IMemoryCache memoryCache, IDistributedCache distributedCache, IOptions settings) { CacheConnection cacheCon = new(settings: settings.Value); _cacheType = cacheCon.CacheType; if (_cacheType == EnumCacheType.Redis) { _db = cacheCon.Connection.GetDatabase(); foreach (EndPoint endpoint in cacheCon.Connection.GetEndPoints(configuredOnly: true)) { _server = cacheCon.Connection.GetServer(endpoint); } } else if (_cacheType == EnumCacheType.SqlServer) { _distributedCache = distributedCache; } else { _memoryCache = memoryCache; } } /// /// /// /// /// /// /// /// /// public bool Set(string key, string key2, T value, DateTimeOffset options) { if (!string.IsNullOrEmpty(key2)) key = $"{key}~{key2}"; if (_cacheType == EnumCacheType.Redis) { TimeSpan expiryTime = options.DateTime.Subtract(DateTime.Now); return _db.StringSet(key: key, value: JsonConvert.SerializeObject(value), expiry: expiryTime); } else if (_cacheType == EnumCacheType.SqlServer) { _distributedKeys.Add(key); DistributedCacheEntryOptions expiryTime = new() { SlidingExpiration = options.DateTime.Subtract(DateTime.Now) }; _distributedCache.SetString(key: key, value: JsonConvert.SerializeObject(value), options: expiryTime); return true; } else { using ICacheEntry entry = _memoryCache.CreateEntry(key); entry.RegisterPostEvictionCallback(PostEvictionCallback); _cacheEntries.AddOrUpdate(key: key, addValue: entry, (o, cacheEntry) => { cacheEntry.Value = entry; return cacheEntry; }); entry.AbsoluteExpiration = options; entry.Value = value; return true; } } /// /// /// /// /// /// /// private void PostEvictionCallback(object key, object value, EvictionReason reason, object state) { if (_cacheType == EnumCacheType.InMemory && reason != EvictionReason.Replaced) { _cacheEntries.TryRemove(key: $"{key}", value: out _); } } /// /// /// /// /// /// /// /// public bool TryGetValue(string key, string key2, out T value) { if (!string.IsNullOrEmpty(key2)) key = $"{key}~{key2}"; if (_cacheType == EnumCacheType.Redis) { RedisValue keyValue = _db.StringGet(key); if (string.IsNullOrEmpty(keyValue)) { value = default; return false; } value = JsonConvert.DeserializeObject(keyValue); return true; } else if (_cacheType == EnumCacheType.SqlServer) { string keyValue = _distributedCache.GetString(key); if (string.IsNullOrEmpty(keyValue)) { value = default; return false; } value = JsonConvert.DeserializeObject(keyValue); return true; } else { return _memoryCache.TryGetValue(key: key, value: out value); } } /// /// Delete caches of matched pattern /// /// Pattern to search public void Clear(string pattern) { if (_cacheType == EnumCacheType.Redis) { foreach (RedisKey key in _server.Keys(pattern: $"*{pattern}*")) { _db.KeyDelete(key: key); } } else if (_cacheType == EnumCacheType.SqlServer) { foreach (string key in _distributedKeys.Where(key => key.Contains(pattern, StringComparison.OrdinalIgnoreCase))) { _distributedCache.Remove(key: key); } } else { foreach (string key in _cacheEntries.Keys.Where(key => key.Contains(pattern, StringComparison.CurrentCultureIgnoreCase))) { _memoryCache.Remove(key: key); } } } /// /// /// /// public IEnumerator> GetEnumerator() => _cacheEntries.Select(pair => new KeyValuePair(pair.Key, pair.Value.Value)).GetEnumerator(); /// /// /// /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }