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:
public class RENRedisCacheService : IRENCacheService
{
private IDatabase _cacheDb;
private ConnectionMultiplexer _connection;
private int _defaultAbsoluteExpirationHours;
protected RENRedisCacheService(IConnectionMultiplexer connection, IConfiguration configuration)
{
_connection = connection;
_cacheDb = _connection.GetDatabase();
SetDefaults(configuration);
}
/// <summary>
/// Retrieves data of type <typeparamref name="T"/> from the cache.
/// </summary>
/// <typeparam name="T">The type of data to retrieve.</typeparam>
/// <param name="cacheKey">The key associated with the cached data.</param>
/// <returns>The cached T value.</returns>
public virtual T Get<T>(string cacheKey)
{
var data = _cacheDb.StringGet(cacheKey);
return data.HasValue ? JsonConvert.DeserializeObject<T>(data) : default;
}
/// <summary>
/// Asynchronously retrieves data of type <typeparamref name="T"/> from the cache.
/// </summary>
/// <typeparam name="T">The type of data to retrieve.</typeparam>
/// <param name="cacheKey">The key associated with the cached data.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
/// <returns>A task representing the cached T value.</returns>
public virtual async Task<T> GetAsync<T>(string cacheKey, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var data = await _cacheDb.StringGetAsync(cacheKey);
return data.HasValue ? JsonConvert.DeserializeObject<T>(data) : default;
}
/// <summary>
/// Retrieves a string value from the cache.
/// </summary>
/// <param name="cacheKey">The key associated with the cached string.</param>
/// <returns>The cached string value.</returns>
public virtual string Get(string cacheKey)
{
return _cacheDb.StringGet(cacheKey);
}
/// <summary>
/// Asynchronously retrieves a string value from the cache.
/// </summary>
/// <param name="cacheKey">The key associated with the cached string.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
/// <returns>A task representing the cached string value.</returns>
public virtual async Task<string> GetAsync(string cacheKey, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await _cacheDb.StringGetAsync(cacheKey);
}
/// <summary>
/// Retrieves a list of data of type <typeparamref name="T"/> that matches the specified pattern.
/// </summary>
/// <typeparam name="T">The type of data to retrieve.</typeparam>
/// <param name="pattern">The pattern to match keys in the cache.</param>
/// <returns>A list of cached data items typed <typeparamref name="T"/>.</returns>
public virtual List<T> GetByPattern<T>(string pattern = "*")
{
var server = _connection.GetServer(_connection.GetEndPoints().First());
var keys = server.Keys(pattern: pattern).Select(key => (string)key);
return keys.Select(Get<T>).ToList();
}
/// <summary>
/// Asynchronously retrieves a list of data of type <typeparamref name="T"/> that matches the specified pattern.
/// </summary>
/// <typeparam name="T">The type of data to retrieve.</typeparam>
/// <param name="pattern">The pattern to match keys in the cache.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
/// <returns>A task representing a list of cached data items typed <typeparamref name="T"/>.</returns>
public virtual Task<List<T>> GetByPatternAsync<T>(string pattern = "*", CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.Factory.StartNew(() => GetByPattern<T>(pattern), cancellationToken);
}
/// <summary>
/// Retrieves a list of string keys that match the specified pattern in the cache.
/// </summary>
/// <param name="pattern">The pattern to match keys in the cache.</param>
/// <returns>A list of cached string keys.</returns>
public virtual List<string> GetByPattern(string pattern = "*")
{
var server = _connection.GetServer(_connection.GetEndPoints().First());
return server.Keys(pattern: pattern).Select(key => (string)key).ToList();
}
/// <summary>
/// Asynchronously retrieves a list of string keys that match the specified pattern in the cache.
/// </summary>
/// <param name="pattern">The pattern to match keys in the cache.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
/// <returns>A task representing a list of cached string keys.</returns>
public virtual Task<List<string>> GetByPatternAsync(string pattern = "*", CancellationToken cancellationToken = default)
{
return Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
var server = _connection.GetServer(_connection.GetEndPoints().First());
return server.Keys(pattern: pattern).Select(key => (string)key).ToList();
}, cancellationToken);
}
/// <summary>
/// Sets data of type <typeparamref name="T"/> in the cache with optional expiration settings.
/// </summary>
/// <typeparam name="T">The type of data to store in the cache.</typeparam>
/// <param name="cacheKey">The key for storing the data in the cache.</param>
/// <param name="data">The data to cache of type <typeparamref name="T"/>.</param>
/// <param name="absoluteExpiration">The optional absolute expiration time for the cached data.</param>
/// <param name="slidingExpiration">The optional sliding expiration time for the cached data.</param>
public virtual void Set<T>(string cacheKey, T data, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null)
{
var expiration = GetAbsoluteExpiration(absoluteExpiration);
_cacheDb.StringSet(cacheKey, JsonConvert.SerializeObject(data), expiration);
}
/// <summary>
/// Asynchronously sets data of type <typeparamref name="T"/> in the cache with optional expiration settings.
/// </summary>
/// <typeparam name="T">The type of data to store in the cache.</typeparam>
/// <param name="cacheKey">The key for storing the data in the cache.</param>
/// <param name="data">The data to cache of type <typeparamref name="T"/>.</param>
/// <param name="absoluteExpiration">The optional absolute expiration time for the cached data.</param>
/// <param name="slidingExpiration">The optional sliding expiration time for the cached data.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
public virtual async Task SetAsync<T>(string cacheKey, T data, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var expiration = GetAbsoluteExpiration(absoluteExpiration);
await _cacheDb.StringSetAsync(cacheKey, JsonConvert.SerializeObject(data), expiration);
}
/// <summary>
/// Deletes cache values by key pattern.
/// </summary>
/// <param name="pattern">The pattern for cache keys to be deleted.</param>
public virtual void DeleteKeysByPattern(string pattern = "*")
{
var server = _connection.GetServer(_connection.GetEndPoints().First());
var keys = server.Keys(pattern: pattern);
foreach (var key in keys) Remove((string)key);
}
/// <summary>
/// Asynchronously deletes cache values by key pattern.
/// </summary>
/// <param name="pattern">The pattern for cache keys to be deleted.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
public virtual async Task DeleteKeysByPatternAsync(string pattern = "*", CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var server = _connection.GetServer(_connection.GetEndPoints().First());
var keys = server.Keys(pattern: pattern);
foreach (var key in keys) await RemoveAsync((string)key, cancellationToken);
}
/// <summary>
/// Removes cache value by key.
/// </summary>
/// <param name="cacheKey">The key to be deleted.</param>
public virtual void Remove(string cacheKey)
{
_cacheDb.KeyDelete(cacheKey);
}
/// <summary>
/// Asynchronously removes cache value by key.
/// </summary>
/// <param name="cacheKey">The key to be deleted.</param>
/// <param name="cancellationToken">The optional cancellation token.</param>
public virtual async Task RemoveAsync(string cacheKey, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await _cacheDb.KeyDeleteAsync(cacheKey);
}
/// <summary>
/// Clears the database.
/// </summary>
public virtual void Clear()
{
var server = _connection.GetServer(_connection.GetEndPoints().First());
server.FlushDatabase();
}
/// <summary>
/// Asynchronously clears the database.
/// </summary>
/// <param name="cancellationToken">The optional cancellation token.</param>
public virtual async Task ClearAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var server = _connection.GetServer(_connection.GetEndPoints().First());
await server.FlushDatabaseAsync();
}
private TimeSpan GetAbsoluteExpiration(TimeSpan? absoluteExpiration = null)
{
return absoluteExpiration ?? TimeSpan.FromHours(_defaultAbsoluteExpirationHours);
}
private void SetDefaults(IConfiguration configuration)
{
_defaultAbsoluteExpirationHours = int.Parse(configuration.GetSection("CacheConfiguration:RedisConfiguraton:TimeConfiguration:AbsoluteExpirationInHours")?.Value ?? "12");
}
}
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:
builder.Services.AddSingleton<IConnectionMultiplexer>(provider =>
{
var configuration = provider.GetRequiredService<IConfiguration>();
var redisOptions = new ConfigurationOptions
{
EndPoints = { configuration.GetSection("CacheConfiguration:RedisConfiguration:Url")?.Value },
DefaultDatabase = int.Parse(configuration.GetSection("CacheConfiguration:RedisConfiguration:DatabaseId")?.Value ?? "0"),
AbortOnConnectFail = bool.Parse(configuration.GetSection("CacheConfiguration:RedisConfiguration:AbortOnConnectFail")?.Value),
User = configuration.GetSection("CacheConfiguration:RedisConfiguration:Username")?.Value,
Password = configuration.GetSection("CacheConfiguration:RedisConfiguration:Password")?.Value
};
return ConnectionMultiplexer.Connect(redisOptions);
});
builder.Services.RegisterRENCacheAccessHelpers<RENRedisCacheService>();
Here is the content of RegisterRENCacheAccessHelpers:
public static IServiceCollection RegisterRENCacheAccessHelpers<T>(this IServiceCollection services) where T: ICacheService
{
services.AddScoped(typeof(ICacheService), typeof(T));
return services;
}
You can use IRENCacheService like as follows:
public class HomeController : ControllerBase
{
private readonly IRENRepository<User> _userRepository;
private readonly IRENCacheService _cacheService;
public HomeController(IRENUnitOfWork<RENDbContext> uow, IRENCacheService cacheService)
{
_userRepository = uow.GetRENRepository<User>();
_cacheService = cacheService;
}
[HttpGet, Route("Index")]
public async Task<IActionResult> Index()
{
var cacheKey = "users";
var users = await _cacheService.GetAsync<List<User>>(cacheKey);
if (users != null) return Ok(users);
users = await _userRepository.GetListAsync();
await _cacheService.SetAsync(cacheKey, users);
return Ok(users);
}
}
Last updated