Reputation: 6787
I'm using Fusion Cache in ASP.NET Core (v6.0) project.
I need to turn off the cache (globally) for some environments (in this case, tests).
I'm looking for the best practices.
Upvotes: 2
Views: 1217
Reputation: 558
FusionCache creator here.
Your approach definitely works and is reasonable, but what I'm thinking about is if it would make sense to have a ready-made "null/no-op implementation" of IFusionCache
directly, something that works as a simple pass-through like you did.
For your scenario it would mean not having to introduce a new ICacheService
abstraction, and keep depending on IFusionCache
without having to change the existing code.
Thoughts?
🔔 UPDATE (2023-07-09)
I've decided to officially add support for a NullFusionCache
(just like the NullLogger
in .NET itself), and I created an issue to track the development.
Btw, the development is already done, and tests have been created to make sure everything is fine.
Will release v0.22.0 in the next days, let me know what you think.
Thanks!
🔔 UPDATE (2023-07-10)
Hi! I just released v0.22.0 and the NullFusionCache
class is now included 🎉
Upvotes: 3
Reputation: 6787
I came up with some scenarios and I'll explain the solution I found most impressive.
I've created my own ICacheService
interface with the methods I needed and have two implementations: one using FusionCache and the other one bypassing the cache.
The choice between these two implementations would be made in the Startup
class.
Considering that I used to inject
IFusionCache
into all caching services, I have to replace them withICacheService
.
appsettings.json
{
"Caching": {
"Enabled": true
}
// other settings...
}
public class CacheConfig
{
public bool Enabled { get; set; }
}
public interface ICacheService
{
/// <summary>
/// Get the value of type <typeparamref name="TValue"/> in the cache for the specified <paramref name="key"/>: if not there, the <paramref name="factory"/> will be called and the returned value saved.
/// </summary>
/// <typeparam name="TValue">The type of the value in the cache.</typeparam>
/// <param name="key">The cache key which identifies the entry in the cache.</param>
/// <param name="factory">The function which will be called if the value is not found in the cache.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
/// <returns>The value in the cache, either already there or generated using the provided <paramref name="factory"/> .</returns>
public ValueTask<TValue?> GetOrSetAsync<TValue>(string key,
Func<CancellationToken, Task<TValue?>> factory,
CancellationToken cancellationToken = default);
}
public class FusionCacheService : ICacheService
{
private readonly IFusionCache _cache;
public FusionCacheService(IFusionCache cache)
{
_cache = cache;
}
public ValueTask<TValue?> GetOrSetAsync<TValue>(string key,
Func<CancellationToken, Task<TValue?>> factory,
CancellationToken cancellationToken = default)
=> _cache.GetOrSetAsync<TValue>(key, (_, ct) => factory(ct), default, null, cancellationToken);
}
public class NoOpCacheService : ICacheService
{
public async ValueTask<TValue?> GetOrSetAsync<TValue>(string key,
Func<CancellationToken, Task<TValue?>> factory,
CancellationToken cancellationToken = default) =>
await factory(cancellationToken);
}
Startup
:public static IServiceCollection AddCache(this IServiceCollection serviceCollection,
IConfiguration configuration)
{
var cacheSettings = new CacheConfig();
configuration.Bind(CacheConfig.SectionName, cacheSettings);
if (!cacheSettings.Enabled)
{
serviceCollection.AddSingleton<ICacheService, NoOpCacheService>();
return serviceCollection;
}
serviceCollection.AddSingleton<ICacheService, FusionCacheService>();
serviceCollection.AddMemoryCache();
serviceCollection.AddFusionCache()
.WithOptions(options =>
{
.
.
.
With this approach, I have more control over the behavior of the service as well as Decoupling, Reduced dependency, and Ease of testing.
Upvotes: 0