Søren
Søren

Reputation: 6787

How to disable ASP.NET Core Caching (FusionCache) for the test scenarios?

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

Answers (2)

Jody Donetti
Jody Donetti

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

Søren
Søren

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 with ICacheService.

  1. Have a setting in appsettings.json
{
  "Caching": {
    "Enabled": true
  }
  // other settings...
}

  1. Create class and map configuration:
public class CacheConfig
{
    public bool Enabled { get; set; }
}

  1. The interface:
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);

}
  1. The implementation based on FusionCache:
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);
}
  1. The implementation with no-cache:
public class NoOpCacheService : ICacheService
{
    public async ValueTask<TValue?> GetOrSetAsync<TValue>(string key,
        Func<CancellationToken, Task<TValue?>> factory,
        CancellationToken cancellationToken = default) =>
        await factory(cancellationToken);
}
  1. And the wire-up at the 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

Related Questions