ASP.NET Core Converting In-Memory Cache with IMemoryCache to Redis

Viewed 377

I have an ASP.NET Core Project ( Aspect-Oriented ) with In-Memory Cache and I use IMemoryCache interface for it.

I want to convert this to Redis caching...

But I noticed that In-Memory caching with IMemoryCache can keep data as "object" type.

public void Add(string key, object data, int duration)
{
   _cache.Set(key, data, TimeSpan.FromMinutes(duration));
}

And when I get the value, it can bring it as object...

public object Get(string key)
{
    var keyValue = _cache.Get(key);  //Get method returns as string
    return keyValue;
}

SO , I can get the value from cache as the type of it and return it...

And when I convert it to RedisCacheManager ;

public static string ToJson(this object value)
{
   var aa = JsonConvert.SerializeObject(value, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
   return aa;
}

SET 
public void Set(string key, object value, int duration)
{
    var k = value.ToJson();
    _client.GetDatabase().StringSet(key, k, TimeSpan.FromMinutes(duration));
}

GET
public object Get(string key)
{
   var keyValue = _client.GetDatabase().StringGet(key); //returns as "RedisValue" type
   return keyValue;
}

this returns as "RedisValue"

and while using In-Memory cache with IMemoryCache, value returns as object and would be cast to its own type (for example as "List-Product" or "List-Category" ....)

So I wasn't touching anything in that process but when I use Redis, value returns as RedisValue then it returns this value

SO I GOT AN ERROR about Casting the value to whatever I need , But I use Interceptor and don't specify the type of it..

public class CacheAspect : MethodInterception
{
  private int _duration;
  private IRedisCacheService _cacheManager;

  public CacheAspect(int duration = 60)//Default
  {
    _duration = duration;
    _cacheManager = ServiceTool.ServiceProvider.GetService<IRedisCacheService>();
  }
  public override void Intercept(IInvocation invocation)
  {
    var methodName = string.Format($"{invocation.Method.ReflectedType.FullName}.{invocation.Method.Name}");
    var arguments = invocation.Arguments.ToList();
    var key = $"{methodName}({string.Join(",", arguments.Select(x => x != null ? JsonConvert.SerializeObject(x, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }) : "<Null>"))})";

    if (_cacheManager.IsAdd(key))
    {
        var cacheValue = _cacheManager.Get(key); //returns as object
        return;
    }

    invocation.Proceed();
    _cacheManager.Set(key, invocation.ReturnValue, _duration);
  }
} 

Is there ANYONE who has encountered with this situation or what can I do to deal with this ??

1 Answers

I have solved this problem by using Generic class "RedisCacheAspect"

public class RedisCacheAspect<T> : MethodInterception where T : class
{
    private int _duration;
    private IRedisCacheManager _cacheManager;

    public RedisCacheAspect(int duration = 60)
    {
        _duration = duration;
        _cacheManager = ServiceTool.ServiceProvider.GetService<IRedisCacheManager>();
    }

    public async override void Intercept(IInvocation invocation)
    {
        var methodName = string.Format($"{invocation.Method.ReflectedType.Name}.{invocation.Method.Name}");
        var arguments = invocation.Arguments.ToList();
    
        var key = $"{methodName}({string.Join(",", arguments.Select(x => x != null ? JsonConvert.SerializeObject(x) : "<Null>"))})";

        if (_cacheManager.KeyExists(key))
        {
            var cacheValue = _cacheManager.Get(key);
            var data = JsonConvert.DeserializeObject<T>(cacheValue, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });

            var isAsync = invocation.Method.IsReturnTask();

            if (isAsync)
            {
                var returnValue = Task.FromResult(data);
                invocation.ReturnValue = returnValue;
            }
            else
                invocation.ReturnValue = data;

            return;
        }

        invocation.Proceed();

        var method = invocation.MethodInvocationTarget;
        var isAsync2 = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
        if (isAsync2 && typeof(Task).IsAssignableFrom(method.ReturnType))
        {
            var cacheValue = await InterceptAsync((dynamic)invocation.ReturnValue);
            _cacheManager.Set(key, cacheValue, _duration);
        }
        else
            _cacheManager.Set(key, invocation.ReturnValue, _duration);
    }

    private static async Task InterceptAsync(Task task)
    {
        await task.ConfigureAwait(false);
        // do the continuation work for Task...
    }

    private static async Task<T> InterceptAsync<T>(Task<T> task)
    {
        T result = await task.ConfigureAwait(false);
        // do the continuation work for Task<T>...
        return result;
    }
}

With this class, you can use it as an attribute on a method like this;

Data : DataResult<List<CategoryDto<<

[RedisCacheAspect<DataResult<List<CategoryDto<<>(duration: 60)]

As you see I use DataResult, but you shouldn't use an Interface while caching a value. This gives you an error because Deserializing an object from Redis cache has be to be mapped to an object that you specify and interfaces cannot be mapped.

An example to RedisCacheManager;

public class RedisCacheManager : IRedisCacheManager, IDisposable
{
    private readonly ConnectionMultiplexer _client;
    private const string RedisDbName = "RedisCache1:";
    private readonly string _connectionString;
    private readonly ConfigurationOptions _configurationOptions;


    public RedisCacheManager(IConfiguration configuration)
    {
        _connectionString = configuration.GetSection("RedisConfiguration:ConnectionString")?.Value;
        var connectionStrings = _connectionString.Split(",");


        _configurationOptions = new ConfigurationOptions()
        {
            //EndPoints = { aa.ToString() },
            AbortOnConnectFail = false,
            AsyncTimeout = 10000,
            ConnectTimeout = 10000,
            KeepAlive = 180
            //ServiceName = ServiceName, AllowAdmin = true
        };

        foreach (var item in connectionStrings)
        {
            _configurationOptions.EndPoints.Add(item);
        }

        _client = ConnectionMultiplexer.Connect(_configurationOptions);
    }

    public IDatabase GetDatabase()
    {
        return _client.GetDatabase();
    }

    public T Get<T>(string key) where T : class
    {
        string value = _client.GetDatabase().StringGet(RedisDbName + key);

        return value.ToObject<T>();
    }

    public async Task<T> GetAsync<T>(string key) where T : class
    {
        string value = await _client.GetDatabase().StringGetAsync(RedisDbName + key);

        return value.ToObject<T>();
    }
}
Related