Link Search Menu Expand Document

JSON Cache


This page contains the reference implementation when implementing a file system cache object that is based for JSON. The consolidated output of this page can be found here.

The class is using the Newtonsoft.Json package.

Implementation

First, create an interface that implements an ICache interface. This is also to support the dependency injection.

public interface IJsonCache : ICache
{
    // Properties
    string Extension { get; }
    string Path { get; }

    // Methods
    string GetFileName(string key);
    void EnsureDirectory();
}

Then, create a class that implements your newly created interface.

public class JsonCache : IJsonCache
{
    public JsonCache(string path,
        string extension)
    {
        ...
    }
}

All methods of the ICache interface must be implemented manually.

Properties

Implement the properties that will hold the extension of the file and the location of the caches.

public string Extension { get; }
public string Path { get; }

Methods

Implement the helper methods that would help you compose the filename and creates a directory.

public string GetFileName(string key)
{
    var fileName = $"{Regex.Replace(key, "[^a-zA-Z0-9 -]", "_")}.{Extension}";
    return System.IO.Path.Combine(Path, fileName);
}

public void EnsureDirectory()
{
    if (Directory.Exists(Path) == false)
    {
        Directory.CreateDirectory(Path);
    }
}

Then, implement each method of the ICache interface itself.

Add

public void Add<T>(string key,
    T value,
    int expiration = 180,
    bool throwException = true)
{
    var fileName = GetFileName(key);
    var contains = Contains(key);
    if (contains)
    {
        if (throwException)
        {
            throw new Exception($"File '{fileName}' already exists.");
        }
    }
    File.WriteAllText(fileName, JsonConvert.SerializeObject(value));
}

public void Add<T>(CacheItem<T> item,
    bool throwException = true)
{
    Add<T>(item.Key, item.Value, item.CacheItemExpiration, throwException);
}

Clear

public void Clear()
{
    Directory.Delete(Path, true);
    EnsureDirectory();
}

Contains

public bool Contains(string key)
{
    var fileName = GetFileName(key);
    return File.Exists(fileName);
}

Get

public CacheItem<T> Get<T>(string key,
    bool throwException = true)
{
    var fileName = GetFileName(key);
    if (File.Exists(fileName))
    {
        var value = JsonConvert.DeserializeObject<T>(File.ReadAllText(fileName));
        return new CacheItem<T>(key, value);
    }
    if (throwException)
    {
        throw new FileNotFoundException($"File '{fileName}' is not found.");
    }
    return null;
}

Remove

public void Remove(string key,
    bool throwException = true)
{
    var fileName = GetFileName(key);
    if (File.Exists(fileName))
    {
        File.Delete(fileName);
    }
    if (throwException)
    {
        throw new FileNotFoundException($"File '{fileName}' is not found.");
    }
}

GetEnumerator

IEnumerator IEnumerable.GetEnumerator()
{
    foreach (var fileName in Directory.GetFiles(Path))
    {
        yield return JsonConvert.DeserializeObject<object>(File.ReadAllText(fileName));
    }
}

The class CacheItem does not have default constructor, therefore, you cannot serialize/deserialize this object into JSON. The only thing that you can serialize/deserialize is the value itself. If you wish to cache the properties (i.e.: Key, Expiration, ExpirationInMinutes), then you have to create a class in between before serializing/deserializing it.

Usability

Create a factory class.

public static class CacheFactory
{
    private static object _syncLock = new object();
    private static ICache _cache = null;
    
    public static ICache CreateCacher()
    {
        if (_cache == null)
        {
            lock (_syncLock)
            {
                if (_cache == null)
                {
                    _cache = new JsonCache();
                }
            }
        }
        return _cache;
    }
}

Or inject it as a singleton object.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // Registration
    services.AddSingleton<IJsonCache, JsonCache>();
}