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?
Three caching challenges to consider Caches take up space on the disk, so we have to assess whether the time we are saving is worth the amount of disk space used. Cached data might not be the most accurate, particularly for volatile real-time data. Therefore, volatile data should not be cached.
L1 cache, or primary cache, is extremely fast but relatively small, and is usually embedded in the processor chip as CPU cache. L2 cache, or secondary cache, is often more capacious than L1.
ASP.NET Core uses two caching techniques. In-memory caching uses the server memory to store cached data locally, and distributed caching distributes the cache across various servers. We'll explore them both below.
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
}
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With