Making ASP.NET Output Cache Work with Post-Back

Making ASP.NET Output Cache Work with Post-Back.

ASP.NET Output Cache & Post-Back

ASP.NET’s page caching and user control fragment caching prevents post-back from working because, during post-back, a cached version of the page may be displayed instead of the form submission being processed. However, all is not lost! I’ve found a simple solution to this using a HTTP module to bypass the output cache during post-back. This allows the initial page view to be cached to disk, but – and here’s the good bit – user interaction via post-back remains un-cached, so you get the benefits of post-back while losing none of the goodness of output caching!

HTTP Module to Bypass Output Caching During Post-Back

public class BypassCacheModule : IHttpModule
{
   public void Dispose()
   {
   }

   public void Init(HttpApplication application)
   {
      application.PostMapRequestHandler += (object sender, EventArgs e) =>
      {
         if (!String.Equals(application.Context.Request.HttpMethod, "GET",
            StringComparison.InvariantCultureIgnoreCase))
         {
            application.Context.Response.Cache.SetNoServerCaching();
         }
      };
   }
}

The bypass cache module can be added to the web.config like so:

<system.web>
   <httpModules>
      <add name="BypassCache" type="BypassCacheModule, BypassCacheModuleAssembly"/>
   </httpModules>
</system.web>

Using the OutputCache Directive with Post-Back

The bypass cache module allows you to configure page caching and user control fragment caching using the OutputCache directive while still allowing your code to execute un-cached during post-back. The OutputCache directive can be added to pages and user controls to cache their contents to disk for a specific duration in seconds.

<%@ OutputCache Duration="3600" %>

Allowing Unique Cache Entries Based on the Query String

Another common issue related to page and user control fragment caching is that, by default, only the physical file path will be used as a cache key and nothing else. This has two main issues for the unwary developer:

1) If the HTML output varies based on user agent then only version of the HTML will end up in the cache.

2) If your page’s content varies by query string parameter (for instance when using URL rewriting or database driven applications), then the differing content is not cached properly.

The solution to these issues is to use some extra features provided with output caching, primarily the VaryByCustom attribute of the OutputCache directive.

In your page or user control add to your OutputCache directive:

<%@ OutputCache VaryByParam="Custom" VaryByCustom="RawUrl" Duration="3600" %>

The “RawUrl” parameter passed to VaryByCustom is my choice of keyword, the parameter you pass in here is just passed through to a custom cache key generator you must add to the Global.asax.

Update the Global.asax

public class Global : System.Web.HttpApplication
{
   public override string GetVaryByCustomString(HttpContext context, string custom)
   {
      if (custom == "RawUrl")
      {
         return context.Request.RawUrl.ToLower();
      }
      else
      {
         return base.GetVaryByCustomString(context, custom);
      }
   }
}

In the above code, you can see how the “RawUrl” parameter expressed in the OutputCache directive for the VaryByCustom attribute can be used to provide a measure of control for your own version of GetVaryByCustomString.

A word of caution: I would personally avoid the VaryByParam=”*” approach to ensuring unique cache entries for query string driven content due to post-back (form) data also being included in the cache key. Also, the VaryByParam=”*” approach wouldn’t work for post-back scenarios that need to write to the database because submissions with the same data would result in cache hits rather than post-back code being executed reliably.

Clearing the ASP.NET Output Cache

You can use cache dependencies to solve the problem of needing to clear pages from the output cache. The HttpResponse has a method call AddCacheItemDependency which, to quote MSDN, “makes the validity of a cached response dependent on another item in the cache”, so if you add to all pages you are caching, the following code:

protected override void OnInit(EventArgs e)
{
   base.OnInit(e);

   Response.AddCacheItemDependency("Site");
}

So, when you need to clear those pages from the cache, call:

HttpRuntime.Cache.Insert("Site", new object(), null, DateTime.MaxValue, TimeSpan.Zero,
   CacheItemPriority.NotRemovable, null);

The act of inserting an object with a cache key of “Site” into the cache will trigger all cache pages to be flushed due to their dependency on the “Site” key.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s