Berkay Yaylacı
Berkay Yaylacı

Reputation: 4513

How to resolve scoped service inside singleton object

I have MemoryCache objects (Application,Configuration etc) which I registered them as Singleton. Also there are scoped repositories which selects data from db to fill cache.

For example here is the Singleton registered class,

public class ApplicationCache : MultipleLoadCache<Application>
{
    public ApplicationCache() 
    {
            
    }
}

MultipleLoadCache overrides the CacheItemPolicy, (there is also SingleLoadCache),

public class MultipleLoadCache<TEntity> : SmartCache<TEntity> where TEntity : class
{
    public MultipleLoadCache()
    {
    }

    protected override CacheItemPolicy SetPolicy()
    {
        return new CacheItemPolicy()
        {
           AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(15)
        };
    }
 }

And base class is,

public class SmartCache<TEntity> : IDisposable where TEntity : class
{
      public bool TryGetList(IRepository<TEntity> repository, out List<TEntity> valueList)
      {
          valueList = null;
          lock (cacheLock)
          {
              GenerateCacheIfNotExists(repository, out valueList);
              if (valueList == null || valueList.Count == 0)
              {
                valueList = (List<TEntity>)_memoryCache.Get(key);
              }
          }

          return valueList != null;
      }

I know that scoped services can't be injected to singleton class. So I prefer to use method injection.

private void GenerateCacheIfNotExists(IRepository<TEntity> repository, out List<TEntity> list)
{
          list = null;
          if (!_memoryCache.Any(x => x.Key == key)) // if key not exists, get db records from repo.
          {
              IEnumerable<TEntity> tempList = repository.GetList();
              list = tempList.ToList();
              _cacheItemPolicy = SetPolicy();
              SetCacheList(list);
          }
      }
}

And at controller I try to get cache values, but this part seems wrong to me. If I try to get cache values, I shouldn't pass repository as parameter.

private readonly ApplicationCache _appCache;
public LogController(ApplicationCache appCache)
{
   _appCache = appCache;
}

[HttpPost]
[Route("Register")]
public List<Application> Register([FromServices] IApplicationRepository repository)
{
   List<Application> cf;
   _appCache.TryGetList(repository, out cf);

   return cf;
}

Also, by doing Method Injection. I am also unable to use RemovedCallBack event of CacheItemPolicy. Because, when callback triggers (reload cache), I need repository to get records from db again.

Is this design seems nice, what is the best design to do this by using callback events of MemoryCache?

Update 1-

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllers();
     services.AddMemoryCache();
     services.AddSingleton(x => new ApplicationCache());
     services.AddScoped<IApplicationRepository, ApplicationRepository>();
}

Thanks,

Upvotes: 1

Views: 893

Answers (1)

oguzhanozpinar
oguzhanozpinar

Reputation: 31

I had the same issue. Since static classes is compiled at the beginning it cannot inject the required services later. I figured it out by using IServiceScopeFactory.

You basically inject IServiceScopeFactory serviceScopeFactory in the constructer .

static SampleClass(IServiceScopeFactory serviceScopeFactory){  
   //serviceScopedFactory will act as Singleton, since it is a static class
   _serviceScopeFactory = serviceScopeFactory;
}

And use it like this in the method :

using (var scope = _serviceScopeFactory.CreateScope())
{
  var service = scope.ServiceProvider.GetRequiredService<IService>();
  //Here you can use the service. This will be used as Scoped since it will be 
  //recreated everytime it is called
   
}

Upvotes: 1

Related Questions