Partial Output Caching in ASP.NET MVC.
// Partial Output Caching in ASP.NET MVC
//
// Data caching continues to work perfectly well in ASP.NET MVC, because it's
// just about getting objects in and out of a collection, and isn't specific
// to any particular UI technology.
//
// Drop the class (`ActionOutputCacheAttribute`) somewhere in
// your MVC project.
//
// ---------------
// EXAMPLE USAGE
// ---------------
//
// public class BlogController : Controller
// {
// [ActionOutputCache(60)] // Caches for 60 seconds
// public ActionResult LatestPosts()
// {
// ViewData["currentTime"] = DateTime.Now;
// ViewData["posts"] = new[] {
// "Here's a post",
// "Here's another post. Marvellous.",
// "Programmer escapes from custody"
// };
// return View();
// }
// }
//
// Original: http://blog.stevensanderson.com/2008/10/15/partial-output-caching-in-aspnet-mvc/
// Updated: http://blog.rthand.com/post/2009/03/21/Partial-Output-Caching-in-ASPNET-MVC-updated.aspx
// - Fixes output content encoding issues.
//
public class ActionOutputCacheAttribute : ActionFilterAttribute
{
// This hack is optional; I'll explain it later in the blog post
private static MethodInfo _switchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);
public ActionOutputCacheAttribute(int cacheDuration)
{
_cacheDuration = cacheDuration;
}
private int _cacheDuration;
private TextWriter _originalWriter;
private string _cacheKey;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_cacheKey = ComputeCacheKey(filterContext);
CacheContainer cachedOutput = (CacheContainer)filterContext.HttpContext.Cache[_cacheKey];
if (cachedOutput != null)
{
filterContext.HttpContext.Response.ContentType = cachedOutput.ContentType;
filterContext.Result = new ContentResult { Content = cachedOutput.Output };
}
else
{
StringWriter stringWriter = new StringWriterWithEncoding(filterContext.HttpContext.Response.ContentEncoding);
HtmlTextWriter newWriter = new HtmlTextWriter(stringWriter);
_originalWriter = (TextWriter)_switchWriterMethod.Invoke(HttpContext.Current.Response, new object[] { newWriter });
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (_originalWriter != null) // Must complete the caching
{
HtmlTextWriter cacheWriter = (HtmlTextWriter)_switchWriterMethod.Invoke(HttpContext.Current.Response, new object[] { _originalWriter });
string textWritten = ((StringWriter)cacheWriter.InnerWriter).ToString();
filterContext.HttpContext.Response.Write(textWritten);
CacheContainer container = new CacheContainer(textWritten, filterContext.HttpContext.Response.ContentType);
filterContext.HttpContext.Cache.Add(_cacheKey, container, null, DateTime.Now.AddSeconds(_cacheDuration), Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
}
private string ComputeCacheKey(ActionExecutingContext filterContext)
{
var keyBuilder = new StringBuilder();
foreach (var pair in filterContext.RouteData.Values)
keyBuilder.AppendFormat("rd{0}_{1}_", pair.Key.GetHashCode(), pair.Value.GetHashCode());
foreach (var pair in filterContext.ActionParameters)
keyBuilder.AppendFormat("ap{0}_{1}_", pair.Key.GetHashCode(), pair.Value.GetHashCode());
return keyBuilder.ToString();
}
}
class CacheContainer
{
public string Output;
public string ContentType;
/// <summary>
/// Initializes a new instance of the CacheContainer class.
/// </summary>
/// <param name="data"></param>
/// <param name="contentType"></param>
public CacheContainer(string data, string contentType)
{
Output = data;
ContentType = contentType;
}
}