Welcome to part 13 of my tour through ASP.NET 4.0. In this episode, we're going to look at caching. One goal for .NET as a whole is to have it use a single caching framework. ASP.NET won't make use of this global framework until .NET 5, but in the meantime, v4 does see some much needed changes in System.Web.Caching that address its more major limitations when used against high-use websites.

Up until now, ASP.NET caching has been a one trick pony with its only storage option for cached items being in-process on the server which hosts the website doing the caching. This is fine for websites of a certain size, but once they need to be scaled up across web farms or web gardens, the in-process boundary makes this built-in caching functionality no longer useful. Indeed, the need for any alternative to memory-based, process-bound caching is anathema to the built-in facilities of ASP.NET 3.5-.

Changes to caching in ASP.NET 4.0 then are not so much about performance, but geared towards creating an extensibility point for caching and introducing the notion of an output cache provider. That way, should we so choose to do so, we can define our own mechanism for both control-level output caching and page-level (i.e. caching the HTTP Response as a whole) caching.

Constraints on your Custom Provider

One of the main reasons for providing an output cache provider model is to offer a way to have items in the cache stored outside of the worker process (and therefore the AppDomain) looking after your website.
However, with that barrier crossed and caching now taking place on disk, cloud, distributed cache etc, several issues arise which mean that some constraints must necessarily be placed on any output cache provider.

  1. Absolute expiration is supported on controls and pages that use custom ouput cache providers, but sliding expiration is not. Consider the case where five web servers are all talking to the same distributed cache. There's no way to keep that sliding expiration in sync across all five machines.
  2. File dependencies are supported but custom dependencies are not. Thus if you invalidate a page, all the supporting cache items for that page will also be invalidated, but there is no support for creating a dependency between cache and other types of object using a custom CacheDependency.
  3. Static substitution, also known as doughnut-hole caching, and output cache validation are supported but only with a static delegate. This is so the caching information can survive a process restart.  When an application restarts, there will at least be a serialized value that tells us what type to get and method to use to wire the cache back up again, because it's static.

With those constraints in mind, to create your own custom cache provider, you'll need to write a class that derives from the new System.Web.Caching.OutputCacheProvider class and overrides its Get, Set, Add and Remove methods. Gunnar Peipman has an excellent post detailing a simple sample implementation for you as a starting point.

Microsoft are also slated to release two output cache providers of their own (most likely via Codeplex) post .NET 4.0 RTM. One is a disk-based caching provider and the other targets AppFabric Caching (formerly known as Velocity), a distributed caching service.

Incorporating Your Provider Into A Site

Once you've created your custom cache provider, integrating it into your site is quick and easy. First, you'll need to declare it in web.config. ASP.NET 4.0 expands <system.web>\<caching>\<outputCache> for this purpose

<configuration>
...
  <system.web>
  ...
    <caching>
      <outputCache defaultProvider="AspNetInternalProvider">
        <providers>
          <add name="CloudCache"
            type="Sample.CloudCacheProvider, SampleCacheProviders"/>
        </providers>
      </outputCache>
    </caching>
  </system.web>
</configuration>

Setting up your new caching provider to provide output caching at a control-level is now very easy. A new attribute to a control's @OutputCache directive allows you to set the provider used declaratively.

<%@OutputCache Duration="360" VaryByParam="UserName" ProviderName="CloudCache" %> 

However, should you wish to set your new provider to cache at the page level you'll need to take a different route. Cache checking at a page-level takes place in the ASP.NET processing pipeline several stages before an instance of the Page class in question has been created, so declaring your required provider in the Page's @OutputCache directive would be several steps too late to influence anything. Instead, you'll need to override a new virtual method on the HttpApplication class called GetOutputCacheProviderName in a custom class or global.asax directly to make the change.

public class Global : System.Web.HttpApplication
{
  public override string GetOutputCacheProviderName(HttpContext context)
  {
    if (AppSettingsHelper.GetSettingAsBool("UseCloud"))
    {
      return "CloudCache"; // name maps to one in web.config
    }
    else
    {
      return base.GetOutputCacheProviderName(context);
    }
  }
}

Summary

In this post, we've looked at the new OutputCacheProvider model added to ASP.NET 4.0. We've looked at the constraints under which new cache provider classes must live and how to integrate them into our sites once written, depending on whether we need caching to work at a control or a page level.

Until next time, happy coding!