Home » ASP » ASP.MVC » Using Redis in Asp.Net in an Enterprise System

Using Redis in Asp.Net in an Enterprise System

I wrote about how to integrate Redis into Asp.Net MVC few days ago. Here is a way how to integrate Redis into your solution with dependency injection and abstracting Redis. This additional layer will be helpful if in the future we change from Redis to Memcached or simply.

The first step is to create the interface that will be used.

public interface ICache
{
    void SetString(string key, string objectToCache, TimeSpan? expiry = null);
    void Set<T>(string key, T objectToCache, TimeSpan? expiry = null) where T : class;
    string GetString(string key);
    T Get<T>(string key) where T : class;
    void Delete(string key);
    void FlushAll();
}

This interface gives primary operations that can be execute against Redis (or any other cache system). It’s possible to enhance this interface with more methods, This is the basic operations that is required to run a cache. The first two methods are to set a value inside the cache. One set a simple string, the second take a class of type T. The second one will be mostly used to take an object and serialize it. The next two methods are to get from a key the unserialized data. The next two methods is to delete. One use a key to delete a specific object and the other one delete everything from the cache.

A second interface is used. This one will allow us to get some status about if the cache is enable and if the cache is running properly.

public interface ICacheStatus
{
    bool IsCacheEnabled { get;}
    bool IsCacheRunning { get;}
}

The difference between IsCacheEnable and IsCacheRunning is that the first one is controlled by us. Normally from the web.config, you should have a key to turn on and off the cache. In case you notice a problem with the cache, it is always a good option to be able to turn off. The Second property is about getting the status of the caching server, Redis. If this one become inactive, it’s interesting to get the status from an administration panel for example.

Despite this interface, we need to have an abstract class with shared logic for any cache system (not only Redis). This is where we will have the serialization process, the error logging and the handling of the on/off mechanism. This is where the Circuit Pattern could also be used. I will discuss about it in a future article. Keep in mind for the moment that

public abstract class Cache : ICache, ICacheStatus
{
    private readonly bool isCacheEnable;

    public Cache(bool isCacheEnable)
    {
        this.isCacheEnable = isCacheEnable;
    }

    public void Set<T>(string key, T objectToCache, TimeSpan? expiry = null) where T : class
    {
        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException("key");
        }
        if (this.isCacheEnable)
        {
            try
            {
                var serializedObjectToCache = JsonConvert.SerializeObject(objectToCache
                     , Formatting.Indented
                     , new JsonSerializerSettings
                     {
                         ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                         PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                         TypeNameHandling = TypeNameHandling.All
                     });

                this.SetStringProtected(key, serializedObjectToCache, expiry);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Cannot Set {0}", key), e);
            }
        }
    }

    public T Get<T>(string key) where T : class
    {
        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException("key");
        }
        if (this.isCacheEnable)
        {
            try{
                var stringObject = this.GetStringProtected(key);
                if(stringObject  ==  null)
                {
                     return default(T);
                }
                else
                {
                     var obj = JsonConvert.DeserializeObject<T>(stringObject
                         , new JsonSerializerSettings
                         {
                             ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                             PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                             TypeNameHandling = TypeNameHandling.All
                         });
                    return obj;
                }
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Cannot Set key {0}", key), e);
            }
        }
        return null;
    }

    public void Delete(string key)
    {
        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException("key");
        }
        if (this.isCacheEnable)
        {
            try{
                this.DeleteProtected(key);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Cannot Delete key {0}",key), e);
            }
        }
    }

    public void DeleteByPattern(string prefixKey)
    {
        if (string.IsNullOrEmpty(prefixKey))
        {
            throw new ArgumentNullException("prefixKey");
        }
        if (this.isCacheEnable)
        {
            try
            {
                this.DeleteByPatternProtected(prefixKey);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Cannot DeleteByPattern key {0}", prefixKey), e);
            }
        }
    }

    public void FlushAll()
    {
        if (this.isCacheEnable)
        {
            try{
                this.FlushAllProtected();
            }
            catch (Exception e)
            {
                Log.Error("Cannot Flush", e);
            }
        }
    }

    public string GetString(string key)
    {
        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException("key");
        }
        if (this.isCacheEnable)
        {
            try
            {
                return this.GetStringProtected(key);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Cannot Set key {0}", key), e);
            }
        }
        return null;
    }

    public void SetString(string key, string objectToCache, TimeSpan? expiry = null)
    {
        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException("key");
        }
        if (this.isCacheEnable)
        {
            try
            {
                this.SetStringProtected(key, objectToCache, expiry);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Cannot Set {0}", key), e);
            }
        }
    }
    public bool IsCacheEnabled
    {
        get { return this.isCacheEnable; }

    }
    
    protected abstract void SetStringProtected(string key, string objectToCache, TimeSpan? expiry = null);
    protected abstract string GetStringProtected(string key);
    protected abstract void DeleteProtected(string key);
    protected abstract void FlushAllProtected();
    protected abstract void DeleteByPatternProtected(string key);
    public abstract bool IsCacheRunning { get;  }
}

As you can see, this abstract class will delegate all methods into a protected abstract methods which contains the cache implementation code. This one does not know about concrete implementation, just how to handle general caching knowledge. It also abstract a single method which save a string. This mean that the implementer does not need to care about anything other than string. However, the one that will use the class has access to a Set method that allow to pass a string or an object. The next class, is the one that does the real job. Here is a simple Redis implementations of this abstract class.

public class RedisCache : Definitions.Cache
{
    private ConnectionMultiplexer redisConnections;

    private IDatabase RedisDatabase {
        get {
            if (this.redisConnections == null)
            {
                InitializeConnection();
            }
            return this.redisConnections != null ? this.redisConnections.GetDatabase() : null;
        }
    }

    public RedisCache(bool isCacheEnabled):base(isCacheEnabled)
    {
        InitializeConnection();
    }

    private void InitializeConnection()
    {
        try
        {
             this.redisConnections = ConnectionMultiplexer.Connect(System.Configuration.ConfigurationManager.AppSettings["CacheConnectionString"]);
        }
        catch (RedisConnectionException errorConnectionException)
        {
            Log.Error("Error connecting the redis cache : " + errorConnectionException.Message, errorConnectionException);
        }
    }

    protected override string GetStringProtected(string key)
    {
        if (this.RedisDatabase == null)
        {
            return null;
        }
        var redisObject = this.RedisDatabase.StringGet(key);
        if (redisObject.HasValue)
        {
            return redisObject.ToString();
        }
        else
        {
            return null;
        }
    }

    protected override void SetStringProtected(string key, string objectToCache, TimeSpan? expiry = null)
    {
        if (this.RedisDatabase == null)
        {
            return;
        }

        this.RedisDatabase.StringSet(key, objectToCache, expiry);
    }

    protected override void DeleteProtected(string key)
    {
        if (this.RedisDatabase == null)
        {
            return;
        }
        this.RedisDatabase.KeyDelete(key);
    }

    protected override void FlushAllProtected()
    {
        if (this.RedisDatabase == null)
        {
            return;
        }
        var endPoints = this.redisConnections.GetEndPoints();
        foreach (var endPoint in endPoints)
        {
            var server = this.redisConnections.GetServer(endPoint);
            server.FlushAllDatabases();
        }
    }

    public override bool IsCacheRunning
    {
        get { return this.redisConnections != null && this.redisConnections.IsConnected; }
    }
}

The Redis connection get its setting from the web.config. The instantiation of the Redis object is done by using the ConnectionMultiplexer that come from the StackExchange API. This one is thread save and this is why the Cache will be a singleton from the dependency container.

    container.RegisterType<RedisCache>(new ContainerControlledLifetimeManager()
                                                                , new InjectionConstructor(
                                                                        Convert.ToBoolean(ConfigurationManager.AppSettings["IsCacheEnabled"])
                                                                )); //Singleton ( RedisCache use thread-safe code)
    container.RegisterType<ICache, RedisCache>(); //Re-use the singleton above
    container.RegisterType<ICacheStatus, RedisCache>(); //Re-use the singleton above

This is how to register the cache with Microsoft Unity. The first one register the RedisCache class with a new object object shared by every queries to the cache, thus every requests. The two next registrations associate the two interfaces to that cache instance.

From there, it’s possible to use anywhere the interface. It’s also easy to unit test since you can mock the ICache interface which is the only interface that you need to pass through all your code. About what need to be used, it’s clear from the dependency injection code that we use ICache as the interface to use and not the concrete RedisCache class. The cache shouldn’t be used in the controller class, neither in your service class or in your repository class. This belong to the accessory classes which are between your service and repository class. Here is the a graphic of the layers that is recommended to have when using a cache system and a database.

Layers

The idea is that that the only layer to know about the cache is the accessor. The service layer does not know about the cache or the database — it only know about to get and set from the accessor. The repository does not know about caching, it’s responsibility is to get from the persistence storage the data. This can be with Entity Framework (or any other ORM) or directly with Ado.Net. On the other hand, the cache does not know about the database, it only know how to store data in a fast access way. This mean that the accessor class is the only one to get the cache injected. Here is a small example.

public class ContestAccessor: IContestAccessor
{
	private readonly IContestRepository contestRepository;
	private readonly ICache cache;
	public ContestAccessor(IContestRepository repository, ICache cache)
	{
		//...
	}
}

This class can have methods to get specific information. Here is an example to get a contest by id.

public Contest GetById(int id)
{
    var key = string.Format("contest_by_id_", id);
    var contestObject = this.cache.Get<Contest>(key);
    if (contestObject == null)
    {
        contestObject = this.contestRepository.GetById(id);
        this.cache.Set(key, contestObject);
    }
    return contestObject;
}

This is a basic example, that get the contest from the cache, if this one does not find it, than get it from the repository and store it inside the cache for the next call. Every time, we return the object whatever where it comes from. The service layer uses the injected accessor (the interface IContest for example). It does not know anything about the repository or the cache — the service just knows about getting its object by an id.

If you like my article, think to buy my annual book, professionally edited by a proofreader. directly from me or on Amazon. I also wrote a TypeScript book called Holistic TypeScript

10 Responses so far.

  1. Vincent Bouret says:

    Very interesting post.

    1) Are you using Redis hosted on Linux or the Windows port? If the Redis server is down for some amount of time, I believe the only impact is a slowdown of performance but the system will still proceed directly to the database?

    2) In your design with the Accessor being the only layer aware of both the repository, don’t you end up with very redundant code where every Get method on the Accessors has to repeat the same boilerplate code of :
    try get from cache, is null,

    • Hello,
      1) I am using Redis on Windows. If Redis is down, yes the only impact is the performance but you can also decide to use Redis without having the data backed in a persistence storage (DB). For example, the number of active user on the website could be only managed by Redis. If this one is down, than the user has not the information, thus less features. Since it’s an exceptional case which should happen rarely, this is fine too. It’s always a question of importance or not.
      2) The only code that is repeated is the one that check if the data is there in the cache, if not to go in the database. There is no way to no have that check done. You can try to generalize the concept on a base accessor class which all your accessor classes inherit. Sure, but is the “IF” redundancy a problem that will make your code less readable or less maintainable?

    • Also, if it is not in the cache, you get from the repository. It doesn’t mean that you must set in the cache. This depend of your system but if you have divided the Read and Write operations you may never want to write in the cache in a read operation. That said, it depends of the case. In my opinion, there is nothing wrong to set in the cache since you have the information in memory. Nevertheless, in a more purist way, the read shouldn’t write any information. However, this cause many subsequent read actions to hit the cache with nothing, thus hitting the database. Performance wise, setting the data in the cache if this one is not there is an interesting pattern even in a get operations. Albeit setting in the cache can be complex because of the same information can be duplicated for performance reason, sometime the cache shouldn’t be set in the read. Like always, it’s a question of evaluate the best options for your scenario. I would say that most of the time, the pattern illustrated in this article cover 75% of common scenarios.

  2. Vincent Bouret says:

    Very interesting post.

    1) Are you using Redis hosted on Linux or the Windows port? If the Redis server is down for some amount of time, I believe the only impact is a slowdown of performance but the system will still proceed directly to the database?

    2) In your design with the Accessor being the only layer aware of both the repository, don’t you end up with very redundant code where every Get method on the Accessors has to repeat the same boilerplate code of :
    try get from cache, is null, get from repository, set to cache. Is there any way to avoid this?

  3. Excellent, just what I was looking for thank-you. This appears to be a very nice way of allowing code to be run in dev without caching or local redis installation.

    if this.GetStringProtected(..) returns null (which it always does first time as nothing is in the cache), then JsonConvert fails with a null exception so this code will never work as is. Is the correct solution just to add a null check to the Get method, like so?

    var stringObject = this.GetStringProtected(key);

    if (stringObject == null)
    return null;

    DeleteByPatternProtected() is’t implemented and as far as I can tell redis doesn’t quite have that without scanning keys. I think the correct solution would be to use redis sets, and I wonder if you’d consider updating your example to include a working implementation of DeleteByPatternProtected. There are a several stackoverflow questions about that and none with a concrete solution so yours would be the first.

    • Hi Ewart,
      You are right. In fact, my final code is not the one I posted in this article. I will try to update soon. Here is what I have (which is also using an other pattern called Circuit Breaker. I will update the article without the Circuit Breaker.

      public T Get(string key)
      {
      if (string.IsNullOrEmpty(key))
      {
      throw new ArgumentNullException(“key”);
      }
      if (this.isCacheEnable)
      {
      string stringObject = null;

      if (this.ExecuteUnderCircuitBreaker(() => { stringObject = this.GetStringProtected(key); }, key))
      {
      if (stringObject == null)
      {
      return default(T);
      }
      var obj = Serialization.Deserialize(stringObject);
      return obj;
      }
      else
      {
      Log.Error(string.Format(“Cannot Get key {0}”, key));
      }
      }
      return default(T);
      }

  4. Saibal says:

    Where to find full code base?

  5. Ratan Kalwa says:

    It would be great if you could upload the code.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.