示例:
第一步:
在项目中添加类;如下。
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", System.Reflection.BindingFlags.Instance | System.Reflection.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);
string cachedOutput = (string)filterContext.HttpContext.Cache[_cacheKey];
if (cachedOutput != null)
filterContext.Result = new ContentResult { Content = cachedOutput };
else
_originalWriter = (TextWriter)_switchWriterMethod.Invoke(HttpContext.Current.Response, new object[] { new HtmlTextWriter(new StringWriter()) });
}
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);
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();
}
}
第二步:
在用户控件的View函数上添加特性,如下。
[ActionOutputCache(60)] // Caches for 60 seconds
完成!
有兴趣的,请阅读以下原文:
The ASP.NET platform provides two major caching facilities:
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.
If only output caching was so simple! ASP.NET’s output caching facility is deeply stuck in WebForms thinking, which makes it problematic in ASP.NET MVC. You could try to use ASP.NET output caching with ASP.NET MVC, but then you’d have the following issues:
Unfortunately, the [OutputCache] filter that ships with ASP.NET MVC is merely a thin wrapper around ASP.NET output caching, so it has exactly those problems. The MVC team have explained that they’re aware of the issues, but it’s very difficult to make ASP.NET output caching fit into MVC’s design, and they are focusing on other things first. And personally I’m happy with that: I’d rather see a finished 1.0 RTM release this year than fuss about output caching.
Update: Since the Beta release, the [Authorize] filter now does some clever trickery to co-operate with ASP.NET output caching. Specifically, it registers a delegate using HttpCachePolicy.AddValidationCallback(), so that it can intercept future cache hits and tell ASP.NET output caching not to use the cache when [Authorize] would reject the request. This solves the problem of ASP.NET output caching bypassing the [Authorize] filter. If you’re going to write your own authorization filter, be sure to derive it from AuthorizeAttribute so you can inherit this useful behaviour.
Note that this doesn’t stop ASP.NET output caching from bypassing any of your other action filters, and it doesn’t add any support for partial caching. If that’s a problem for you then consider using [ActionOutputCache] (below) instead.
One reason why I don’t mind [OutputCache]’s limitations so much is that ASP.NET MVC is extremely extensible, and without too much trouble we can replace the output caching system with something new and more suitable.
We can quite easily create a new caching filter that captures actions’ output and uses ASP.NET’s data caching facility to store it for next time. This filter will fit properly into the MVC pipeline, not strangely bypassing authorization or other earlier filters (it will run at the right time in whatever ordered set of filters you’ve using). And if you’re using something like the PartialRequest system for widgets that I described yesterday, it will naturally let you cache PartialRequests’ output separately from the actions that host them, which is also known as partial output caching.
I must first acknowledge that the following code is very similar to the custom output caching filter that Maarten Balliauw presented way back in June. The reason I think this warrants a whole new post is because the following code works better for partial caching with PartialRequest and with MVC Contrib’s subcontrollers, and because it would be good for more MVC developers to discover it. But Maarten was first to write a good blog post on this subject.
So here it is. Drop the following class somewhere in your MVC project:
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", System.Reflection.BindingFlags.Instance | System.Reflection.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); string cachedOutput = (string)filterContext.HttpContext.Cache[_cacheKey]; if (cachedOutput != null) filterContext.Result = new ContentResult { Content = cachedOutput }; else _originalWriter = (TextWriter)_switchWriterMethod.Invoke(HttpContext.Current.Response, new object[] { new HtmlTextWriter(new StringWriter()) }); } 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); 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(); } }
Now you can use [ActionOutputCache] instead of MVC’s built-in [OutputCache]. The advantage of [ActionOutputCache] is that it’s a fully native MVC action filter, and doesn’t rely on or inherit the problems of ASP.NET’s WebForms-oriented output caching technology. So, for example, [ActionOutputCache] plays nicely with MVC’s [Authorize] filter. Update: Since the beta release, the built in [Authorize] and [OutputCache] filters now play nicely together too.
You can use [ActionOutputCache] to cache the output of any action method just like [OutputCache], but it’s perhaps most interesting when you combine it with the PartialRequests method of rendering widgets (or use it with Html.RenderAction(), which isn’t compatible with the built-in [OutputCache]). Put an [ActionOutputCache] attribute on the widget’s action method (not on the action that hosts it), then you’ll have partial page caching, as shown in the following code.
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(); } }
This is great if your widget displays some relatively static data (e.g., a list of the “most recent” things), or is an action method whose output is constant for a given set of parameters (e.g, a dynamically-built navigation menu that highlights the visitor’s current location, where the current location is one of the parameters passed to the action method), and you don’t want to recompute it on every page hit.
Support for partial caching is a major advantage of PartialRequest over the use of viewdata-populating filters and partial views to render widgets. The filter/partialview technique can never support proper output caching, because inherently it mixes the widget’s viewdata with the main page’s viewdata, and the two can’t be distinguished by the time you’re actually rendering the view. The closest you could get would be to limit yourself to data caching, but that’s more complex and not always viable anyway, such as if you’re using an IQueryable to defer a SQL query until view rendering time.
To keep the [ActionOutputCache] code short and easy to understand, and because its current behaviour is adequate for my own current project’s needs, there are a number of limitations and caveats you should know about:
In other words, it works great for most straightforward widget output caching scenarios, but if you’re doing something more complex then please be prepared to dive into the code yourself! Hope this is useful to a few people.