12

In ASP.NET 5 we have two interfaces for caching IDistributedCache and IMemoryCache, and then we also have LocalCache which is an implementation of IDistributedCache that uses MemoryCache internally.

IMemoryCache seems to have the kind of api I am used to, you put in an object of whatever type and you get back an object that you can cast back to the original type. When using MemoryCache I think no serialization is involved, the object is just stored directly in memory which is why the api is more simple.

IDistibutedCache seems like what we should be using to develop scalable cloud applications but it has a less appealing api in that we pass in a byte array and get back a byte array. The objects in this case must be serializable and we must serialize them ourselves in order to put them in the cache and deserialize them after retrieval such as in this code snippet:

public async Task<TreeNode<NavigationNode>> GetTree()
{
    if (rootNode == null)
    {
        await cache.ConnectAsync();
        byte[] bytes = await cache.GetAsync(cacheKey);
        if (bytes != null)
        {
            string json = Encoding.UTF8.GetString(bytes);
            rootNode = BuildTreeFromJson(json);
        }
        else
        {
            rootNode = await BuildTree();
            string json = rootNode.ToJsonCompact();

            await cache.SetAsync(
                                cacheKey,
                                Encoding.UTF8.GetBytes(json),
                                new    DistributedCacheEntryOptions().SetSlidingExpiration(
                                    TimeSpan.FromSeconds(100))
                                    );
        }
    }

    return rootNode;
}

In this specific example I'm using custom serialization and deserialization becuase the object in this example needs some help with serialization because it is not just a simple class.

For more general usage of cache with easily serializable objects it seems like I should implement some kind of cache helper or wrapper around IDistributedCache, to make an api that is more similar to IMemoryCache, so that I can just pass in object and get object back by key and reduce the complexity and duplication of my caching code. Internally I suppose my CacheHelper class would just use standard json serialization or is there something else I should use?

Are there any plans for a CacheHelper of this sort in the framework itself or should I implement my own?

I'm thinking that particularly in environments like Azure I should probably implement caching for most things retrieved frequently from the SqlAzure database in order to reduce costs, and IDistributeCache allows easily plugging in different caching solutions Azure cache or Redit etc by DI.

Is this the right approach or is there any guidance for a better approach or pattern?

When using LocalCache is there any performance difference vs using MemoryCache directly?

Should we always or almost always use IDistributedCache or are there specific example scenarios where using IMemoryCache would be preferred?

18
  • 3
    I personally really don't understand why we have separate caching interfaces. The team say they are different but why do I care in my application? I should be able to switch between in-memory cache to distributed cache without any worry and they should just implement the same interface. Commented Jul 16, 2015 at 15:59
  • 1
    @tugberk there are 2 different interfaces because they don't serve the same purpose: IMemoryCache has been specially designed to accept non-serializable objects that IDistributedCache implementations couldn't handle (e.g a CompilationResult or a CompilerCacheEntry). If you want to easily switch between in-memory and distributed caching, use IDistributedCache: the default implementation (LocalCache) uses IMemoryCache. Commented Jul 16, 2015 at 16:16
  • 2
    @JoeAudette ah yeah sure, but this also means that you can choose a very efficient/cheap way to "serialize" your objects, using either BinaryWriter or BitConverter for instance (stricto sensu, it's not really a serializer). Commented Jul 16, 2015 at 16:40
  • 2
    Here's how we use BinaryWriter and BinaryReader in aspnet/Security to serialize an authentication ticket: github.com/aspnet/Security/blob/dev/src/…. Of course, this kind of "manual serialization" has its own downsides: you need a specific serialization format for every type you want to support, your Write/Read calls must respect the same order and every change made to the serialization format is a "breaking change" (that's why we use a Version hint)... but it's blazingly fast. Commented Jul 16, 2015 at 17:11
  • 2
    If you're looking for a JsonConvert equivalent that simply takes an object and tries to serialize it, BinaryWriter is clearly not the way to go. Commented Jul 16, 2015 at 17:13

2 Answers 2

5

Here are some generic extensions for IDistributedCache that where useful to me running on .net core 3.1. You can use them to serialize and store any object.

public static class DistributedCacheExtensions
{
    public static Task SetAsync<T>(this IDistributedCache cache, string key, T value)
    {
        return SetAsync(cache, key, value, new DistributedCacheEntryOptions());
    }

    public static Task SetAsync<T>(this IDistributedCache cache, string key, T value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (var memoryStream = new MemoryStream())
        {
            var binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, value);
            bytes = memoryStream.ToArray();
        }

        return cache.SetAsync(key, bytes, options);
    }

    public static async Task<T> GetAsync<T>(this IDistributedCache cache, string key)
    {
        var val = await cache.GetAsync(key);
        var result = default(T);

        if (val == null) return result;

        using (var memoryStream = new MemoryStream(val))
        {
            var binaryFormatter = new BinaryFormatter();
            result = (T)binaryFormatter.Deserialize(memoryStream);
        }

        return result;
    }

    public static bool TryGet<T>(this IDistributedCache cache, string key, out T value)
    {
        var val = cache.Get(key);
        value = default(T);

        if (val == null) return false;

        using (var memoryStream = new MemoryStream(val))
        {
            var binaryFormatter = new BinaryFormatter();
            value = (T)binaryFormatter.Deserialize(memoryStream);
        }

        return true;
    }
}
Sign up to request clarification or add additional context in comments.

Comments

4

I posted this issue on the ASP.NET Caching GitHub project.

There is already a set of IDistributedCache extension methods here that we can potentially add to (It's all open source so we can fix this ourselves and submit a pull request :)).

Note that BinaryFormatter is not available in .NET Core (Not sure if it ever will be) so I wrapped it with #if DNX451 and included the BinaryWriter and BinaryReader extension methods which both runtimes can use. Note also that if you are using the BinaryFormatter extension methods, you will need to add the [Serializable] attribute to your entities you want to serialize.

public static class CacheExtensions
{
    // Omitted existing extension methods...

    public static async Task<bool> GetBooleanAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadBoolean();
        }
    }

    public static async Task<char> GetCharAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadChar();
        }
    }

    public static async Task<decimal> GetDecimalAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadDecimal();
        }
    }

    public static async Task<double> GetDoubleAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadDouble();
        }
    }

    public static async Task<short> GetShortAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadInt16();
        }
    }

    public static async Task<int> GetIntAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadInt32();
        }
    }

    public static async Task<long> GetLongAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadInt64();
        }
    }

    public static async Task<float> GetFloatAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadSingle();
        }
    }

    public static async Task<string> GetStringAsync(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryReader binaryReader = new BinaryReader(memoryStream);
            return binaryReader.ReadString();
        }
    }

    public static Task SetAsync(this IDistributedCache cache, string key, bool value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, char value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, decimal value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, double value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, short value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, int value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, long value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, float value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

    public static Task SetAsync(this IDistributedCache cache, string key, string value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            binaryWriter.Write(value);
            bytes = memoryStream.ToArray();
        }
        return cache.SetAsync(key, bytes, options);
    }

#if DNX451
    public static async Task<T> GetAsync<T>(this IDistributedCache cache, string key)
    {
        byte[] bytes = await cache.GetAsync(key);
        using (MemoryStream memoryStream = new MemoryStream(bytes))
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            return (T)binaryFormatter.Deserialize(memoryStream);
        }
    }

    public static Task SetAsync<T>(this IDistributedCache cache, string key, T value)
    {
        return SetAsync(cache, key, value, new DistributedCacheEntryOptions());
    }

    public static Task SetAsync<T>(this IDistributedCache cache, string key, T value, DistributedCacheEntryOptions options)
    {
        byte[] bytes;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, value);
            bytes = memoryStream.ToArray();
        }

        return cache.SetAsync(key, bytes, options);
    }
#endif
}

1 Comment

Thanks Muhammad, you save my day. is there any update or GitHub repo for it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.