How can I cache objects in ASP.NET MVC?

I would like to cache objects in ASP.NET MVC. I have a BaseController that I want all controllers to inherit from. BaseController has a User property that simply captures user data from the database so that I can use it in the controller or pass it to the views.

I would like to cache this information. I use this information on every page, so there is no need to go to each page with a request for each page.

I would like something like:

 if(_user is null) GrabFromDatabase StuffIntoCache return CachedObject as User 

How to implement simple caching in ASP.NET MVC?

+45
caching asp.net-mvc
Jan 14 '09 at 23:04
source share
8 answers

You can still use the cache (common to all responses) and the session (unique to each user) for storage.

I like the following "try get from cache / create and store" template (C # -like pseudo-code):

 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 should use it like this:

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

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

+63
Jan 15 '09 at 1:20
source share

I took the β€œAnswer” and modified it to make the CacheExtensions class static and suggest a slight change so that Func<T> be 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 this step again to implement a test session solution that extends the abstract class System.Web.HttpSessionStateBase.

 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; } } 
+58
Jun 09 '09 at 8:35
source share

If you want it to be cached for the length of the request, put this in your base controller 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 longer, use replacing the ControllerContext call with one on Cache []. If you decide to use the Cache object for caching for longer, you will need to use a unique cache key, as it will be used by all users / users.

+4
Jan 14 '09 at 23:11
source share

I like to hide the fact that data is cached in the repository. You can access the cache through the HttpContext.Current.Cache property and save user information using "User" + id.ToString () as a key.

This means that all access to user data from the repository will use cached data, if available, and does not require code changes in the model, controller or view.

I used this method to fix serious performance issues on a system that requested a database for each user property and reduced page load time from minutes by one digit seconds.

+3
Jan 15 '09 at 2:58
source share

@njappboy: Good implementation. I would defer the call to Generator( ) until the last crucial moment. this way you can also cache method calls.

 /// <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; } 
+3
Aug 11 '10 at
source share

Unless you need special ASP.NET caching invalidation features, static fields are pretty good, lightweight, and easy to use. However, as soon as you need advanced features, you can switch to the ASP.NET Cache object for storage.

The approach I'm using is to create a property and a private field. If the field is null , the property will populate it and return. I also provide an InvalidateCache method that manually sets the field to null . The advantage of this approach is that the caching mechanism is encapsulated in the property, and you can switch to another approach if you want.

+2
Jan 14 '09 at 23:09
source share

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

 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; } } 
+1
Oct 28 '11 at 15:50
source share

A few other answers here do not concern the following:

  • cache stampede
  • double check lock

This can lead to the fact that the generator (which can take a lot of time) runs more than once in different threads.

Here is my version that should not suffer from this problem:

 // using System.Web.Caching; public static class CacheExtensions { private static 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.Now.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration); } } } return (T)result; } } 
+1
Feb 24 '17 at 16:19
source share



All Articles