How can I cache objects in ASP.NET MVC?

后端 未结 8 1003
一生所求
一生所求 2020-12-04 06:25

I\'d like to cache objects in ASP.NET MVC. I have a BaseController that I want all Controllers to inherit from. In the BaseController there is a User

相关标签:
8条回答
  • 2020-12-04 06:33

    You can still use the cache (shared among all responses) and session (unique per user) for storage.

    I like the following "try get from cache/create and store" pattern (c#-like pseudocode):

    public static class CacheExtensions
    {
      public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
      {
        var result = cache[key];
        if(result == null)
        {
          result = generator();
          cache[key] = result;
        }
        return (T)result;
      }
    }
    

    you'd use this like so:

    var user = HttpRuntime
                  .Cache
                  .GetOrStore<User>(
                     $"User{_userId}", 
                     () => Repository.GetUser(_userId));
    

    You can adapt this pattern to the Session, ViewState (ugh) or any other cache mechanism. You can also extend the ControllerContext.HttpContext (which I think is one of the wrappers in System.Web.Extensions), or create a new class to do it with some room for mocking the cache.

    0 讨论(0)
  • @njappboy: Nice implementation. I would only defer the Generator( ) invocation until the last responsible moment. thus you can cache method invocations too.

    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="Cache">calling object</param>
    /// <param name="Key">Cache key</param>
    /// <param name="Generator">Func that returns the object to store in cache</param>
    /// <returns></returns>
    /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
    public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator )
    {
        return Cache.GetOrStore( Key, Generator, DefaultCacheExpiration );
    }
    
    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="Cache">calling object</param>
    /// <param name="Key">Cache key</param>
    /// <param name="Generator">Func that returns the object to store in cache</param>
    /// <param name="ExpireInMinutes">Time to expire cache in minutes</param>
    /// <returns></returns>
    public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator, double ExpireInMinutes )
    {
        var Result = Cache [ Key ];
    
        if( Result == null )
        {
            lock( Sync )
            {
                if( Result == null )
                {
                    Result = Generator( );
                    Cache.Insert( Key, Result, null, DateTime.Now.AddMinutes( ExpireInMinutes ), Cache.NoSlidingExpiration );
                }
            }
        }
    
        return ( T ) Result;
    }
    
    0 讨论(0)
  • 2020-12-04 06:38

    If you want it cached for the length of the request, put this in your controller base class:

    public User User {
        get {
            User _user = ControllerContext.HttpContext.Items["user"] as User;
    
            if (_user == null) {
                _user = _repository.Get<User>(id);
                ControllerContext.HttpContext.Items["user"] = _user;
            }
    
            return _user;
        }
    }
    

    If you want to cache for longer, use the replace the ControllerContext call with one to Cache[]. If you do choose to use the Cache object to cache longer, you'll need to use a unique cache key as it will be shared across requests/users.

    0 讨论(0)
  • 2020-12-04 06:41

    A couple of the other answers here don't deal with the following:

    • cache stampede
    • double check lock

    This could lead to the generator (which could take a long time) running more than once in different threads.

    Here's my version that shouldn't suffer from this problem:

    // using System;
    // using System.Web.Caching;
    
    // https://stackoverflow.com/a/42443437
    // Usage: HttpRuntime.Cache.GetOrStore("myKey", () => GetSomethingToCache());
    
    public static class CacheExtensions
    {
        private static readonly object sync = new object();
        private static TimeSpan defaultExpire = TimeSpan.FromMinutes(20);
    
        public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator) =>
            cache.GetOrStore(key, generator, defaultExpire);
    
        public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator, TimeSpan expire)
        {
            var result = cache[key];
            if (result == null)
            {
                lock (sync)
                {
                    result = cache[key];
                    if (result == null)
                    {
                        result = generator();
                        cache.Insert(key, result, null, DateTime.UtcNow.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration);
                    }
                }
            }
            return (T)result;
        }
    }
    
    0 讨论(0)
  • 2020-12-04 06:44

    Implementation with a minimal cache locking. The value stored in the cache is wrapped in a container. If the value is not in the cache, then the value container is locked. The cache is locked only during the creation of the container.

    public static class CacheExtensions
    {
        private static object sync = new object();
    
        private class Container<T>
        {
            public T Value;
        }
    
        public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, TimeSpan slidingExpiration)
        {
            return cache.GetOrStore(key, create, Cache.NoAbsoluteExpiration, slidingExpiration);
        }
    
        public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration)
        {
            return cache.GetOrStore(key, create, absoluteExpiration, Cache.NoSlidingExpiration);
        }
    
        public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
        {
            return cache.GetOrCreate(key, x => create());
        }
    
        public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<string, TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
        {
            var instance = cache.GetOrStoreContainer<TValue>(key, absoluteExpiration, slidingExpiration);
            if (instance.Value == null)
                lock (instance)
                    if (instance.Value == null)
                        instance.Value = create(key);
    
            return instance.Value;
        }
    
        private static Container<TValue> GetOrStoreContainer<TValue>(this Cache cache, string key, DateTime absoluteExpiration, TimeSpan slidingExpiration)
        {
            var instance = cache[key];
            if (instance == null)
                lock (cache)
                {
                    instance = cache[key];
                    if (instance == null)
                    {
                        instance = new Container<TValue>();
    
                        cache.Add(key, instance, null, absoluteExpiration, slidingExpiration, CacheItemPriority.Default, null);
                    }
                }
    
            return (Container<TValue>)instance;
        }
    }
    
    0 讨论(0)
  • 2020-12-04 06:49

    I took Will's answer and modified it to make the CacheExtensions class static and to suggest a slight alteration in order to deal with the possibility of Func<T> being null :

    public static class CacheExtensions
    {
    
        private static object sync = new object();
        public const int DefaultCacheExpiration = 20;
    
        /// <summary>
        /// Allows Caching of typed data
        /// </summary>
        /// <example><![CDATA[
        /// var user = HttpRuntime
        ///   .Cache
        ///   .GetOrStore<User>(
        ///      string.Format("User{0}", _userId), 
        ///      () => Repository.GetUser(_userId));
        ///
        /// ]]></example>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache">calling object</param>
        /// <param name="key">Cache key</param>
        /// <param name="generator">Func that returns the object to store in cache</param>
        /// <returns></returns>
        /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
        public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator ) {
            return cache.GetOrStore( key, (cache[key] == null && generator != null) ? generator() : default( T ), DefaultCacheExpiration );
        }
    
    
        /// <summary>
        /// Allows Caching of typed data
        /// </summary>
        /// <example><![CDATA[
        /// var user = HttpRuntime
        ///   .Cache
        ///   .GetOrStore<User>(
        ///      string.Format("User{0}", _userId), 
        ///      () => Repository.GetUser(_userId));
        ///
        /// ]]></example>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache">calling object</param>
        /// <param name="key">Cache key</param>
        /// <param name="generator">Func that returns the object to store in cache</param>
        /// <param name="expireInMinutes">Time to expire cache in minutes</param>
        /// <returns></returns>
        public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator, double expireInMinutes ) {
            return cache.GetOrStore( key,  (cache[key] == null && generator != null) ? generator() : default( T ), expireInMinutes );
        }
    
    
        /// <summary>
        /// Allows Caching of typed data
        /// </summary>
        /// <example><![CDATA[
        /// var user = HttpRuntime
        ///   .Cache
        ///   .GetOrStore<User>(
        ///      string.Format("User{0}", _userId),_userId));
        ///
        /// ]]></example>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache">calling object</param>
        /// <param name="key">Cache key</param>
        /// <param name="obj">Object to store in cache</param>
        /// <returns></returns>
        /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
        public static T GetOrStore<T>( this Cache cache, string key, T obj ) {
            return cache.GetOrStore( key, obj, DefaultCacheExpiration );
        }
    
        /// <summary>
        /// Allows Caching of typed data
        /// </summary>
        /// <example><![CDATA[
        /// var user = HttpRuntime
        ///   .Cache
        ///   .GetOrStore<User>(
        ///      string.Format("User{0}", _userId), 
        ///      () => Repository.GetUser(_userId));
        ///
        /// ]]></example>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache">calling object</param>
        /// <param name="key">Cache key</param>
        /// <param name="obj">Object to store in cache</param>
        /// <param name="expireInMinutes">Time to expire cache in minutes</param>
        /// <returns></returns>
        public static T GetOrStore<T>( this Cache cache, string key, T obj, double expireInMinutes ) {
            var result = cache[key];
    
            if ( result == null ) {
    
                lock ( sync ) {
                    result = cache[key];
                    if ( result == null ) {
                        result = obj != null ? obj : default( T );
                        cache.Insert( key, result, null, DateTime.Now.AddMinutes( expireInMinutes ), Cache.NoSlidingExpiration );
                    }
                }
            }
    
            return (T)result;
    
        }
    
    }
    

    I would also consider taking this a step further to implement a testable Session solution that extends the System.Web.HttpSessionStateBase abstract class.

    public static class SessionExtension
    {
        /// <summary>
        /// 
        /// </summary>
        /// <example><![CDATA[
        /// var user = HttpContext
        ///   .Session
        ///   .GetOrStore<User>(
        ///      string.Format("User{0}", _userId), 
        ///      () => Repository.GetUser(_userId));
        ///
        /// ]]></example>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache"></param>
        /// <param name="key"></param>
        /// <param name="generator"></param>
        /// <returns></returns>
        public static T GetOrStore<T>( this HttpSessionStateBase session, string name, Func<T> generator ) {
    
            var result = session[name];
            if ( result != null )
                return (T)result;
    
            result = generator != null ? generator() : default( T );
            session.Add( name, result );
            return (T)result;
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题