Creating Singleton CacheManager in Asp.Net Core

Viewed 731

I am trying to create Singleton CacheManager class that has dependency on IMemoryCache.

public class CacheManager:ICacheManager
{
   private readonly IMemoryCache _cache;
   
   public CacheManager(IMemoryCache cache)
   {
     _cache = cache;
   }
   
   public void LoadCache(MyData data)
   {
        // load cache here at startup from DB
   }

}

I also have a Scoped service that retrives data from the database

public class LookupService:ILookupService
{
   private readonly MyDatabaseContext _dbContext;
   
   public class LookupService(MyDatabaseContext dbContext)
   {
      _dbContext = dbContext;
   }
  
  public void Dispose()
  {
     //Dispose DBContext here
  }
  
   // some async methods that returns lookup collection    
}

Register these services in Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        // EF
        services.AddDbContext<MyDatabaseContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        // domain services            
        services.AddScoped<ILookupService, LookupService>();
        
        services.AddMemoryCache();
        
        // singleton
        services.AddSingleton<CacheManager>(sp=> 
        { 
            using(var scope = sp.CreateScope())
            {
                using (var service = scope.ServiceProvider.GetService<ILookupService>())
                {
                    how do i create cacheManager instance by injecting IMemoryCache and also register callback function
                }
            }
        });                 
    }
    

ILookupService is registered as Scoped service becuase it has dependency on DBContext which is also (by default) registered with Scoped lifetime. I do not want to change lifetime of these services.

However I want CacheManager to be registered as Singleton, that means I cannot inject ILookupService as dependency into CacheManager.

So here is my possible solution to create & register singleton instance of CacheManager

        services.AddSingleton<CacheManager>(sp=> 
        { 
            using(var scope = sp.CreateScope())
            {
                using (var lookupService = scope.ServiceProvider.GetService<ILookupService>())
                {
                    var cache = scope.ServiceProvider.GetService<IMemoryCache>();
                    var manger =  new CacheManager(cache);
                    manger.LoadCache(lookupService.GetData());
                    return manger;
                }
            }
        });   
        

Not sure this is the best way to create CacheManager. How do I implement a callback function to re-populate CacheEntry if it becomes null?

3 Answers

I guess I would simply configure services.AddSingleton<CacheManager>(); (CacheManager having a default constructor)

After configuring all of the DI dependencies and having a serviceprovider, get the Cachemanager singleton and initialize it with LoadCache. (so let DI create "empty" singleton cachemanager, but initialize immediately somewhere in startup of application)

var cachemanager = scope.ServiceProvider.Get<CacheManager>();
var lookupService = scope.ServiceProvider.Get<ILookupService>();
var cache = scope.ServiceProvider.Get<IMemoryCache>();
cachemanager.Cache = cache;
cachemanager.LoadCache(lookupService.GetData());

Looks like the underlying issue is that ILookupService cannot be resolved until runtime and requests start coming in. You need to create CacheManager before this.

DI COMPOSITION

This should be done when the app starts - as in this class of mine. Note the different lifetimes for different types of object but I just focus on creating the objects rather than interactions.

DI RESOLUTION

.Net uses a container per request pattern where scoped objects are stored against the HttpRequest object. So a singleton basically needs to ask for the current ILookupService, which is done by calling:

container.GetService<ILookupService>

So include the DI container as a constructor argument to your CacheManager class and you will be all set up. This is the service locator pattern and is needed to meet your requirement.

An alternative per request resolution mechanism is via the HttpContext object as in this class, where the following code is used:

IAuthorizer authorizer = (IAuthorizer)this.Context.RequestServices.GetService(typeof(IAuthorizer));

SUMMARY

The important thing is to understand the above design pattern, and you can then apply it to any technology.

register Cache service as singleton, try below code

 public class CacheService : ICacheService
    {
        private ObjectCache _memoryCache;

        /// <summary>
        /// Initializes a new instance of the <see cref="CacheService"/> class.
        /// </summary>
        public CacheService()
        {
            this._memoryCache = System.Runtime.Caching.MemoryCache.Default;
        }
}
Related