0

I have a project where we use a service architecture, with Ninject to resolve our services. Nearly all our services use async.

Now, I want to add caching to a couple of the services.

I have never worked with caching in async methods, so I am unsure how to do it. I just know that I currently get this error as HttpContext.Current is null:

enter image description here

So, what strategy should I go for to make this work?

My IoC setup: NinjectWebCommon.cs:

 private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<Context>().To<Context>().InRequestScope();
            kernel.Bind<UserManager<User>>().To<UserManager>().InTransientScope();
            kernel.Bind<IUserTokenProvider<User, string>>().ToMethod((x) =>
            {
                var provider = OwinConfig.DataProtectionProvider;
                return new DataProtectorTokenProvider<User>(provider.Create("ASP.NET Identity"));
            }).InTransientScope();

            kernel.Bind<ICacheService>().To<HttpCache>();    
            kernel.Bind<IUserService>().To<UserService>();
            // Lots of nice services

        }

My ICacheService:

public interface ICacheService
{
    object GetById(string cacheKey);
    object Create(string cacheKey, object obj);
    object Create(string cacheKey, object obj, DateTime expire);
    void Delete(string cacheKey);
    void DeleteByContaining(string containing);
    string GetCacheKey(string methodName, string value);
    bool ContainsKey(string cacheKey);
}

CacheService:

public class HttpCache : ICacheService
    {
        private static readonly ILog logger = LogManager.GetLogger(typeof (HttpCache));


        public object GetById(string cacheKey)
        {
            if (ContainsKey(cacheKey))
            {
                return HttpContext.Current.Cache[cacheKey];
            }

            return null;
        }

        public object Create(string cacheKey, object obj)
        {
            return Create(cacheKey, obj, DateTime.UtcNow.AddHours(5));
        }

        public object Create(string cacheKey, object obj, DateTime expire)
        {
            if (!ContainsKey(cacheKey))
            {
                HttpContext.Current.Cache.Insert(cacheKey,
                    obj,
                    null,
                    expire,
                    Cache.NoSlidingExpiration);
            }
            return GetById(cacheKey);
        }

        public void Delete(string cacheKey)
        {
            HttpContext.Current.Cache.Remove(cacheKey);
        }

        public void DeleteByContaining(string containing)
        {
            List<string> deleteList = new List<string>();
            HttpContext oc = HttpContext.Current;

            // find all cache keys in the system... maybe insane? I don't know lol
            IDictionaryEnumerator en = oc.Cache.GetEnumerator();
            while (en.MoveNext())
            {
                var k = en.Key.ToString();
                if (k.Contains(containing))
                {
                    deleteList.Add(k);
                }
            }

            foreach (var del in deleteList)
            {
                Delete(del);
            }
        }


        public bool ContainsKey(string cacheKey)
        {
            return HttpContext.Current.Cache[cacheKey] != null;
        }

        public string GetCacheKey(string methodName, string value)
        {
            return string.Format("{0}_{1}", methodName, value);
        }
    }

My caller:

 public async Task<City> GetById(int id)
        {

            var cacheKey = _cacheService.GetCacheKey(MethodBase.GetCurrentMethod().Name, id.ToString());
            if (!_cacheService.ContainsKey(cacheKey))
            {
                var city = await _db.Cities.FirstAsync(c => c.Id == id);


                _cacheService.Create(cacheKey, city);
                return city;
            }
            return (City)_cacheService.GetById(cacheKey);
        }
4
  • @AlexeiLevenkov Probably because I am stupid - there is a very high probability! Thanks for link Commented Oct 5, 2015 at 22:02
  • @AlexeiLevenkov So, you say, if I use the System.Web.HttpRuntime.Cache directly, I'll be safe? Please post an answer - you're also welcome to tease me for stupidity :D Commented Oct 5, 2015 at 22:03
  • Alternatively, use System.Runtime.Caching. Commented Oct 5, 2015 at 22:27
  • @AlexeiLevenkov The link you sent helped me. And I think that deserves to be an answer. Can you make an answer I can accept? Commented Oct 6, 2015 at 10:45

1 Answer 1

3

The HttpContext.Current.Cache is one of several shortcuts to access ASP.Net global Cache object (another one for request lifitime is Page.Cache). If you need to access cache outside request use HttpRuntime.Cache. (Also covered in Accessing the ASP.NET Cache from a Separate Thread?)

Notes:

  • Note that the fact you don't get HttpContext.Current could be symptom of bigger issue - you may expect code to run with HttpContext set but it is not, along with culture... Whether it is actually problem for you is open question, but at least check if you code does not unexpectedly use .ConfigureAwait(false), Task.Run or schedule work on thread pool manually.
  • While adding/reading single item from cache is thread safe be careful when you expect multiple entries to be consistent - you may need locking.
  • Be careful not to put user-specific data in the cache, especially if your caching is at IOC level and you have no direct information whether result is user-specific or not.
Sign up to request clarification or add additional context in comments.

Comments

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.