With ASP.NET Core 9.0, we have access to a new Caching API: HybridCache, and it's designed to replace both
IDistributedCache
and
IMemoryCache.
Let's go through with an example code.
I have the following code:
public interface IDataService
{
Task<ConfigurationData> GetConfigurationData(CancellationToken cancellationToken = default);
}
public abstract class DataServiceBase : IDataService
{
protected const string CacheKey = "configuration-cache-key";
public abstract Task<ConfigurationData> GetConfigurationData(CancellationToken cancellationToken = default);
protected async Task<ConfigurationData> GetConfigurationFromSource(CancellationToken cancellationToken = default)
{
return await Task.FromResult(new ConfigurationData
{
SomeConfig1 = "Some Config1",
SomeConfig2 = "Some Config2"
});
}
}
First, let's see how
IDistributedCache works and then let's see how
HybridCache can simplify it.
public class DataServiceWithIDistributedCache(IDistributedCache distributedCache)
: DataServiceBase
{
public async override Task<ConfigurationData> GetConfigurationData(CancellationToken cancellationToken = default)
{
byte[]? bytes = await distributedCache.GetAsync(CacheKey, cancellationToken); // Try to get from cache.
// Cache hit; return the deserialized data.
if (bytes is not null)
{
return JsonSerializer.Deserialize<ConfigurationData>(bytes)!;
}
// Cache miss; get the data from the real source and cache it.
ConfigurationData configurationData = await GetConfigurationFromSource(cancellationToken);
bytes = JsonSerializer.SerializeToUtf8Bytes(configurationData);
await distributedCache.SetAsync(CacheKey, bytes, cancellationToken);
return configurationData;
}
}
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "<ConnectionString>";
});
builder.Services.AddScoped<IDataService, DataServiceWithIDistributedCache>();
Now here in DataServiceWithIDistributedCache, we are first checking the cache to see whether the item exists, if it is we
return the item from the cache, if not we retrieve the item from the original
source, cache it, and then return the item.
There are potential problems here. Say the item does not exist in the cache
and more than one thread attempts to read Configuration simultaneously. In
that case, multiple threads are going to cache the item.
On top of that, we had to first check whether the item exists in the cache,
and if not, we need to retrieve the item from original source, and cache it.
When retrieving an item from a cache, almost all the time, that's something we
will have to do.
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.7.24406.2" />
public class DataServiceWithHybridCache(HybridCache hybridCache)
: DataServiceBase
{
public async override Task<ConfigurationData> GetConfigurationData(CancellationToken cancellationToken = default)
{
return await hybridCache.GetOrCreateAsync(
CacheKey,
factory: async token => await GetConfigurationFromSource(token),
cancellationToken: cancellationToken
);
}
}
Now we need to register HybridCache.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "<ConnectionString>";
});
builder.Services.AddScoped<IDataService, DataServiceWithHybridCache>();
builder.Services.AddHybridCache(options =>
{
// TODO: customize options if required
});
So here HybridCache is created with a primary cache and a secondary cache.
|
HybridCache |
HybridCache by default uses MemoryCache for its primary cache, and for secondary
cache, it uses any IDistributedCache implementation that is configured. Since I have Redis configured,
Redis is registered as the secondary cache here.
HybridCache exposes GetOrCreateAsync with two overloads, taking a key and:
- A factory method.
- State, and a factory method.
The method uses the key to retrieve the item from the primary cache. If it's not there (cache miss), it then checks the
secondary cache (if it's configured). If it doesn't find the item there
(another cache miss), it calls the factory method to get the item from the original data source. It then caches the item in both primary and secondary caches.
The factory method is never called if the item is found in the primary or
secondary cache (a cache hit).
The HybridCache service ensures that only one concurrent caller for a given
key calls the factory method, and all other callers wait for the result of
that call. The CancellationToken passed to GetOrCreateAsync represents the combined cancellation of all concurrent callers.
I love this.
Hope this helps.
More read:
Happy Coding.
Regards,
Jaliya