views:

338

answers:

2

Hi folks,

i'm trying to use OutputCaching in my ASP.NET MVC website. Problem is, when i try and change the value of one my querystring params, it's returning the data for the first item that was requested!

Here's my code (with the param names changed) ...

[ApiAuthorize]
[HandleErrorAsJson]
public class SearchController : Controller
{
    [AcceptVerbs(HttpVerbs.Get)]
    [OutputCache(Duration = 60, VaryByParam = "*")]
    public ActionResult ScoreCard(string foo, byte? bar, byte? pewpew)
    {
    ..
    }
}
  • NOTE 1: ApiAuthorize - custom attribute that checks for a querystring param called 'Key' and checks an in memory dictionary, to see if it exists.
  • NOTE 2: HandleErrorAsJson - custom attribute that returns the error message as json if an exception was/is thrown.

and here's two sample calls i'm making to this action :-

so the data from the first call (foo = hello world, Pew Pew) is returned as a 200 OK. Then the second api call returns a 200 OK but with the data from the previous call.

Also, i'm not using any proxy server. If i comment out the OutputCache attribute, all is good.

I've also tried the following (manually listing each time i need to cache) .....

[OutputCache(Duration = 60, VaryByParam = "foo,key,bar,pewpew")]

No luck :(

Notice how i need to make sure that i include the API 'Key' parameter as part of the cache unique key. I don't want to people to search for the same thing, but if the second person doesn't have the right key, they shouldn't get a cached result, but an error message (techinically, it's a 401 Not Authorised, but anyways)...

Thoughts?

A: 

I use a filter for output caching which will give programmatic control for debugging and also will set when it needs to be. Feel free to use this and see if setting it here would make the difference which I think it might (also make sure any previous caching is cleared)

public class CacheFilterAttribute : ActionFilterAttribute
    {
        private const int Second = 1;
        private const int Minute = 60 * Second;
        private const int Hour = 60 * Minute;
        public const int SecondsInDay = Hour * 24;


        /// <summary>
        /// Gets or sets the cache duration in seconds. The default is 10 seconds.
        /// </summary>
        /// <value>The cache duration in seconds.</value>
        public int Duration
        {
            get;
            set;
        }

        public int DurationInDays
        {
            get { return Duration / SecondsInDay; }
            set { Duration = value * SecondsInDay; }
        }

        public CacheFilterAttribute()
        {
            Duration = 10;
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (Duration <= 0) return;

            HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
            TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);

            cache.SetCacheability(HttpCacheability.Public);
            cache.SetExpires(DateTime.Now.Add(cacheDuration));
            cache.SetMaxAge(cacheDuration);
            cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
        }
    }

I'm just using this so far for a very static part of a site, so have a call like this

[CacheFilter(DurationInDays = 1)]

Obviously you'd just like to extend this to have VaryByParams exposed as a property, can provide but sounds like you'd know what to do.

dove
Out of interest, why did u end up creating your own Attribute, here?
Pure.Krome
I applied on a couple of views and will do more. I wasn't 100% on exact settings you see above and so with this I easily could tweak and know it was applied everywhere. I'm still not fixed on whether to add more headers when setting cache. Of course if I find this to be a bad idea I can make this a clean extension of the OutputCache attribute and lose nothing. So in short, had something to gain with a clear path to safety if any risk arose.
dove
There's nothing wrong with what he's done here, but it doesn't answer your question. The filter he has created affects the *browser* caching, not the server caching. That's legitimate to do, and I'm not aware of anything built into ASP.NET MVC which does it. It also, implicitly, makes a good point, which is that there are two different caches at play here, the one in the browser and the one in the server. Your question appears to me to be related to the server cache, although it would be good to double check this with Firebug or Fiddler.
Craig Stuntz
@Craig thanks for pointing that out, I'd assumed HttpCacheability.Public included Server but you are right it does not. And looking it's not possible to have full selection. That is you could set HttpCacheability.ServerAndPrivate but that would rule out proxies (ISPs) which I would definitely want. However, above does cache at the server as well, thanks to the AppendCacheExtension("must-revalidate, proxy-revalidate"); I had confirmed this while debugging when I'd added this so it is a server cache albeit not perfectly expressed and potentially with problems.
dove
AppendCacheExtension does one thing: Adds text to a header to the HTTP response. See http://msdn.microsoft.com/en-us/library/system.web.httpcachepolicy.appendcacheextension.aspx That it's adding text to the response, not the request, tells you what it is affecting.
Craig Stuntz
+2  A: 

The list of parameters in VaryByParam is supposed to be semicolon delimited, not comma delimited. Give that a try. That said, the * should have worked.

Craig Stuntz
+1 more directly focussed on question at hand alright. though i don't think it's the only issue he has here. replied to your comment below too.
dove