10

I was just testing Output Caching in the RC build of ASP.NET MVC 3.

Somehow, it is not honoring the VaryByParam property (or rather, I am not sure I understand what is going on):

public ActionResult View(UserViewCommand command) {

Here, UserViewCommand has a property called slug which is used to look up a User from the database.

This is my OutputCache declaration:

[HttpGet, OutputCache(Duration = 2000, VaryByParam = "None")]

However, when I try and hit the Action method using different 'slug' values (by manupulating the URL), instead of serving wrong data (which I am trying to force by design), it is instead invoking the action method.

So for example (in order of invocation)

/user/view/abc -> Invokes action method with slug = abc /user/view/abc -> Action method not invoked /user/view/xyz -> Invokes action method again with slug = xyz! Was it not supposed to come out of the cache because VaryByParam = none?

Also, what is the recommended way of OutputCaching in such a situation? (example above)

kidoman
  • 2,252
  • 5
  • 23
  • 33

2 Answers2

11

Just wanted to add this information so that people searching are helped:

The OutputCache behavior has been changed to be 'as expected' in the latest release (ASP.NET MVC 3 RC 2):

http://weblogs.asp.net/scottgu/archive/2010/12/10/announcing-asp-net-mvc-3-release-candidate-2.aspx

Way to go ASP.NET MVC team (and Master Gu)! You all are awesome!

kidoman
  • 2,252
  • 5
  • 23
  • 33
5

VaryByParam only works when the values of the url look like /user/view?slug=abc. The params must be a QueryString parameter and not part of the url like your above examples. The reason for this is most likely because Caching happens before any url mapping and that mapping isn't included in the cache.

Update

The following code will get you where you want to go. It doesn't take into account stuff like Authorized filters or anything but it will cache based on controller/action/ids but if you set ignore="slug" it will ignore that particular attribute

public class ActionOutputCacheAttribute : ActionFilterAttribute {
    public ActionOutputCacheAttribute(int cacheDuration, string ignore) {
        this.cacheDuration = cacheDuration;
        this.ignore = ignore;
    }

    private int cacheDuration;
    private string cacheKey;
    private string ignore;

    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        string url = filterContext.HttpContext.Request.Url.PathAndQuery;
        this.cacheKey = ComputeCacheKey(filterContext);

        if (filterContext.HttpContext.Cache[this.cacheKey] != null) {
            //Setting the result prevents the action itself to be executed
            filterContext.Result =
            (ActionResult)filterContext.HttpContext.Cache[this.cacheKey];
        }

        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        //Add the ActionResult to cache 
        filterContext.HttpContext.Cache.Add(this.cacheKey, filterContext.Result,null, DateTime.Now.AddSeconds(cacheDuration),
          System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);

        //Add a value in order to know the last time it was cached.
        filterContext.Controller.ViewData["CachedStamp"] = DateTime.Now;

        base.OnActionExecuted(filterContext);
    }

    private string ComputeCacheKey(ActionExecutingContext filterContext) {
        var keyBuilder = new StringBuilder();
        keyBuilder.Append(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName);
        keyBuilder.Append(filterContext.ActionDescriptor.ActionName);

        foreach (var pair in filterContext.RouteData.Values) {
            if (pair.Key != ignore) 
                keyBuilder.AppendFormat("rd{0}_{1}_", pair.Key.GetHashCode(), pair.Value.GetHashCode());
        }
        return keyBuilder.ToString();
    }
}
Buildstarted
  • 25,661
  • 9
  • 79
  • 93
  • But I haven't even specified the 'slug' parameter in the VaryByParam property. Since it is actually not checking for 'slug', shouldn't I get the same result? Or is it dependent on the URL of the request as well? – kidoman Nov 23 '10 at 16:33
  • Correct but the `None` here only applies to QueryString values - The entire VaryByParam option only works on QueryStringValues. I was just using your slug as an example :) – Buildstarted Nov 23 '10 at 16:38
  • So if I am reading this right, the Cache varies by request URL? Seems odd that OutputCache (the implementation in MVC, atleast) doesn't have support for this case. Or are we missing something? – kidoman Nov 23 '10 at 16:48
  • There's a nice method to do it...looking for it now. – Buildstarted Nov 23 '10 at 16:56
  • http://blog.stevensanderson.com/2008/10/15/partial-output-caching-in-aspnet-mvc/ I found this a while back that will probably do what you're looking for. and http://code.google.com/p/atomsite/source/browse/trunk/WebCore/ActionOutputCacheAttribute.cs?spec=svn41&r=41 – Buildstarted Nov 23 '10 at 17:10
  • So we are upon the third release of the ASP.NET MVC framework and this behavior hasn't been changed by the MVC team? The article you linked me to (thanks!) is from 2008! Recently, The Gu blogged about introducing the use of OutputCache on Child Actions... how will it be useful there when a ChildAction doesn't have a query string at all? (at least directly) – kidoman Nov 23 '10 at 19:10
  • @BuildStarted: When you compute the key, why didn't you use the HashCodes from the ControllerName and ActionNames instead of their names ? – Luciano Dec 17 '13 at 16:13