20

We've got a fairly standard e-commerce scenario with paged lists of products within categories. For better or worse, about 80% of visitors never navigate past the first page, depending on the category there may then be 5-10 more pages of results which are viewed far less often. (Yes we do optimise what appears on the first page and have good search - but that's a different discussion)

We can't cache every single page of results, because we're constrained by memory, but the benefit of caching just the first page of results for each category would be huge.

I know I could do something similar using object caching to store the datasets in question, but is this possible using output caching, perhaps by using the response.Cache object?

Where in the page lifecycle could this be done? Pre-render?

Much simplified, the URL is something like "/ProductList?Category=something&Page=1" And I'd want logic something like (pseudocode):

If paramater "Page" equals 1
   Use output caching: vary by param = "categoryName; page"
else
   Don't use caching at all, just render the page from scratch.

We're using ASP.NET 2.0, on IIS 6/win2003.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Andrew M
  • 9,149
  • 6
  • 44
  • 63
  • Have a look at the last answer on [this](http://stackoverflow.com/questions/1122837/how-do-i-configure-asp-net-outputcache-to-vary-by-http-vs-https) post. I hope this helps. – James Dec 31 '09 at 21:52

5 Answers5

31

Instead of using the OutputCache directive, you can do the same thing programmatically, as follows:

if (yourArbitraryCondition) {
  OutputCacheParameters outputCacheSettings = new OutputCacheParameters();
  outputCacheSettings.Duration = 60;
  InitOutputCache(outputCacheSettings);
}

Doing this from OnInit should work fine. And obviously, you can tweak the caching behavior by setting the various properties on the OutputCacheParameter, which has all the same knobs as the directive (in fact, that's what we generate when you use the directive).

The key point is that you're only executing this logic conditionally, while the directive makes it unconditional.

UPDATE:

As an alternative, you can use the low level cache API that the code above is built on. e.g.

HttpCachePolicy cache = Response.Cache;
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(Context.Timestamp.AddSeconds(60));
cache.VaryByParams["categoryName"] = true;

Basically, it's another way of doing the same thing, without using any API's marked as 'should not be called'. In the end, either way will work, so take your pick.

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
David Ebbo
  • 42,443
  • 8
  • 103
  • 117
  • Works. Any idea why `InitOutputCache` is `EditorBrowsableState.Never` and should not be called directly according to http://msdn.microsoft.com/en-us/library/ms153473.aspx? – Josef Pfleger Dec 31 '09 at 11:57
  • 1
    Well, it's mostly due to some lack of forward thinking when we first designed this. There are a number of public/protected APIs on Page that are marked this way. They are all things that get called by generated code as a result of using various syntax, and we figured that the user didn't have any good reasons to call them directly. But the reality is that there is nothing wrong with calling them yourself, and in fact there are times like here where it lets you do things that you couldn't do with the page syntax. I'll file a bug to get those flags removed, though it's way to late for VS2010. – David Ebbo Dec 31 '09 at 16:38
  • One more thing: you can actually do the same thing by calling the low level cache API directly (and avoid InitOutputCache). It will work the same, but the code will be more complex. Let me know if you want that alternative solution. – David Ebbo Dec 31 '09 at 16:40
  • Can this work with fragment caching for caching individual controls that are generated dynamically? In other words, what if one dynamic control varies by one parameter, another varies by another parameter? – sagescrub Dec 01 '11 at 19:56
  • 1
    the top code throws and exception if you don't include outputCacheSettings.VaryByParam = "none"; – NSjonas Aug 10 '12 at 18:18
5

edit: I like David Ebbo's answer a lot better than my own.


You could use

<%@ OutputCache Duration="60"  VaryByParam="none" VaryByCustom="pageOne" %>

and implement it in a way that returns a fixed key for the first page and a random key for all other pages. You can (and should) let the scavenging mechanism take care of memory but you can use HttpResponse.RemoveOutputCacheItem to remove cache items if you must.

public override string GetVaryByCustomString(HttpContext ctx, string custom)
{
    if(custom == "pageOne")
    {
        if(ctx.Request["page"] == "1")
        {
            return "1";
        }

        HttpResponse.RemoveOutputCacheItem("/Default.aspx");
        return Guid.NewGuid().ToString();
    }
    return base.GetVaryByCustomString(ctx, custom);
}
Josef Pfleger
  • 74,165
  • 16
  • 97
  • 99
4

I believe the best way to do this is to use HttpCachePolicy.AddValidationCallback

See http://www.hanselman.com/blog/AdvancedASPNETCachingAndAddValidationCallBack.aspx - There's a full example that answers precisely this question.

candrews
  • 1,995
  • 1
  • 16
  • 13
1

You can still use the outputcache directive, and in my opinion, rather than litter your page code with a bunch of caching nuts and bolts, you're better off going with a reusable solution based on handling this in Global.asax the way you normally would any VaryByCustom scenario.

So, for example, if you are using a paging approach with a repeater, you might simply in your search scenario want to exclude any postback on a particular page from the cache. Here is a code example that does just that. The approach merely requires using the HttpContext object to access Response.Cache.SetNoServerCaching(), after trapping whatever criteria for which you wish to avoid caching. I hope this helps.

Iucounu
  • 1,630
  • 1
  • 20
  • 31
0

I think you should be able to use the OutputCache directive with the VaryByParam property set to a semi-colon separated list of strings used to vary the output cache.

Unless you were wanting to just cache only when Page == 1?

Ian Oxley
  • 10,916
  • 6
  • 42
  • 49
  • unfortunately that's exactly what I want (Only when page == 1). If I wanted to cache every page of results that would be easy, using varybyparam as you say. Sorry I don't think I phrased the question very clearly, but what I'm after is only subtely (but significantly) different to the normal scenario everyone is used to. – Andrew M Jun 25 '09 at 13:24