If all you want is to cache data, then OutputCache
is not the right mechanism. OutputCache
can be used to cache the HTML that is generated by an action method so it doesn't have to be regenerated. If you want to cache data, you can do so readily by using HttpContextBase.Cache
.
Also, I recommend against using a base controller class. This is sure to mean you will be mixing logic for feature a and feature b and feature c in the same base controller - you are creating a god object. MVC has a better way - filters which can be registered to run for all actions and can be used on specific actions if you use them in conjunction with filters.
Although, there might not be any point at all to using a global filter for your cache, because normally data is cached at the point it is requested, I have created a demo of how it could be done. Note that caching is a very broad subject that can be done numerous ways, but since you have provided NO INFORMATION about what is being cached, what the source of the data is, or how it is being used, I am only showing this as one possible way.
MyCacheFilter
Here we have an action filter that does the caching. Since action filters are guaranteed to run before the view, this is one way it could be done.
MyCacheFilter
does 3 things:
- Reads the data from the cache
- Checks whether the data exists, and if not, reloads the cache
- Adds a reference to the data object to the request cache, where it can be accessed from anywhere else in the application, including the view.
public class MyCacheFilter : IActionFilter
{
/// <summary>
/// The cache key that is used to store/retrieve your default values.
/// </summary>
private static string MY_DEFAULTS_CACHE_KEY = "MY_DEFAULTS";
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// Do nothing
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var cache = filterContext.HttpContext.Cache;
// This method is called for each request. We check to ensure the cache
// is initialized, and if not, load the values into it.
IDictionary<string, string> defaults =
cache[MY_DEFAULTS_CACHE_KEY] as IDictionary<string, string>;
if (defaults == null)
{
// The value doesn't exist in the cache, load it
defaults = GetDefaults();
// Store the defaults in the cache
cache.Insert(
MY_DEFAULTS_CACHE_KEY,
defaults,
null,
DateTime.Now.AddHours(1), // Cache for exactly 1 hour from now
System.Web.Caching.Cache.NoSlidingExpiration);
}
// Caching work is done, now return the result to the view. We can
// do that by storing it in the request cache.
filterContext.HttpContext.SetMyDefaults(defaults);
}
private IDictionary<string, string> GetDefaults()
{
// You weren't specific about where your defaults data is coming from
// or even what data type it is, but you can load it from anywhere in this method
// and return any data type. The type returned should either by readonly or thread safe.
var defaults = new Dictionary<string, string>
{
{ "value1", "testing" },
{ "value2", "hello world" },
{ "value3", "this works" }
};
// IMPORTANT: Cached data is shared throughout the application. You should make
// sure the data structure that holds is readonly so it cannot be updated.
// Alternatively, you could make it a thread-safe dictionary (such as ConcurrentDictionary),
// so it can be updated and the updates will be shared between all users.
// I am showing a readonly dictionary because it is the safest and simplest way.
return new System.Collections.ObjectModel.ReadOnlyDictionary<string, string>(defaults);
}
}
MyCacheFilter Usage
To use our cache filter and ensure the cache is populated before any view is show, we register it as a global filter. Global filters are great for keeping separate pieces of functionality in different classes, where they can be easily maintained (unlike a base controller).
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// Add our MyCacheFilter globally so it runs before every request
filters.Add(new MyCacheFilter());
filters.Add(new HandleErrorAttribute());
}
}
HttpContextBaseExtensions
To make the cached data type-safe for the rest of the application, here are a couple of handy extension methods.
/// <summary>
/// Extensions for convenience of using the request cache in views and filters.
/// Note this is placed in the global namespace so you don't have to import it in your views.
/// </summary>
public static class HttpContextBaseExtensions
{
/// <summary>
/// The key that is used to store your context values in the current request cache.
/// The request cache is simply used here to transfer the cached data to the view.
/// The difference between the request cache (HttpContext.Items) and HttpContext.Cache is that HttpContext.Items
/// is immediately released at the end of the request. HttpContext.Cache is stored (in RAM) for the length of
/// the timeout (or alternatively, using a sliding expiration that keeps it alive for X time after
/// the most recent request for it).
///
/// Note that by using a reference type
/// this is very efficient. We aren't storing a copy of the data in the request cache, we
/// are simply storing a pointer to the same object that exists in the cache.
/// </summary>
internal static string MY_DEFAULTS_KEY = "MY_DEFAULTS";
/// <summary>
/// This is a convenience method so we don't need to scatter the reference to the request cache key
/// all over the application. It also makes our cache type safe.
/// </summary>
public static string GetMyDefault(this HttpContextBase context, string defaultKey)
{
// Get the defaults from the request cache.
IDictionary<string, string> defaults = context.Items[MY_DEFAULTS_KEY] as IDictionary<string, string>;
// Get the specific value out of the cache that was requested.
// TryGetValue() is used to prevent an exception from being thrown if the key doesn't
// exist. In that case, the result will be null
string result = null;
defaults.TryGetValue(defaultKey, out result);
return result ?? String.Empty;
}
/// <summary>
/// This is a convenience method so we don't need to scatter the reference to the request cache key
/// all over the application. It also makes our cache type safe.
/// </summary>
internal static void SetMyDefaults(this HttpContextBase context, IDictionary<string, string> defaults)
{
context.Items[MY_DEFAULTS_KEY] = defaults;
}
}
Usage
Finally, we get to using the data from a view. Since we have extension methods on the HttpContextBase
object, all we need to do is access it through the view and call our extension method.
<p>
value1: @this.Context.GetMyDefault("value1")<br />
value2: @this.Context.GetMyDefault("value2")<br />
value3: @this.Context.GetMyDefault("value3")<br />
</p>
I have created a working demo of this solution in this GitHub repository.
Again, this isn't the only way. You may wish to alter this a bit for your application. For example, you could use ViewData
to return the data to the view instead of HttpContextBase.Items
. Or you may wish to get rid of the global filter and move the caching pattern into a single extension method that loads/returns the data from the cache. The exact solution depends on what you haven't provided in your question - requirements.