Standard Implementation of REN Redis Cache Service

RENRedisCacheService is a service that allows you to handle cache operations in redis. Here is the standard implementation:

using Microsoft.Extensions.Options;
using Ren.Kit.CacheKit.Abstractions;
using StackExchange.Redis;
using System.Text.Json;

namespace Ren.Kit.CacheKit.Services
{
    public class RENRedisCacheService : IRENCacheService
    {
        private readonly IDatabase _cacheDb;
        private readonly IConnectionMultiplexer _connection;
        private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);

        private int _defaultAbsoluteExpirationHours = 12;
        private bool _useDefaultAbsoluteExpirationWhenNull = true;

        private const bool UseEnvelopeOnlyWhenSlidingProvided = true;

        internal sealed record CacheEnvelope<T>(
            T Value,
            DateTimeOffset CreatedAt,
            DateTimeOffset? AbsoluteExpiresAt,
            TimeSpan? SlidingExpiration
        );

        public RENRedisCacheService(IConnectionMultiplexer connection)
            : this(connection, null)
        {
        }

        public RENRedisCacheService(IConnectionMultiplexer connection, IOptions<RENCacheKitOptions>? options = null)
        {
            _connection = connection;
            _cacheDb = _connection.GetDatabase();

            var opt = options?.Value?.CacheConfiguration;

            if (opt is not null)
            {
                _useDefaultAbsoluteExpirationWhenNull =
                    opt.UseDefaultAbsoluteExpirationWhenNull;

                var timeConfig = opt.RedisConfiguration?.TimeConfiguration;
                if (timeConfig is not null)
                {
                    _defaultAbsoluteExpirationHours =
                        timeConfig.AbsoluteExpirationInHours;
                }
            }
        }

        /// <inheritdoc />
        public virtual T? Get<T>(string cacheKey)
        {
            var data = _cacheDb.StringGet(cacheKey);
            if (!data.HasValue) return default;

            var json = (string?)data;
            if (string.IsNullOrWhiteSpace(json)) return default;

            if (TryDeserializeEnvelope(json, out CacheEnvelope<T>? env) && env is not null)
            {
                var now = DateTimeOffset.UtcNow;

                if (env.AbsoluteExpiresAt is not null && env.AbsoluteExpiresAt <= now)
                {
                    _cacheDb.KeyDelete(cacheKey);
                    return default;
                }

                TouchSliding(cacheKey, env, now);
                return env.Value;
            }

            try
            {
                return JsonSerializer.Deserialize<T>(json, _jsonOptions);
            }
            catch
            {
                return default;
            }
        }

        /// <inheritdoc />
        public virtual async Task<T?> GetAsync<T>(string cacheKey, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var data = await _cacheDb.StringGetAsync(cacheKey).ConfigureAwait(false);
            if (!data.HasValue) return default;

            var json = (string?)data;
            if (string.IsNullOrWhiteSpace(json)) return default;

            if (TryDeserializeEnvelope(json, out CacheEnvelope<T>? env) && env is not null)
            {
                var now = DateTimeOffset.UtcNow;

                if (env.AbsoluteExpiresAt is not null && env.AbsoluteExpiresAt <= now)
                {
                    await _cacheDb.KeyDeleteAsync(cacheKey).ConfigureAwait(false);
                    return default;
                }

                await TouchSlidingAsync(cacheKey, env, now).ConfigureAwait(false);
                return env.Value;
            }

            try
            {
                return JsonSerializer.Deserialize<T>(json, _jsonOptions);
            }
            catch
            {
                return default;
            }
        }

        /// <inheritdoc />
        public virtual string? Get(string cacheKey)
        {
            var value = _cacheDb.StringGet(cacheKey);
            return value.HasValue ? value.ToString() : null;
        }

        /// <inheritdoc />
        public virtual async Task<string?> GetAsync(string cacheKey, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var value = await _cacheDb.StringGetAsync(cacheKey).ConfigureAwait(false);
            return value.HasValue ? value.ToString() : null;
        }

        /// <inheritdoc />
        public virtual void Set<T>(string cacheKey, T data, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null)
        {
            var now = DateTimeOffset.UtcNow;

            if (UseEnvelopeOnlyWhenSlidingProvided && slidingExpiration is null)
            {
                var legacyJson = JsonSerializer.Serialize(data, _jsonOptions);
                var ttl = ResolveTtl(absoluteExpiration);
                _ = SetStringCompat(cacheKey, legacyJson, ttl);
                return;
            }

            var abs = ResolveAbsoluteForEnvelope(absoluteExpiration);
            DateTimeOffset? absAt = abs is null ? null : now.Add(abs.Value);

            var env = new CacheEnvelope<T>(data, now, absAt, slidingExpiration);
            var json = JsonSerializer.Serialize(env, _jsonOptions);

            var ttl2 = ComputeEnvelopeTtl(now, absAt, slidingExpiration);
            _ = SetStringCompat(cacheKey, json, ttl2);
        }

        /// <inheritdoc />
        public virtual async Task SetAsync<T>(string cacheKey, T data, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var now = DateTimeOffset.UtcNow;

            if (UseEnvelopeOnlyWhenSlidingProvided && slidingExpiration is null)
            {
                var legacyJson = JsonSerializer.Serialize(data, _jsonOptions);
                var ttl = ResolveTtl(absoluteExpiration);
                _ = await SetStringCompatAsync(cacheKey, legacyJson, ttl).ConfigureAwait(false);
                return;
            }

            var abs = ResolveAbsoluteForEnvelope(absoluteExpiration);
            DateTimeOffset? absAt = abs is null ? null : now.Add(abs.Value);

            var env = new CacheEnvelope<T>(data, now, absAt, slidingExpiration);
            var json = JsonSerializer.Serialize(env, _jsonOptions);

            var ttl2 = ComputeEnvelopeTtl(now, absAt, slidingExpiration);
            _ = await SetStringCompatAsync(cacheKey, json, ttl2).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public virtual void Remove(string cacheKey)
            => _cacheDb.KeyDelete(cacheKey);

        /// <inheritdoc />
        public virtual async Task RemoveAsync(string cacheKey, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            await _cacheDb.KeyDeleteAsync(cacheKey).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public virtual void Clear()
        {
            var server = _connection.GetServer(_connection.GetEndPoints().First());
            server.FlushDatabase(_cacheDb.Database);
        }

        /// <inheritdoc />
        public virtual async Task ClearAsync(CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var server = _connection.GetServer(_connection.GetEndPoints().First());
            await server.FlushDatabaseAsync(_cacheDb.Database).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public virtual async Task<T?> GetOrCreateAsync<T>(
            string cacheKey,
            Func<CancellationToken, Task<T?>> factory,
            TimeSpan? absoluteExpiration = null,
            TimeSpan? slidingExpiration = null,
            CancellationToken cancellationToken = default)
        {
            var cached = await GetAsync<T>(cacheKey, cancellationToken).ConfigureAwait(false);
            if (cached is not null)
                return cached;

            var value = await factory(cancellationToken).ConfigureAwait(false);
            if (value is not null)
                await SetAsync(cacheKey, value, absoluteExpiration, slidingExpiration, cancellationToken).ConfigureAwait(false);

            return value;
        }

        /// <inheritdoc />
        public virtual async Task<bool> ExistsAsync(string cacheKey, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (string.IsNullOrWhiteSpace(cacheKey))
                return false;

            try
            {
                return await _cacheDb.KeyExistsAsync(cacheKey).ConfigureAwait(false);
            }
            catch (RedisTimeoutException)
            {
                return false;
            }
            catch (RedisConnectionException)
            {
                return false;
            }
        }

        /// <inheritdoc />
        public virtual async Task<bool> RefreshAsync(string cacheKey, TimeSpan? absoluteExpiration = null, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (string.IsNullOrWhiteSpace(cacheKey))
                return false;

            var ttl = ResolveTtl(absoluteExpiration);

            try
            {
                if (ttl is null)
                    return true;

                return await _cacheDb.KeyExpireAsync(cacheKey, ttl).ConfigureAwait(false);
            }
            catch (RedisTimeoutException)
            {
                return false;
            }
            catch (RedisConnectionException)
            {
                return false;
            }
        }

        /// <inheritdoc />
        public virtual async Task<long> RemoveManyAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (keys is null) return 0;

            var arr = keys
                .Where(k => !string.IsNullOrWhiteSpace(k))
                .Distinct(StringComparer.Ordinal)
                .Select(k => (RedisKey)k)
                .ToArray();

            if (arr.Length == 0) return 0;

            try
            {
                return await _cacheDb.KeyDeleteAsync(arr).ConfigureAwait(false);
            }
            catch (RedisTimeoutException)
            {
                return 0;
            }
            catch (RedisConnectionException)
            {
                return 0;
            }
        }

        /// <inheritdoc />
        public virtual async Task<long> IncrementAsync(string cacheKey, long by = 1, TimeSpan? absoluteExpiration = null, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (string.IsNullOrWhiteSpace(cacheKey))
                return 0;

            var ttl = ResolveTtl(absoluteExpiration);

            try
            {
                var next = await _cacheDb.StringIncrementAsync(cacheKey, by).ConfigureAwait(false);

                if (ttl is not null)
                    _ = _cacheDb.KeyExpireAsync(cacheKey, ttl);

                return next;
            }
            catch (RedisTimeoutException)
            {
                return 0;
            }
            catch (RedisConnectionException)
            {
                return 0;
            }
        }

        /// <inheritdoc />
        public virtual async Task HashSetAsync<T>(string key, string field, T value, TimeSpan? absoluteExpiration = null)
        {
            var json = JsonSerializer.Serialize(value, _jsonOptions);
            await _cacheDb.HashSetAsync(key, field, json).ConfigureAwait(false);

            var ttl = ResolveTtl(absoluteExpiration);
            if (ttl is not null)
                await _cacheDb.KeyExpireAsync(key, ttl).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public virtual async Task<T?> HashGetAsync<T>(string key, string field)
        {
            var data = await _cacheDb.HashGetAsync(key, field).ConfigureAwait(false);
            if (!data.HasValue) return default;

            try
            {
                return JsonSerializer.Deserialize<T>(data.ToString()!, _jsonOptions);
            }
            catch
            {
                return default;
            }
        }

        /// <inheritdoc />
        public virtual async Task<bool> HashDeleteFieldAsync(string key, string field)
            => await _cacheDb.HashDeleteAsync(key, field).ConfigureAwait(false);

        /// <inheritdoc />
        public virtual async Task<HashSet<string>> HashGetAllFieldsAsync(string key)
        {
            var fields = await _cacheDb.HashKeysAsync(key).ConfigureAwait(false);
            return fields.Select(x => (string)x).ToHashSet(StringComparer.Ordinal);
        }

        /// <inheritdoc />
        public virtual async Task<Dictionary<string, T>> HashGetAllAsync<T>(string key)
        {
            var entries = await _cacheDb.HashGetAllAsync(key).ConfigureAwait(false);
            var dict = new Dictionary<string, T>(entries.Length, StringComparer.Ordinal);

            foreach (var e in entries)
            {
                try
                {
                    var v = JsonSerializer.Deserialize<T>(e.Value.ToString()!, _jsonOptions);
                    if (v is not null)
                        dict[e.Name.ToString()] = v;
                }
                catch { }
            }

            return dict;
        }

        /// <inheritdoc />
        public virtual async Task<Dictionary<string, T?>> HashGetManyAsync<T>(string key, IEnumerable<string> fields, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (string.IsNullOrWhiteSpace(key) || fields is null)
                return new Dictionary<string, T?>(0, StringComparer.Ordinal);

            var arr = fields
                .Where(f => !string.IsNullOrWhiteSpace(f))
                .Distinct(StringComparer.Ordinal)
                .Select(f => (RedisValue)f)
                .ToArray();

            if (arr.Length == 0)
                return new Dictionary<string, T?>(0, StringComparer.Ordinal);

            try
            {
                var values = await _cacheDb.HashGetAsync(key, arr).ConfigureAwait(false);

                var dict = new Dictionary<string, T?>(arr.Length, StringComparer.Ordinal);
                for (int i = 0; i < arr.Length; i++)
                {
                    var field = (string)arr[i]!;
                    var rv = values[i];

                    if (!rv.HasValue)
                    {
                        dict[field] = default;
                        continue;
                    }

                    try
                    {
                        dict[field] = JsonSerializer.Deserialize<T>(rv.ToString()!, _jsonOptions);
                    }
                    catch
                    {
                        dict[field] = default;
                    }
                }

                return dict;
            }
            catch (RedisTimeoutException)
            {
                return arr.ToDictionary(f => (string)f!, _ => default(T?), StringComparer.Ordinal);
            }
            catch (RedisConnectionException)
            {
                return arr.ToDictionary(f => (string)f!, _ => default(T?), StringComparer.Ordinal);
            }
        }

        /// <inheritdoc />
        public virtual async Task HashSetManyAsync<T>(
            string key,
            IReadOnlyCollection<KeyValuePair<string, T>> items,
            TimeSpan? absoluteExpiration = null,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (string.IsNullOrWhiteSpace(key) || items is null || items.Count == 0)
                return;

            var entries = new List<HashEntry>(items.Count);

            foreach (var kv in items)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (string.IsNullOrWhiteSpace(kv.Key)) continue;

                var json = JsonSerializer.Serialize(kv.Value, _jsonOptions);
                entries.Add(new HashEntry(kv.Key, json));
            }

            if (entries.Count == 0)
                return;

            try
            {
                await _cacheDb.HashSetAsync(key, entries.ToArray()).ConfigureAwait(false);

                var ttl = ResolveTtl(absoluteExpiration);
                if (ttl is not null)
                    await _cacheDb.KeyExpireAsync(key, ttl).ConfigureAwait(false);
            }
            catch (RedisTimeoutException) { }
            catch (RedisConnectionException) { }
        }

        /// <inheritdoc />
        public virtual async Task HashSetBytesAsync(string key, string field, byte[] value, TimeSpan? absoluteExpiration = null)
        {
            await _cacheDb.HashSetAsync(key, field, value).ConfigureAwait(false);

            var ttl = ResolveTtl(absoluteExpiration);
            if (ttl is not null)
                await _cacheDb.KeyExpireAsync(key, ttl).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public virtual async Task<byte[]?> HashGetBytesAsync(string key, string field)
        {
            var data = await _cacheDb.HashGetAsync(key, field).ConfigureAwait(false);
            if (!data.HasValue) return null;
            return data;
        }

        /// <inheritdoc />
        public virtual async Task<Dictionary<string, byte[]>> HashGetAllBytesAsync(string key)
        {
            var entries = await _cacheDb.HashGetAllAsync(key).ConfigureAwait(false);
            var dict = new Dictionary<string, byte[]>(entries.Length, StringComparer.Ordinal);

            foreach (var e in entries)
            {
                if (e.Value.HasValue)
                    dict[e.Name.ToString()] = e.Value;
            }

            return dict;
        }

        /// <inheritdoc />
        public virtual byte[]? GetBytes(string cacheKey)
        {
            var data = _cacheDb.StringGet(cacheKey);
            if (!data.HasValue) return null;
            return data;
        }

        /// <inheritdoc />
        public virtual async Task<byte[]?> GetBytesAsync(string cacheKey, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var data = await _cacheDb.StringGetAsync(cacheKey).ConfigureAwait(false);
            if (!data.HasValue) return null;
            return data;
        }

        /// <inheritdoc />
        public virtual void SetBytes(string cacheKey, byte[] data, TimeSpan? absoluteExpiration = null)
        {
            var ttl = ResolveTtl(absoluteExpiration);
            _ = SetBytesCompat(cacheKey, data, ttl);
        }

        /// <inheritdoc />
        public virtual async Task SetBytesAsync(string cacheKey, byte[] data, TimeSpan? absoluteExpiration = null, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var ttl = ResolveTtl(absoluteExpiration);
            _ = await SetBytesCompatAsync(cacheKey, data, ttl).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public virtual async Task<Dictionary<string, byte[]?>> GetBytesManyAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (keys is null)
                return new Dictionary<string, byte[]?>(0, StringComparer.Ordinal);

            var arr = keys
                .Where(k => !string.IsNullOrWhiteSpace(k))
                .Distinct(StringComparer.Ordinal)
                .Select(k => (RedisKey)k)
                .ToArray();

            if (arr.Length == 0)
                return new Dictionary<string, byte[]?>(0, StringComparer.Ordinal);

            try
            {
                var values = await _cacheDb.StringGetAsync(arr).ConfigureAwait(false);

                var dict = new Dictionary<string, byte[]?>(arr.Length, StringComparer.Ordinal);
                for (int i = 0; i < arr.Length; i++)
                {
                    var v = values[i];
                    dict[(string)arr[i]!] = v.HasValue ? (byte[]?)v : null;
                }

                return dict;
            }
            catch (RedisTimeoutException)
            {
                return arr.ToDictionary(k => (string)k!, _ => (byte[]?)null, StringComparer.Ordinal);
            }
            catch (RedisConnectionException)
            {
                return arr.ToDictionary(k => (string)k!, _ => (byte[]?)null, StringComparer.Ordinal);
            }
        }

        /// <inheritdoc />
        public virtual async Task SetBytesManyAsync(IReadOnlyCollection<KeyValuePair<string, byte[]>> items, TimeSpan? absoluteExpiration = null, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (items is null || items.Count == 0)
                return;

            var ttl = ResolveTtl(absoluteExpiration);

            var tasks = new List<Task>(items.Count);

            foreach (var kv in items)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (string.IsNullOrWhiteSpace(kv.Key))
                    continue;

                if (kv.Value is null || kv.Value.Length == 0)
                    continue;

                tasks.Add(SetBytesCompatAsync(kv.Key, kv.Value, ttl));
            }

            try
            {
                await Task.WhenAll(tasks).ConfigureAwait(false);
            }
            catch (RedisTimeoutException) { }
            catch (RedisConnectionException) { }
        }

        private TimeSpan? ResolveTtl(TimeSpan? absoluteExpiration)
        {
            TimeSpan? ttl =
                absoluteExpiration
                ?? (_useDefaultAbsoluteExpirationWhenNull
                    ? TimeSpan.FromHours(_defaultAbsoluteExpirationHours)
                    : (TimeSpan?)null);

            if (ttl.HasValue && ttl.Value <= TimeSpan.Zero)
                ttl = TimeSpan.FromSeconds(1);

            return ttl;
        }

        private TimeSpan? ResolveAbsoluteForEnvelope(TimeSpan? absoluteExpiration)
        {
            if (absoluteExpiration.HasValue)
                return absoluteExpiration.Value;

            if (_useDefaultAbsoluteExpirationWhenNull)
                return TimeSpan.FromHours(_defaultAbsoluteExpirationHours);

            return null;
        }

        private static TimeSpan? ComputeEnvelopeTtl(DateTimeOffset now, DateTimeOffset? absAt, TimeSpan? sliding)
        {
            if (absAt is null && sliding is null)
                return null;

            if (absAt is null)
                return sliding;

            var remaining = absAt.Value - now;
            if (remaining <= TimeSpan.Zero)
                return TimeSpan.FromSeconds(1);

            if (sliding is null)
                return remaining;

            return remaining <= sliding ? remaining : sliding;
        }

        private bool TryDeserializeEnvelope<T>(string json, out CacheEnvelope<T>? envelope)
        {
            envelope = null;

            if (!json.Contains("\"value\"", StringComparison.OrdinalIgnoreCase))
                return false;

            try
            {
                envelope = JsonSerializer.Deserialize<CacheEnvelope<T>>(json, _jsonOptions);
                return envelope is not null;
            }
            catch
            {
                return false;
            }
        }

        private void TouchSliding<T>(string cacheKey, CacheEnvelope<T> env, DateTimeOffset now)
        {
            if (env.SlidingExpiration is null)
                return;

            var newTtl = env.SlidingExpiration.Value;

            if (env.AbsoluteExpiresAt is not null)
            {
                var remaining = env.AbsoluteExpiresAt.Value - now;
                if (remaining <= TimeSpan.Zero)
                {
                    _cacheDb.KeyDelete(cacheKey);
                    return;
                }

                if (remaining < newTtl)
                    newTtl = remaining;
            }

            if (newTtl > TimeSpan.Zero)
                _cacheDb.KeyExpire(cacheKey, newTtl);
        }

        private async Task TouchSlidingAsync<T>(string cacheKey, CacheEnvelope<T> env, DateTimeOffset now)
        {
            if (env.SlidingExpiration is null)
                return;

            var newTtl = env.SlidingExpiration.Value;

            if (env.AbsoluteExpiresAt is not null)
            {
                var remaining = env.AbsoluteExpiresAt.Value - now;
                if (remaining <= TimeSpan.Zero)
                {
                    await _cacheDb.KeyDeleteAsync(cacheKey).ConfigureAwait(false);
                    return;
                }

                if (remaining < newTtl)
                    newTtl = remaining;
            }

            if (newTtl > TimeSpan.Zero)
                await _cacheDb.KeyExpireAsync(cacheKey, newTtl).ConfigureAwait(false);
        }

        private static long ToPxMilliseconds(TimeSpan ttl)
        {
            if (ttl <= TimeSpan.Zero) ttl = TimeSpan.FromSeconds(1);
            var ms = (long)Math.Ceiling(ttl.TotalMilliseconds);
            return ms <= 0 ? 1 : ms;
        }

        private bool SetStringCompat(string key, string value, TimeSpan? ttl)
        {
            if (ttl is null)
                return _cacheDb.StringSet(key, value);

            var ms = ToPxMilliseconds(ttl.Value);
            var result = _cacheDb.Execute("SET", key, value, "PX", ms);
            return result.ToString() == "OK";
        }

        private async Task<bool> SetStringCompatAsync(string key, string value, TimeSpan? ttl)
        {
            if (ttl is null)
                return await _cacheDb.StringSetAsync(key, value).ConfigureAwait(false);

            var ms = ToPxMilliseconds(ttl.Value);
            var result = await _cacheDb.ExecuteAsync("SET", key, value, "PX", ms).ConfigureAwait(false);
            return result.ToString() == "OK";
        }

        private bool SetBytesCompat(string key, byte[] value, TimeSpan? ttl)
        {
            if (ttl is null)
                return _cacheDb.StringSet(key, value);

            var ms = ToPxMilliseconds(ttl.Value);
            var result = _cacheDb.Execute("SET", key, value, "PX", ms);
            return result.ToString() == "OK";
        }

        private async Task<bool> SetBytesCompatAsync(string key, byte[] value, TimeSpan? ttl)
        {
            if (ttl is null)
                return await _cacheDb.StringSetAsync(key, value).ConfigureAwait(false);

            var ms = ToPxMilliseconds(ttl.Value);
            var result = await _cacheDb.ExecuteAsync("SET", key, value, "PX", ms).ConfigureAwait(false);
            return result.ToString() == "OK";
        }
    }
}

Here, you can see all methods are defined as virtual which allows you to override them if necessary according to your needs. To use this pre-defined methods, you need register them in Program.cs file, in addition to that, you need to define IConnectionMultiplexer on Program.cs to allow it to be used in cache service:

Here is the content of RegisterRENCacheAccessHelpers:

You can use IRENCacheService like as follows:

circle-info

This code automatically uses redis cache thanks to our registerations!

Last updated