using System;
using System.Web.Mvc;
using System.IO;
using System.Text;
using System.Web.Caching;
///
/// Alternative implementation of ActionOutputCacheAttribute that doesn't use the reflection hack
/// The disadvantage of this approach is that it reduces your control over output buffering (it has to flush the response buffer at particular times)
///
public class ActionOutputCacheAttribute : ActionFilterAttribute
{
public ActionOutputCacheAttribute(int cacheDuration)
{
_cacheDuration = cacheDuration;
}
private int _cacheDuration;
private string _cacheKey;
private Stream _originalOutputStream;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_cacheKey = ComputeCacheKey(filterContext);
string cachedOutput = (string)filterContext.HttpContext.Cache[_cacheKey];
if (cachedOutput != null)
filterContext.Result = new ContentResult { Content = cachedOutput };
else
{
_originalOutputStream = filterContext.HttpContext.Response.Filter;
filterContext.HttpContext.Response.Flush();
filterContext.HttpContext.Response.Filter = new CapturingResponseFilter(filterContext.HttpContext.Response.Filter);
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (_originalOutputStream != null) // Must complete the caching
{
filterContext.HttpContext.Response.Flush();
CapturingResponseFilter capturingResponseFilter = (CapturingResponseFilter)filterContext.HttpContext.Response.Filter;
filterContext.HttpContext.Response.Filter = _originalOutputStream;
string textWritten = capturingResponseFilter.GetContents(filterContext.HttpContext.Response.ContentEncoding);
filterContext.HttpContext.Response.Write(textWritten);
filterContext.HttpContext.Cache.Add(_cacheKey, textWritten, 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();
}
///
/// CapturingResponseFilter borrowed from MVC Contrib. See the original at http://mvccontrib.googlecode.com/svn/trunk/src/MVCContrib/UI/CapturingResponseFilter.cs
///
private class CapturingResponseFilter : Stream
{
private Stream _sink;
private MemoryStream mem;
public CapturingResponseFilter(Stream sink)
{
_sink = sink;
mem = new MemoryStream();
}
// The following members of Stream must be overriden.
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { return 0; }
}
public override long Position { get; set; }
public override long Seek(long offset, SeekOrigin direction)
{
return 0;
}
public override void SetLength(long length)
{
_sink.SetLength(length);
}
public override void Close()
{
_sink.Close();
mem.Close();
}
public override void Flush()
{
_sink.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _sink.Read(buffer, offset, count);
}
// Override the Write method to filter Response to a file.
public override void Write(byte[] buffer, int offset, int count)
{
//Here we will not write to the sink b/c we want to capture
//Write out the response to the file.
mem.Write(buffer, 0, count);
}
public string GetContents(Encoding enc)
{
var buffer = new byte[mem.Length];
mem.Position = 0;
mem.Read(buffer, 0, buffer.Length);
return enc.GetString(buffer, 0, buffer.Length);
}
}
}