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