2

I have an ASP.NET core application and I am attempting to use Redis Cache - however I am getting an Error saying cannot access a disposed object so I must not have set up my Cache classes correctly. I have pulled the Cache service classes into my own Nuget repository so it can be used by other applications passing in a different Db Number from appsettings.json in other applications

I am using inbuilt .NET Core DI to register the cache service as below:

services.AddTransient<ICacheService, CacheService>();

The cache service is used in my Application then as so:

var dataFromCache = _cacheService.TryGetCachedObject<List<MyObject>>(cacheKey);

The implentation of my Cache service in the nuget pacakge is below:

public class CacheService : ICacheService, IDisposable

{
    public virtual T TryGetCachedObject<T>(string cacheKey)

    {

        if (RedisCacheHandler.Cache.KeyExists(cacheKey))

        {

            return JsonConvert.DeserializeObject<T>(RedisCacheHandler.Cache.StringGet(cacheKey));

        }

        return default(T);

    }
    //other metjhods omitted for brevity

I get the cannot access disposed object exception on the line if (RedisCacheHandler.Cache.KeyExists(cacheKey))

My redis cache handler class is below (the commented out lines if things I have been trying with no success.

public static class RedisCacheHandler

{

    private static Lazy<ConnectionMultiplexer> _lazyConnection;

    private static ConnectionMultiplexer Connection => _lazyConnection.Value;

    //private static CacheSettings _cacheSettings;



    public static IDatabase Cache { get; set; }



    //public static IDatabase Cache => Connection.GetDatabase(Convert.ToInt32(_cacheSettings.DbNumber));



    //private static readonly Lazy<ConnectionMultiplexer> LazyConnection

    //    = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(_cacheSettings.Connection));



    //public static ConnectionMultiplexer Connection => LazyConnection.Value;





    public static void AddRedisCacheHandler(this IServiceCollection services, IConfiguration configuration)

    {

        var cacheSettings = new CacheSettings();

        configuration.Bind("CacheSettings", cacheSettings);



        //_cacheSettings = cacheSettings;



        _lazyConnection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(cacheSettings.Connection));



        Cache = Connection.GetDatabase(Convert.ToInt32(cacheSettings.DbNumber));

    }

}

And I was calling the AddRedisCacheHandler method in asp.net core startup class in the ConfigureServices method as below:

services.AddRedisCacheHandler(Configuration);

EDIT

The usage of this is that I hit an API controller to go get reference data. The API controller calls to the Service Layer which then will check if the data is in cache and retrieve from there else will go get data from DB and set it in the cache for 24 hours

    private readonly IMyService _myService

    public MyController(IMyService myService)
    {
        _myService = myService;
    }

    [Route("SomeReferenceData")]
    public IEnumerable<SomeDto> GetReferenceData()
    {
        var data = _myService.GetRefData();
         //do stuff and return
    }

At the service layer then the code is:

public class MyService : IMyService
{
    private readonly ICacheService _cacheService;
    private readonly CacheSettings _cacheSettings;    

    public MyService(CacheSettings cacheSettings, ICacheService cacheService)
    {
        _cacheSettings = cacheSettings;
        _cacheService = cacheService;
    }

    public virtual IEnumerable<MyObject> GetRefData()
    {

        string cacheKey = CachingConstants.CacheKey;

        var data = _cacheService.TryGetCachedObject<List<MyObject>>(cacheKey);

        if (data != null)
        {
            return data;
        }

        //go get data from db

        _cacheService.SetCachedObject<IEnumerable<MyObject>>(cacheKey, countries, Convert.ToInt32(_cacheSettings.DurationLongMinutes));

        return data;
    }

From startup I am calling the below to Register all the dependencies including the cache service:

services.RegisterServiceDependencies();

public static class ServiceConfig
{
    public static void RegisterServiceDependencies(this IServiceCollection services)
    {
        services.AddTransient<ICacheService, CacheService>();
        //lots of other services
5
  • It's transient, so you're getting an instance and after your controller method exits that instance is getting disposed. You're holding onto a reference to that instance after it has been disposed. You should be getting a reference, using it, and disposing of it yourself, without keeping it around. Have you stuck it in a static property or something? Commented Jan 24, 2019 at 15:46
  • should I be registering the CacheService as a singleton then? No its not in a static property - will edit the question with more context on the CacheService usage Commented Jan 24, 2019 at 15:56
  • though the class where I am registering the cache service as a transient is a static class Commented Jan 24, 2019 at 16:03
  • Do you need to do all this dancing around the redis cache? Just new up your IDatabase implementation when you need it and dispose of it when done. Commented Jan 24, 2019 at 16:13
  • @Will - there could be an argument for the KISS principle - but I'd like to try and get it working with my Nuget pacakge and current approach so I can drop the NuGet package into multiple applications and re-use the same code and just pass in a different Redis DB Number (think it has 16) for each of my 5 applications Commented Jan 24, 2019 at 16:20

1 Answer 1

4

You're killing yourself here. Literally none of this is necessary. ASP.NET Core has built-in support for distributed caching, including using Redis as a provider. In your Startup.cs just do:

services.AddDistributedRedisCache(options =>
{
    options.Configuration = "localhost";
    options.InstanceName = "SampleInstance";
});

Then, when you need to utilize the cache, just inject IDistributedCache. If you want a self-contained way to automatically serialize/deserialize values in the cache, you can simply add some extension methods:

public static class IDistributedCacheExtensions
{
    public static async Task<T> GetAsync<T>(this IDistributedCache cache, string key) =>
        JsonConvert.DeserializeObject<T>(await cache.GetStringAsync(key));

    public static Task Set<T>(this IDistributedCache cache, string key, T value) =>
        cache.SetStringAsync(key, JsonConvert.SerializeObject(value));
}

If you insist on having a separate CacheService class for this, then merely inject IDistributedCache into that, and then do your work there.

Sign up to request clarification or add additional context in comments.

4 Comments

Chris..that makes a lot more sense. I'll give that a go..And I guess no reason why I can't encapsulate the adding of the DistribtedRedisCache call out into my Nuget package similar to how I was calling the Add handler method anyway
It's better that way anyways, as it increases the utility of your library. What if a consumer doesn't want to use Redis? Before, they're flat-out of luck, but if your library depends only on IDistributedCache, then the consumer can plugin whatever provider they like.
Ok I'll give that a go and see if I can get it wired up. I can't promise I won't come up with a new question at some point.... :)
Hi Chris - I am now injecting IDistributedCache into CacheService - how would I write an extension method so I can do code similar to where I had RedisCcaheHandler.Cache.KeyExists(cacheKey)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.