Redis cache is out process cache provider for a distributed environment. It is popular in Azure Cloud solution, but it also has a standalone application to operate upon in case of small enterprises application.
How to install Redis Cache on a local machine?
Redis can be used as a local cache server too on our local machines.
At first install, Chocolatey https://chocolatey.org/, to make installation of Redis easy. Also, the version under Chocolatey supports more commands and compatible with Official Cache package from Microsoft.
After Chocolatey installation hit choco install redis-64.
Once the installation is done, we can start the server by running redis-server.
Distributed Cache package and registration
dotnet core provides IDistributedCache interface which can be overrided with our own implementation. That is one of the beauties of dotnet core, having DI implementation at heart of framework.
There is already nuget package available to override IDistributedCache i:e Microsoft.Extensions.Caching.Redis.
To use we need to register service by using an extension method from the package.
How to use Redis Cache based on IDistributedCache?
Since Redis cache registration is done, we can use IDistributedCache reference in the constructor to utilize Redis.
The image on the right side provide all options from IDistrubutedCache. Also, you can see I have used it on constructor to get an instance from DI. This is a Disposable implementation so if you are you are not using DI then disposing of the object needs to be taken care explicitly.
As you can see from function names, these are pretty basic to use. The idea is to extend the APIs with C# extension methods through which we can take care of serialization and deserialization of any object too.
In the extension method, I am going to use GetOrSetCacheAsync which would take care of getting or setting of value automatically in the cache, if the cache is available in Redis than it would get from it else it would save in Redis and get the value, we would see how to use shortly.
The GetOrSetCacheAsync function backed by GetCacheValueAsync to get from Redis and to save StoreValueAsync. These all are generic version. Here is the whole implementation of Extension methods.
You can add your own implementation based on a need basis.
How to use created extension methods?
The best way to show this is through Unit Test class, so here is the code which utilizes and explains whole custom extension methods.
Please note that to make it simple, I have not used disposed on RedisCache instance.
How to install Redis Cache on a local machine?
Redis can be used as a local cache server too on our local machines.
At first install, Chocolatey https://chocolatey.org/, to make installation of Redis easy. Also, the version under Chocolatey supports more commands and compatible with Official Cache package from Microsoft.
After Chocolatey installation hit choco install redis-64.
Once the installation is done, we can start the server by running redis-server.
Distributed Cache package and registration
dotnet core provides IDistributedCache interface which can be overrided with our own implementation. That is one of the beauties of dotnet core, having DI implementation at heart of framework.
There is already nuget package available to override IDistributedCache i:e Microsoft.Extensions.Caching.Redis.
To use we need to register service by using an extension method from the package.
services.AddDistributedRedisCache(options =>
{
options.Configuration = Configuration["<Redis connection string>"];
options.InstanceName = "<prefix name for easy identification (optional)>:";
});
How to use Redis Cache based on IDistributedCache?
Since Redis cache registration is done, we can use IDistributedCache reference in the constructor to utilize Redis.
Default options from IDistributedCache |
As you can see from function names, these are pretty basic to use. The idea is to extend the APIs with C# extension methods through which we can take care of serialization and deserialization of any object too.
In the extension method, I am going to use GetOrSetCacheAsync which would take care of getting or setting of value automatically in the cache, if the cache is available in Redis than it would get from it else it would save in Redis and get the value, we would see how to use shortly.
The GetOrSetCacheAsync function backed by GetCacheValueAsync to get from Redis and to save StoreValueAsync. These all are generic version. Here is the whole implementation of Extension methods.
/// <summary>
/// Extension methods for caching.
/// </summary>
public static class CacheExtension
{
/// <summary>
/// Gets or set cache asynchronous.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="cache">The distributed cache interface.</param>
/// <param name="key">The key for storing cache.</param>
/// <param name="storingItem">The storing item.</param>
/// <param name="cacheRule">The cache rule.</param>
/// <returns>
/// The result for stored item.
/// </returns>
public static async Task<TResult> GetOrSetCacheAsync<TResult>(this IDistributedCache cache, string key,
Func<TResult> storingItem, DistributedCacheEntryOptions cacheRule = null)
where TResult : class
{
var cachedValue = await cache.GetCacheValueAsync<TResult>(key);
if (cachedValue == null)
{
return await cache.StoreValueAsync(key, storingItem, cacheRule);
}
return cachedValue;
}
/// <summary>
/// Gets the cache value asynchronous.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="cache">The cache.</param>
/// <param name="key">The key for caching.</param>
/// <param name="storingItem">The storing item.</param>
/// <returns>Value of cached item.</returns>
public static async Task<TResult> GetCacheValueAsync<TResult>(this IDistributedCache cache, string key)
where TResult : class
{
if (IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
}
var cachedValue = await cache.GetStringAsync(key.ToLower(CultureInfo.InvariantCulture));
if (IsNullOrEmpty(cachedValue))
{
return null;
}
return JsonConvert.DeserializeObject<TResult>(cachedValue);
}
/// <summary>
/// Stores the value in cache.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="cache">The cache.</param>
/// <param name="key">The key for caching.</param>
/// <param name="storingItem">The storing item.</param>
/// <param name="cacheRule">The cache rule.</param>
/// <returns>
/// Value of caching item.
/// </returns>
/// <exception cref="ArgumentNullException">key</exception>
public static async Task<TResult> StoreValueAsync<TResult>(this IDistributedCache cache, string key,
Func<TResult> storingItem, DistributedCacheEntryOptions cacheRule = null)
where TResult : class
{
if (IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
}
var storingValue = storingItem();
if (storingValue != null && storingValue != default(TResult))
{
var redisKey = key.ToLower(CultureInfo.InvariantCulture);
var value = JsonConvert.SerializeObject(storingValue);
if (cacheRule != null)
{
await cache.SetStringAsync(redisKey, value, cacheRule);
}
else
{
await cache.SetStringAsync(redisKey, value);
}
}
return storingValue;
}
}
You can add your own implementation based on a need basis.
How to use created extension methods?
The best way to show this is through Unit Test class, so here is the code which utilizes and explains whole custom extension methods.
[TestClass]
public class RedisCacheTest
{
private readonly IDistributedCache CacheStore;
public RedisCacheTest()
{
CacheStore = new RedisCache(new RedisCacheOptions
{
Configuration = "localhost:6379",
InstanceName = "Test:"
});
}
// INFO: Gets or sets the value based on request.
// If it is first call it would save the value in Redis
// and for second call onwards it would just receive the value from cache.
// WARNING: If we use same method to update value in cache again, it would not save.
// Check OverWriteCache() and StoresCacheOnce() function
[TestMethod]
public async Task CacheStoreIsSame()
{
await CacheStore.GetOrSetCacheAsync("key", () =>
{
// Do some sort of process and return object.
return new RedisStoreClass("value");
});
var val = await CacheStore.GetCacheValueAsync<RedisStoreClass>("key");
Assert.IsTrue(val.Name == "value");
}
// INFO: Expiration test after 1 millisecond delay.
[TestMethod]
public async Task CacheExpire()
{
var cacheKey = "cache:expiration:test";
await CacheStore.GetOrSetCacheAsync(cacheKey,
() => new RedisStoreClass("value"), new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMilliseconds(100)
});
await Task.Delay(101);
var cachedValue = await CacheStore.GetCacheValueAsync<RedisStoreClass>(cacheKey);
Assert.IsNull(cachedValue);
}
// INFO: Overwriting cache.
[TestMethod]
public async Task OverWriteCache()
{
var cacheKey = "cache:overwrite";
await CacheStore.GetOrSetCacheAsync(cacheKey, () => new RedisStoreClass("value"));
await CacheStore.StoreValueAsync(cacheKey, () => new RedisStoreClass("value2"));
var cachedValue = await CacheStore.GetCacheValueAsync<RedisStoreClass>(cacheKey);
Assert.AreEqual(cachedValue.Name, "value2");
}
[TestMethod]
public async Task RemoveCache()
{
var cacheKey = "cache:remove";
await CacheStore.GetOrSetCacheAsync(cacheKey,
() => new RedisStoreClass("value"));
await Task.Delay(10000);
await CacheStore.RemoveAsync(cacheKey);
var cachedValue = await CacheStore.GetCacheValueAsync<RedisStoreClass>(cacheKey);
Assert.IsNull(cachedValue);
}
// INFO: Case insensitive check
[TestMethod]
public async Task CaseInsensitiveKey()
{
var cacheKey = "cache:InsensitiveKey";
await CacheStore.GetOrSetCacheAsync(cacheKey,
() => new RedisStoreClass("value"));
var val = await CacheStore.GetCacheValueAsync<RedisStoreClass>(cacheKey.ToUpperInvariant());
Assert.IsNotNull(val);
}
// INFO: If we use GetOrSetCacheAsync multiple times, new value won't be saved.
[TestMethod]
public async Task StoresCacheOnce()
{
var cacheKey = "cache:StoreOnce";
await CacheStore.GetOrSetCacheAsync(cacheKey,
() => new RedisStoreClass("value"));
await CacheStore.GetOrSetCacheAsync(cacheKey,
() => new RedisStoreClass("value2"));
var val = await CacheStore.GetCacheValueAsync<RedisStoreClass>(cacheKey);
Assert.AreEqual(val.Name, "value");
}
}
internal class RedisStoreClass
{
public string Name { get; set; }
public RedisStoreClass(string name)
{
Name = name;
}
}
Please note that to make it simple, I have not used disposed on RedisCache instance.
Dear Sir
ReplyDeletehow can I delete all redis cache with some pattern key
Example: I have 3 key like this
Key 1: :User:1 (1: is dynamic)
Key 2: :User:2 (2: is dynamic)
Key 3: :Role:1
-> how can I delete key 1 and 2
So, sorry for really late..... reply. You might not need now but adding here if it can help someone else.
DeleteYou can create an extension method to remove, which can loop and look for your provided pattern to delete.
Thanks a lot, you made it really easy to understand
ReplyDeleteCucumber BDD Online Training
BDD Cucumber Training in Bangalore