Search code examples
c#asp.net-core-3.1azure-redis-cachelazycache

How to extend LazyCache with Redis Cache for asp.net core 3.1 application?


I have asp.net core 3.1 web api project which leverages Azure SQL database as its data storage option. At present I am also using Azure Redis Cache to improve performance of the application.

Here goes the code details :

ICacheProvider.cs:

public interface ICacheProvider
{
    IList<string> GetKeys();
    IList<string> GetKeys(string strGroup);
    T Get<T>(string strKey, string strGroup = "");
    bool Set(string strKey, object objData, string strGroup = "", int? intMinutes = null);
    bool Remove(string strKey, string strGroup = "");
    bool RemoveGroup(string strGroup);
    bool ClearCache();
    Task<bool> ClearCacheAsync();
}

CacheProvider.cs

public class CacheProvider : ICacheProvider
{
    private static readonly int ExpiryMinutes = ConfigManager.Get(CacheExpiryMinutes, CacheExpiryMinutes);
    private static Lazy<ConnectionMultiplexer> _objCacheConn = CreateConnection();
    private static Lazy<ConnectionMultiplexer> CreateConnection()
    {
        return new Lazy<ConnectionMultiplexer>(() =>
        {
            string conn = ConfigManager.Get(RedisConnString);
            return ConnectionMultiplexer.Connect(conn);
        });
    }

    private ConnectionMultiplexer Connection
    {
        get
        {
            return _objCacheConn.Value;
        }
    }

    private IDatabase GetDatabase()
    {
        return Connection.GetDatabase();
    }

    private EndPoint[] GetEndPoints()
    {
        return Connection.GetEndPoints();
    }

    private IServer GetServer()
    {
        var objEndpoint = GetEndPoints().First();
        return Connection.GetServer(objEndpoint);
    }

    public IList<string> GetKeys()
    {
        return GetKeys("*");
    }

    public IList<string> GetKeys(string strGroup)
    {
        var lstKeys = new List<string>();
        try
        {
            var objServer = GetServer();
            if (objServer != null)
            {
                var strPattern = strGroup + ":*";
                var objRedisKeys = objServer.Keys(pattern: strPattern);
                var objLst = objRedisKeys.GetEnumerator();
                while (objLst.MoveNext())
                {
                    lstKeys.Add(objLst.Current.ToString());
                }
            }
        }
        catch (Exception)
        {
            lstKeys = new List<string>();
        }

        return lstKeys;
    }

    public T Get<T>(string strKey, string strGroup = "")
    {
        T objData = default(T);
        try
        {
            var objCache = GetDatabase();
            if (!strKey.IsEmpty() && objCache != null)
            {
                strKey = (strGroup.IsEmpty() ? C.CacheGroups.General : strGroup) + ":" + strKey;
                var strData = objCache.StringGet(strKey).ToString();
                if (!strData.IsEmpty())
                {
                    objData = JsonConvert.DeserializeObject<T>(strData);
                }
            }
        }
        catch (Exception)
        {
            objData = default(T);
        }

        return objData;
    }

    public bool Set(string strKey, object objData, string strGroup = "", int? intMinutes = null)
    {
        bool blnSuccess = false;
        try
        {
            var objCache = GetDatabase();
            if (!strKey.IsEmpty() && objData != null && objCache != null)
            {
                intMinutes = intMinutes ?? ExpiryMinutes;
                strKey = (strGroup.IsEmpty() ? C.CacheGroups.General : strGroup) + ":" + strKey;
                var strData = JsonConvert.SerializeObject(objData);
                var tsExpiry = new TimeSpan(0, intMinutes.Value, 0);
                blnSuccess = objCache.StringSet(strKey, strData, tsExpiry);
            }
        }
        catch (Exception)
        {
            blnSuccess = false;
        }

        return blnSuccess;
    }

    public bool Remove(string strKey, string strGroup = "")
    {
        bool blnSuccess = false;
        try
        {
            var objCache = GetDatabase();
            if (!strKey.IsEmpty() && objCache != null)
            {
                strKey = (strGroup.IsEmpty() ? C.CacheGroups.General : strGroup) + ":" + strKey;
                blnSuccess = objCache.KeyDelete(strKey);
            }
        }
        catch (Exception)
        {
            blnSuccess = false;
        }

        return blnSuccess;
    }

    public bool RemoveGroup(string strGroup)
    {
        bool blnSuccess = false;
        try
        {
            var lstKeys = GetKeys(strGroup);
            var objCache = GetDatabase();
            if (lstKeys.Count > 0 && objCache != null)
            {
                foreach (var strKey in lstKeys)
                {
                    objCache.KeyDelete(strKey);
                }

                blnSuccess = true;
            }
        }
        catch (Exception)
        {
            blnSuccess = false;
        }

        return blnSuccess;
    }

    public bool ClearCache()
    {
        bool blnSuccess = false;
        try
        {
            var objServer = GetServer();
            if (objServer != null)
            {
                objServer.FlushAllDatabases();
                blnSuccess = true;
            }
        }
        catch (Exception)
        {
            blnSuccess = false;
        }

        return blnSuccess;
    }

    public async Task<bool> ClearCacheAsync()
    {
        // Get server details
        var server = GetServer();
        if (server is null)
            return false;
        // Flush All Databases
        await server.FlushAllDatabasesAsync();
        return true;
    }
}

I am consuming the above CacheProvider in my code as mentioned below :

private async Task<IReadOnlyList<TestInfo>> GetDataAsync(int Id1, int Id2, int Id3, string cacheKey)
{
    var data = _cacheProvider.Get<IReadOnlyList<TestInfo>>(cacheKey, TestCacheGroup);
    if (data is null)
    {
        data = await _test.GetSampleDataAsync(Id1, Id2, Id3);
        _cacheProvider.Set(cacheKey, data, TestCacheGroup, Defaults.CacheExpiryMinutes);
    }

    return data;
}

LazyCache by default uses MemoryCache under the hood. I came through documentation that LazyCache can be extended to swap out to redis or casandra at a later time but keep the same code and API.

Can anyone help me here with some code sample which will serve as a reference for my implementation


Solution

  • It is recommended for v2+ users of LazyCache users (who require the use of Redis, etc) move to the IDistributedCache present in asp .net framework. For versions earlier, they provide a mechanism to do so via the ObjectCache class. The following link may help those 1.x users: LazyCache Redist, Cassandra Extensions

    There is an existing Redis implementation here: LazyCache: Redis