.NET 6 Implementing MemoryCache – 2

Previously on MemoryCache.

Let’s this time generate a base class and extend them as business objects!

Our application needs to get some Configurations from a source and we need to cache these configurations (I will pretend there was a source)

First create a base class of our cache,

public class CacheBase<TEntity> where TEntity : class
{
     private readonly string key = typeof(TEntity).Name; // need a custom name to register cachename
     private MemoryCacheEntryOptions options { get; set; }
     private readonly IMemoryCache _cache;

     public CacheBase(IMemoryCache cache) // resolve from IoC 
     {
        _cache = cache;
     }

     protected virtual MemoryCacheEntryOptions SetPolicy() // override will decide policy
     {
         return null;
     }

     protected virtual List<TEntity> GetEntityList() // override will decide the objects
     {
         return null;
     }
}

Now let’s create our ConfigurationCache and Configuration classes.

public class Configuration
{
    public string Code { get; set; }
    public string Value { get; set; }
} 
public class ConfigurationCache : CacheBase<Configuration>
{
     public ConfigurationCache(IMemoryCache cache) : base(cache)
     {
     }
}

Accessing configuration cache items by using cachebase will provide centrality. There might be more and each of them will be derieved from this base.

Now we need to set cache policy and cache items. What we do is overriding SetPolicy() and GetEntityList()

What we will do is,

protected override List<Configuration> GetEntityList()
{
    return new List<Configuration> {
        new Configuration { Code = "CanLogin", Value = "1" },
        new Configuration{ Code= "LogLevel",Value = "Warning"},
        new Configuration{ Code= "Environment",Value = "UAT"}
    };
}

protected override MemoryCacheEntryOptions SetPolicy()
{
    MemoryCacheEntryOptions options = new MemoryCacheEntryOptions();
    options.AbsoluteExpiration = DateTime.Now.AddSeconds(15); // cache items will be removed after 15 seconds

    return options;
}

There will be 3 items in our cache and they will be alive for only 15 seconds.

These overrides should be called at our base class, so lets wrap them into a single method and call inside constructor.

I will name this method as ReloadCache. So in CacheBase I added this method,

public void ReloadCache()
{
    IEnumerable<TEntity> tempList = GetEntityList();
    options = SetPolicy();
    _cache.Set(key, tempList, options); // this will add our 3 items to cache with ConfigurationCache key
}

And called inside constructor,

public CacheBase(IMemoryCache cache) // resolve from IoC 
{
    _cache = cache;
    ReloadCache();
}

Okay but how will we reach the registered items?

We can write a method that returns cache items to our base,

public bool TryGetList(out List<TEntity> valueList)
{
    valueList = (List<TEntity>)_cache.Get(key);

    if (options != null && options.AbsoluteExpiration < DateTime.Now) // if expired
    {
        ReloadCache(); // try load our 3 items again

        if (!TryGetList(out valueList)) // try select from cache again
        {
            return false; // if it is failed we can't do anything more
        }
    }

    return valueList != null;
}

Items can be expired while we are trying to reach them. options.AbsoluteExpiration < DateTime.Now is checking this condition and if that happened.

It will call ReloadCache again!

Before we run the application we need to register activate memory cache usage. And also register our ConfigurationCache as singleton.

builder.Services.AddMemoryCache();
builder.Services.AddSingleton<ConfigurationCache>();

Now we can try what we have done!

Looks just fine,

Let’s execute after 15 seconds!

Items are Expired! But immediatly reloaded and returned like above.

You can also use a data provider class to load your cache,

First we need to addIServiceScopeFactory to our constructor,

private readonly IServiceScopeFactory _factory;
public ConfigurationCache(IServiceScopeFactory factory, IMemoryCache cache) : base(cache)
{
    this._factory = factory;
}

We have to use IServiceScopeFactory because our Cache is registered as Singleton and it has the longest lifetime. If we resolve our Scoped or Transientservices in the constructor, we will made them singleton indirectly. So we need to resolve service/repository dependencies under scope factory’s scope!

protected override List<Configuration> GetEntityList()
{
    using (IServiceScope scope = _factory.CreateScope()) // ConfigurationService will be disposed, no lifetime violated
    {
        var configurationService = scope.ServiceProvider.GetRequiredService<ConfigurationService>();
        return configurationService.GetConfigurations(); // service will provide configurations!
    }
}

Looks solid! 🙂