Click here to Skip to main content
15,064,166 members
Articles / Database Development / Redis
Article
Posted 24 Aug 2016

Tagged as

Stats

51.2K views
18 bookmarked

A Simple C# Cache Component Using Redis as Provider

Rate me:
Please Sign up or sign in to vote.
4.70/5 (9 votes)
25 Aug 2016CPOL8 min read
A small set of C# classes/projects that create a simple cache component that interacts with Redis

Introduction

Caching is nothing new. You can simply put your data in a cache provider you commonly use, but what if you could have a strategy and a simple component that allows you to hide the details of how to communicate with your cache provider?

Plus, most companies, or teams need a simple solution, that serves people with or without understanding of how things work under the hood, something like plug & play and it's done.

With this is mind, the purpose of this article is to provide a simple client component to perform basic caching operations. I choose Redis as my cache provider, in order to demonstrate the ability for this component to interact with external systems (I could use anything else). The goal is to store the cache provider details in a custom configuration section and provide a simple point of entry that abstracts developers from the details of how do we interact with Redis.

Notice that this component is not a Redis client by itself, it is a facade that uses ServiceStack packages in order to communicate with Redis server. The advantage is that developers don't need to know the details of the client assemblies each time they want to call Redis.

Installing and configuring Redis server is not covered by this article, at this moment it is expected to have the server installed and running. This article does not cover comparison with other solutions or performance tests.

Background

Soon or later, people struggle for performance enhancements, specially (but not only) when it comes to user interface interaction speed.

Every page or form that is loaded, usually carries out loading a bunch of information that is theorically always the same (yet it is prone to change - from time to time, or in some cases... never). Some projects are built from the beginning with this in mind, others adapt as they evolve.

Caching is one technique that may help solving this issue. Although this is nothing new, it is always important to keep in mind that there are several ways of caching information, and the way that information is cached may also vary, specially with NoSQL solutions.

I could write about several caching strategies or solutions, but my goal here is to share with the community one solution to integrate any .Net application with Redis server, with Caching in mind (I could also fully explore Redis, but it is out of scope). 

Using the code

Let's identify what we need.

The goal is to have a small component that can be used in every .Net project. Considering ease to use, scalability and configuration flexibility, I suggest:

  • Creating a Class Library (Configuration) with a class for a custom configuration section that can be declared on any app/web.config, with Redis basic information required to build a RedisEndpoint (configuration flexibility);
  • Creating another Class Library (Common) and inside create a class that acts as the cache provider for Redis. For future flexibility of the solution, let's inherit from an interface with the following operations:
    • Set (which will set a given object in cache);
    • Get (which gets a given object from cache);
    • Remove (removes an object from cache);
    • IsInCache(which states if a given object is in cache or not).
  • Creating a unit test project that references both Configuration and Common projects in order to test our provider.

Notice that this is only a way of achieving what we need. There could be other ways of interacting with a cache provider without going this way, the reason why I choose this option just to sepparate all concerns  - configuration, basic cross-cut functionality (Common/Caching) and testing as the top layer just as demo.

So, in order to begin coding, let's start with pre-requirements:

  • Visual Studio: I've used 2015 + .Net Framework 4.5.2 but I believe you can use a previous version;
  • Redis Server installed and running (there are several ways of downloading and installing, the easier I found out is through MS guys. You can find a one click install here - https://github.com/MSOpenTech/redis/releases - which installs Redis as Windows Service and you're set up!);
  • I've also installed Redis Desktop Manager in order to see what's inside Redis (https://redisdesktop.com/);
  • Nuget packages for ServiceStack (this will be covered below).

So, less text, more code:

I suggest you begin creating a blank solution. Name it 'CacheComponent'.

Create a new class library project, name it 'Configuration'. Our goal is to create a custom configuration section for Redis.

Add a reference to 'System.Configuration'.

Add a new class, name it 'RedisConfigurationSection'. We will have four attributes in our custom configuration section:

  • host (mandatory)
  • port (mandatory)
  • password (optional)
  • databaseID (optional)

Copy-paste the following code to your class:

C#
using System.Configuration;

namespace Configuration
{
    public class RedisConfigurationSection : ConfigurationSection
    {
        #region Constants

        private const string HostAttributeName = "host";
        private const string PortAttributeName = "port";
        private const string PasswordAttributeName = "password";
        private const string DatabaseIDAttributeName = "databaseID";

        #endregion

        #region Properties

        [ConfigurationProperty(HostAttributeName, IsRequired = true)]
        public string Host
        {
            get { return this[HostAttributeName].ToString(); }
        }

        [ConfigurationProperty(PortAttributeName, IsRequired = true)]
        public int Port
        {
            get { return (int)this[PortAttributeName]; }
        }

        [ConfigurationProperty(PasswordAttributeName, IsRequired = false)]
        public string Password
        {
            get { return this[PasswordAttributeName].ToString(); }
        }

        [ConfigurationProperty(DatabaseIDAttributeName, IsRequired = false)]
        public long DatabaseID
        {
            get { return (long)this[DatabaseIDAttributeName]; }
        }

        #endregion
    }
}

Add a new class, name it 'RedisConfigurationManager'. It will be responsible to interact with the configuration section:

C#
using System.Configuration;

namespace Configuration
{
    public class RedisConfigurationManager
    {
        #region Constants

        private const string SectionName = "RedisConfiguration";

        public static RedisConfigurationSection Config
        {
            get
            {
                return (RedisConfigurationSection)ConfigurationManager.GetSection(SectionName);
            }
        }

        #endregion
    }
}

Build the project, it should succeed. We will come back to this later in order to declare the configuration in our app.config file of the unit tests project.

Your solution should look like this:

Now it's time to create another Class Library in order to create logic to interact with Redis, name it 'Common'.

In order to continue, we need something to integrate with Redis. I used ServiceStack packages to do it. Right-click 'Common' project and select 'Manage Nuget packages...', and go to 'Browse' tab. Search for 'ServiceStack' and install ServiceStack.Redis.

You will notice that is also installs Common, Interfaces and Text packages.

Add a reference to 'Configuration' class library.

Add a new Interface, name it 'ICacheProvider':

C++
using System;

namespace Common
{
    public interface ICacheProvider
    {
        void Set<T>(string key, T value);
        
        void Set<T>(string key, T value, TimeSpan timeout);

        T Get<T>(string key);

        bool Remove(string key);

        bool IsInCache(string key);
    }
}

Add a new class, name it 'RedisCacheProvider'. And here we go:

C++
using Configuration;
using ServiceStack.Redis;
using System;

namespace Common
{
    public class RedisCacheProvider : ICacheProvider
    {
        RedisEndpoint _endPoint;

        public RedisCacheProvider()
        {
            _endPoint = new RedisEndpoint(RedisConfigurationManager.Config.Host, RedisConfigurationManager.Config.Port, RedisConfigurationManager.Config.Password, RedisConfigurationManager.Config.DatabaseID);
        }

        public void Set<T>(string key, T value)
        {
            this.Set(key, value, TimeSpan.Zero);
        }

        public void Set<T>(string key, T value, TimeSpan timeout)
        {
            using (RedisClient client = new RedisClient(_endPoint))
            {
                client.As<T>().SetValue(key, value, timeout);
            }
        }

        public T Get<T>(string key)
        {
            T result = default(T);

            using (RedisClient client = new RedisClient(_endPoint))
            {
                var wrapper = client.As<T>();

                result = wrapper.GetValue(key);
            }

            return result;
        }
        
        public bool Remove(string key)
        {
            bool removed = false;

            using (RedisClient client = new RedisClient(_endPoint))
            {
                removed = client.Remove(key);
            }

            return removed;
        }

        public bool IsInCache(string key)
        {
            bool isInCache = false;

            using (RedisClient client = new RedisClient(_endPoint))
            {
                isInCache = client.ContainsKey(key);
            }

            return isInCache;
        }
    }
}

Notice that in the constructor we are accessing our custom configuration section through RedisConfigurationManager, in order to get the necessary information to build a RedisEndpoint.

Build the project, it should succeed. At this moment we have everything we need to use our component in any .NET application.

Your solution should look like this:

Let's create a Unit test project to check everything is ok. Name it 'UnitTests'. It will create a unit test file, rename it to 'CacheTests'.

Add references to both class library projects 'Configuration' and 'Common'.

Right-click the 'UnitTests' project, Add new item, select 'Application Configuration File'. This will add an app.config file to your project.

Open the app.config file, and put it like this:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="RedisConfiguration" type="Configuration.RedisConfigurationSection, Configuration, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </configSections>
  <RedisConfiguration host="localhost" port="6379"/>
</configuration>

This will set up our custom configuration section, which will be ready to use. I could simply add this information to AppSettings, but this way I believe it's cleaner and easier to understand. Of course this is just an oppinion.

Be aware that ServiceStack assemblies are not signed. If your assemblies are signed, a custom configuration section might throw some errors and require to sign third party assemblies (keep this in mind).

Just before setting and getting data to/from Redis, let's create a few classes to store objects.

Let's assume our DataModel has a class Person and a class Contact. A person may have 0+ contacts. These classes are small for the sake of simplicity and demonstration:

C#
using System.Collections.Generic;

namespace UnitTests
{
    public class Contact
    {
        public string Type { get; set; }

        public string Value { get; set; }

        public Contact(string type, string value)
        {
            this.Type = type;
            this.Value = value;
        }
    }

    public class Person
    {
        public long Id { get; set; }

        public string Name { get; set; }

        public List<Contact> Contacts { get; set; }

        public Person(long id, string name, List<Contact> contacts)
        {
            this.Id = id;
            this.Name = name;
            this.Contacts = contacts;
        }
    }
}

Here we go, now change the 'CacheTests' class to look like this:

C#
using Common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;

namespace UnitTests
{
    [TestClass]
    public class CacheTests
    {
        ICacheProvider _cacheProvider;

        [TestInitialize]
        public void Initialize()
        {
            _cacheProvider = new RedisCacheProvider();
        }

        [TestMethod]
        public void Test_SetValue()
        {
            List<Person> people = new List<Person>()
            {
                new Person(1, "Joe", new List<Contact>()
                {
                    new Contact("1", "123456789"),
                    new Contact("2", "234567890")
                })
            };

            _cacheProvider.Set("People", people);
        }

        [TestMethod]
        public void Test_GetValue()
        {
            var contacts = _cacheProvider.Get<List<Contact>>("People");

            Assert.IsNotNull(contacts);
            Assert.AreEqual(2, contacts.Count);
        }
    }
}

Now you have two unit tests:

  • One to set a list of people into Redis;
  • Another to get the list from Redis.

Your solution should look like this:

If you run first 'Test_SetValue' it should succeed. Checking Redis Desktop Manager, should look like this:

If you run 'Test_GetValue' it should also succeed.

Points of Interest

At this moment we have a small cache component that hides the details of communicating with Redis. We can raise the following questions and all of them would make all sense:

  • Isn't Redis much more than this? No doubt, I've kept it simple in order to demonstrate the usage of an external system in order to cache our data. I could do this without Redis or with another solution such as AppFabric or MongoDb for example. Anyway, if you are in a scenario where you need both caching providers + a richer Redis integration component you can create your own component, and then the cache provider instead of interacting with ServiceStack can interact with your own component instead;
  • What if I don't want to use Redis? Just build the provider you want, inherit it from ICacheProvider and you're set up!;
  • What if My cache provider needs more methods than those inside ICacheProvider? You can add logic to ICacheProvider or extend it to another interface and add the logic you need in your custom provider;
  • What If I want a component to handle cache, but my cache provider is not always the same? For example, I want to use Redis to cache data to use on my web sites, but I want to use in-memory for my web or windows services?  Answered below:

This sucks because interacting with Redis has its own way, but if I want to use MongoDb, AppFabric, or in-memory the way we interact is different. Not all configurations apply, classes are different and so on.

I could create a component for each one of my providers, but what if later I also decide that my services that used to store in-memory cache, also to use Redis for example? Should I re-write all code references to use Redis component instead of in-memory component?

There must be a better way. I believe this article deserves further investment to address this questions, but its goal is to provide a simple component to interact with an external storage for caching purpose...

Future work:

  • Add other cache provider(s) based on these projects and classes;
  • Add logic to decide in runtime which provider to use;
  • Use AOP in order to automatically set/get data automatically into/from cache.

To be continued!

PS.: notice that not every project or set of projects require an external cache system such as Redis. Some technologies such as ASP.NET provide out of the box caching funcionalities which may be enough (or not, you should try out and compare). But, independently of the provider you use, following a pattern as the one shown above with the ICacheProvider interface should give you the minimum level of flexibility in case things change later.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

david.ribeiro
Software Developer (Senior)
Portugal Portugal
No Biography provided

Comments and Discussions

 
QuestionCan you plz share the code Pin
GauravSatpute18-Dec-18 19:51
MemberGauravSatpute18-Dec-18 19:51 
PraiseVery nice and clean article Pin
Upendar.Srirangam8-Apr-18 17:15
MemberUpendar.Srirangam8-Apr-18 17:15 
QuestionGet cache failed Pin
rohit.rohitsharma1122-Apr-18 21:54
Memberrohit.rohitsharma1122-Apr-18 21:54 
AnswerRe: Get cache failed Pin
May.201924-Jan-19 20:55
MemberMay.201924-Jan-19 20:55 
QuestionGreat sample, my first successful dabble with Redis Pin
nsdlsandy16-Mar-18 6:48
Membernsdlsandy16-Mar-18 6:48 
Great sample, my first successful dabble with Redis!


Based on the code, your assert statement should be
Assert.AreEqual(1, contacts.Count);


otherwise it will fail.
AnswerRe: Great sample, my first successful dabble with Redis Pin
rohit.rohitsharma1122-Apr-18 22:09
Memberrohit.rohitsharma1122-Apr-18 22:09 
AnswerRe: Great sample, my first successful dabble with Redis Pin
rohit.rohitsharma1123-Apr-18 19:39
Memberrohit.rohitsharma1123-Apr-18 19:39 
GeneralRe: Great sample, my first successful dabble with Redis Pin
Shaun Kie18-Apr-18 5:06
MemberShaun Kie18-Apr-18 5:06 
QuestionGreat example of using Redis Pin
Marc Clifton25-Aug-16 2:10
mvaMarc Clifton25-Aug-16 2:10 
AnswerRe: Great example of using Redis Pin
david.ribeiro25-Aug-16 3:00
Memberdavid.ribeiro25-Aug-16 3:00 
QuestionImages Problem? Pin
Nelek24-Aug-16 23:22
protectorNelek24-Aug-16 23:22 
AnswerRe: Images Problem? Pin
david.ribeiro25-Aug-16 2:47
Memberdavid.ribeiro25-Aug-16 2:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.