Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

DistributedCacheManager.cs

using System.Collections.Concurrent;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using Nop.Core.Configuration;
using Nop.Core.Infrastructure;

namespace Nop.Core.Caching;

/// 
/// Represents a base distributed cache 
/// 
public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheManager
{
    #region Fields

    /// 
    /// Holds the keys known by this nopCommerce instance
    /// 
    protected readonly ICacheKeyManager _localKeyManager;
    protected readonly IDistributedCache _distributedCache;
    protected readonly IConcurrentCollection _concurrentCollection;

    /// 
    /// Holds ongoing acquisition tasks, used to avoid duplicating work
    /// 
    protected readonly ConcurrentDictionary>> _ongoing = new();

    #endregion

    #region Ctor

    protected DistributedCacheManager(AppSettings appSettings,
        IDistributedCache distributedCache,
        ICacheKeyManager cacheKeyManager,
        IConcurrentCollection concurrentCollection)
        : base(appSettings)
    {
        _distributedCache = distributedCache;
        _localKeyManager = cacheKeyManager;
        _concurrentCollection = concurrentCollection;
    }

    #endregion

    #region Utilities

    /// 
    /// Clear all data on this instance
    /// 
    /// A task that represents the asynchronous operation
    protected virtual void ClearInstanceData()
    {
        _concurrentCollection.Clear();
        _localKeyManager.Clear();
    }

    /// 
    /// Remove items by cache key prefix
    /// 
    /// Cache key prefix
    /// Parameters to create cache key prefix
    /// The removed keys
    protected virtual IEnumerable RemoveByPrefixInstanceData(string prefix, params object[] prefixParameters)
    {
        var keyPrefix = PrepareKeyPrefix(prefix, prefixParameters);
        _concurrentCollection.Prune(keyPrefix, out _);

        return _localKeyManager.RemoveByPrefix(keyPrefix);
    }

    /// 
    /// Prepare cache entry options for the passed key
    /// 
    /// Cache key
    /// Cache entry options
    protected virtual DistributedCacheEntryOptions PrepareEntryOptions(CacheKey key)
    {
        //set expiration time for the passed cache key
        return new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)
        };
    }

    /// 
    /// Add the specified key and object to the local cache
    /// 
    /// Key of cached item
    /// Value for caching
    protected virtual void SetLocal(string key, object value)
    {
        _concurrentCollection.Add(key, value);
        _localKeyManager.AddKey(key);
    }

    /// 
    /// Remove the value with the specified key from the cache
    /// 
    /// Cache key
    protected virtual void RemoveLocal(string key)
    {
        _concurrentCollection.Remove(key);
        _localKeyManager.RemoveKey(key);
    }

    /// 
    /// Try get a cached item. If it's not in the cache yet, then return default object
    /// 
    /// Type of cached item
    /// Cache key
    protected virtual async Task<(bool isSet, T item)> TryGetItemAsync(string key)
    {
        var json = await _distributedCache.GetStringAsync(key);

        return string.IsNullOrEmpty(json)
            ? (false, default)
            : (true, item: JsonConvert.DeserializeObject(json));
    }

    /// 
    /// Remove the value with the specified key from the cache
    /// 
    /// Cache key
    /// Remove from instance
    protected virtual async Task RemoveAsync(string key, bool removeFromInstance = true)
    {
        _ongoing.TryRemove(key, out _);
        await _distributedCache.RemoveAsync(key);

        if (!removeFromInstance)
            return;

        RemoveLocal(key);
    }

    #endregion

    #region Methods

    /// 
    /// Remove the value with the specified key from the cache
    /// 
    /// Cache key
    /// Parameters to create cache key
    /// A task that represents the asynchronous operation
    public async Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
    {
        await RemoveAsync(PrepareKey(cacheKey, cacheKeyParameters).Key);
    }

    /// 
    /// Get a cached item. If it's not in the cache yet, then load and cache it
    /// 
    /// Type of cached item
    /// Cache key
    /// Function to load item if it's not in the cache yet
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the cached value associated with the specified key
    /// 
    public async Task GetAsync(CacheKey key, Func> acquire)
    {
        if (_concurrentCollection.TryGetValue(key.Key, out var data))
            return (T)data;

        var lazy = _ongoing.GetOrAdd(key.Key, _ => new(async () => await acquire(), true));
        var setTask = Task.CompletedTask;

        try
        {
            if (lazy.IsValueCreated)
                return (T)await lazy.Value;

            var (isSet, item) = await TryGetItemAsync(key.Key);
            if (!isSet)
            {
                item = (T)await lazy.Value;

                if (key.CacheTime == 0 || item == null)
                    return item;

                setTask = _distributedCache.SetStringAsync(
                    key.Key,
                    JsonConvert.SerializeObject(item),
                    PrepareEntryOptions(key));
            }

            SetLocal(key.Key, item);

            return item;
        }
        finally
        {
            _ = setTask.ContinueWith(_ => _ongoing.TryRemove(new KeyValuePair>>(key.Key, lazy)));
        }
    }

    /// 
    /// Get a cached item. If it's not in the cache yet, then load and cache it
    /// 
    /// Type of cached item
    /// Cache key
    /// Function to load item if it's not in the cache yet
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the cached value associated with the specified key
    /// 
    public Task GetAsync(CacheKey key, Func acquire)
    {
        return GetAsync(key, () => Task.FromResult(acquire()));
    }

    public async Task GetAsync(CacheKey key, T defaultValue = default)
    {
        var value = await _distributedCache.GetStringAsync(key.Key);

        return value != null
            ? JsonConvert.DeserializeObject(value)
            : defaultValue;
    }

    /// 
    /// Get a cached item as an  instance, or null on a cache miss.
    /// 
    /// Cache key
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the cached value associated with the specified key, or null if none was found
    /// 
    public async Task GetAsync(CacheKey key)
    {
        return await GetAsync(key);
    }

    /// 
    /// Add the specified key and object to the cache
    /// 
    /// Key of cached item
    /// Value for caching
    /// A task that represents the asynchronous operation
    public async Task SetAsync(CacheKey key, T data)
    {
        if (data == null || (key?.CacheTime ?? 0) <= 0)
            return;

        var lazy = new Lazy>(() => Task.FromResult(data as object), true);

        try
        {
            _ongoing.TryAdd(key.Key, lazy);
            // await the lazy task in order to force value creation instead of directly setting data
            // this way, other cache manager instances can access it while it is being set
            SetLocal(key.Key, await lazy.Value);
            await _distributedCache.SetStringAsync(key.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));
        }
        finally
        {
            _ongoing.TryRemove(new KeyValuePair>>(key.Key, lazy));
        }
    }

    /// 
    /// Remove items by cache key prefix
    /// 
    /// Cache key prefix
    /// Parameters to create cache key prefix
    /// A task that represents the asynchronous operation
    public abstract Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters);

    /// 
    /// Clear all cache data
    /// 
    /// A task that represents the asynchronous operation
    public abstract Task ClearAsync();

    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }

    #endregion
}