Caching

by Microsoft

An understanding of caching is important for a well-performing ASP.NET application. ASP.NET 1.x offered three different options for caching; output caching, fragment caching, and the cache API.

An understanding of caching is important for a well-performing ASP.NET application. ASP.NET 1.x offered three different options for caching; output caching, fragment caching, and the cache API. ASP.NET 2.0 offers all three of these methods, but it adds some significant additional features. There are several new cache dependencies and developers now have the option to create custom cache dependencies as well. The configuration of caching has also been improved significantly in ASP.NET 2.0.

New Features

Cache Profiles

Cache profiles allow developers to define specific cache settings that can then be applied to individual pages. For example, if you have some pages that should be expired from cache after 12 hours, you can easily create a cache profile that can be applied to those pages. To add a new cache profile, use the <outputCacheSettings> section in the configuration file. For example, below is the configuration of a cache profile called twoday that configures a cache duration of 12 hours.

<outputCacheSettings>
    <outputCacheProfiles>
        <add name="TwoDay" duration="43200" />
    </outputCacheProfiles>
</outputCacheSettings>

To apply this cache profile to a particular page, use the CacheProfile attribute of the @ OutputCache directive as shown below:

<%@ OutputCache CacheProfile="TwoDay" %>

Custom Cache Dependencies

ASP.NET 1.x developers cried out for custom cache dependencies. In ASP.NET 1.x, the CacheDependency class was sealed which prevented developers from deriving their own classes from it. In ASP.NET 2.0, that limitation is removed and developers are free to develop their own custom cache dependencies. The CacheDependency class allows for the creation of a custom cache dependency based on files, directories, or cache keys.

For example, the code below creates a new custom cache dependency based on a file called stuff.xml located in the root of the Web application:

System.Web.Caching.CacheDependency dep = new
    System.Web.Caching.CacheDependency(Server.MapPath("stuff.xml"));
Response.AddCacheDependency(dep);
Cache.Insert("key", "value");

In this scenario, when the stuff.xml file changes, the cached item is invalidated.

It is also possible to create a custom cache dependency using cache keys. Using this method, the removal of the cache key will invalidate the cached data. The following example illustrates this:

// insert a value into cache that will serve
// as the cache key
Cache["CacheKey"] = "something";

// create an array of cache keys
string[] keys = new String[1];
keys[0] = "CacheKey";

CacheDependency dep = new CacheDependency(null, keys);

// insert an item into cache with a dependency on
// the above CacheDependency
Cache.Insert("Key", "Value", dep);

To invalidate the item that was inserted above, simply remove the item that was inserted into cache to act as the cache key.

// Remove the cache item that serves as the cache key
Cache.Remove("CacheKey");

Note that the key of the item that acts as the cache key must be the same as the value added to the array of cache keys.

Polling-Based SQL Cache Dependencies(Also called Table-Based Dependencies)

SQL Server 7 and 2000 use the polling-based model for SQL cache dependencies. The polling-based model uses a trigger on a database table that is triggered when data in the table change. That trigger updates a changeId field in the notification table that ASP.NET checks periodically. If the changeId field has been updated, ASP.NET knows that the data have changed and it invalidates the cached data.

Note

SQL Server 2005 can also use the polling-based model, but because the polling-based model is not the most efficient model, it is advisable to use a query-based model (discussed later) with SQL Server 2005.

In order for a SQL cache dependency using the polling-based model to work correctly, the tables must have notifications enabled. This can be accomplished programmatically using the SqlCacheDependencyAdmin class or by using the aspnet_regsql.exe utility.

The following command line registers the Products table in the Northwind database located on a SQL Server instance named dbase for SQL cache dependency.

aspnet_regsql -S dbase -ed -d Northwind -E -et -t Products

The following is an explanation of the command line switches used in the above command:

Command Line Switch Purpose
-S server Specifies the server name.
-ed Specifies that the database should be enabled for SQL cache dependency.
-d database_name Specifies the database name that should be enabled for SQL cache dependency.
-E Specifies that aspnet_regsql should use Windows authentication when connecting to the database.
-et Specifies that we are enabling a database table for SQL cache dependency.
-t table_name Specifies the name of the database table to enable for SQL cache dependency.

Note

There are other switches available for aspnet_regsql.exe. For a complete list, run aspnet_regsql.exe -? from a command line.

When this command runs the following changes are made to the SQL Server database:

  • An AspNet_SqlCacheTablesForChangeNotification table is added. This table contains one row for each table in the database for which a SQL cache dependency has been enabled.
  • The following stored procedures are created inside of the database:
AspNet_SqlCachePollingStoredProcedure Queries the AspNet_SqlCacheTablesForChangeNotification table and returns all tables that are enabled for SQL cache dependency and the value of changeId for each table. This stored proc is used for polling to determine if data have changed.
AspNet_SqlCacheQueryRegisteredTablesStoredProcedure Returns all of the tables enabled for SQL cache dependency by querying the AspNet_SqlCacheTablesForChangeNotification table and returns all tables enabled for SQL cache dependency.
AspNet_SqlCacheRegisterTableStoredProcedure Registers a table for SQL cache dependency by adding the necessary entry in the notification table and adds the trigger.
AspNet_SqlCacheUnRegisterTableStoredProcedure Unregisters a table for SQL cache dependency by removing the entry in the notification table and removes the trigger.
AspNet_SqlCacheUpdateChangeIdStoredProcedure Updates the notification table by incrementing the changeId for the changed table. ASP.NET uses this value to determine if the data have changed. As indicated below, this stored proc is executed by the trigger created when the table is enabled.
  • A SQL Server trigger called table_name_AspNet_SqlCacheNotification_Trigger is created for the table. This trigger executes the AspNet_SqlCacheUpdateChangeIdStoredProcedure when an INSERT, UPDATE, or DELETE is performed on the table.
  • A SQL Server role called aspnet_ChangeNotification_ReceiveNotificationsOnlyAccess is added to the database.

The aspnet_ChangeNotification_ReceiveNotificationsOnlyAccess SQL Server role has EXEC permissions to the AspNet_SqlCachePollingStoredProcedure. In order for the polling model to work correctly, you must add your process account to the aspnet_ChangeNotification_ReceiveNotificationsOnlyAccess role. The aspnet_regsql.exe tool will not do this for you.

Configuring Polling-Based SQL Cache Dependencies

There are several steps that are required for configuring polling-based SQL cache dependencies. The first step is to enable the database and the table as discussed above. Once that step is complete, the rest of the configuration is as follows:

  • Configuring the ASP.NET configuration file.
  • Configuring the SqlCacheDependency

Configuring the ASP.NET Configuration File

In addition to adding a connection string as discussed in a previous module, you must also configure a <cache> element with a <sqlCacheDependency> element as shown below:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="Pubs"
    connectionString="Data Source=(local);
      Initial Catalog=pubs;Integrated Security=true;"
    providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
    <caching>
      <sqlCacheDependency enabled = "true" pollTime = "60000" >
        <databases>
          <add name="pubs" connectionStringName = "pubs" pollTime = "9000000" />
        </databases>
      </sqlCacheDependency>
    </caching>
  </system.web>
</configuration>

This configuration enables a SQL cache dependency on the pubs database. Note that the pollTime attribute in the <sqlCacheDependency> element defaults to 60000 milliseconds or 1 minute. (This value cannot be less than 500 milliseconds.) In this example, the <add> element adds a new database and overrides the pollTime, setting it to 9000000 milliseconds.

Configuring the SqlCacheDependency

The next step is to configure the SqlCacheDependency. The easiest way to accomplish that is to specify the value for the SqlDependency attribute in the @ Outcache directive as follows:

<%@ OutputCache duration="60"
    VaryByParam="none" SqlDependency="pubs:authors" %>

In the above @ OutputCache directive, a SQL cache dependency is configured for the authors table in the pubs database. Multiple dependencies can be configured by separating them with a semi-colon like so:

<%@ OutputCache duration="60"
    VaryByParam="none"
    SqlDependency="database_name:table_name;database_name:table_name" %>

Another method of configuring the SqlCacheDependency is to do so programmatically. The following code creates a new SQL cache dependency on the authors table in the pubs database.

SqlCacheDependency dep = new SqlCacheDependency("pubs", "authors");

One of the benefits of programmatically defining the SQL cache dependency is that you can handle any exceptions that might occur. For example, if you attempt to define a SQL cache dependency for a database that has not been enabled for notification, a DatabaseNotEnabledForNotificationException exception will be thrown. In that case, you can attempt to enable the database for notifications by calling the SqlCacheDependencyAdmin.EnableNotifications method and passing it the database name.

Likewise, if you attempt to define a SQL cache dependency for a table that has not been enabled for notification, a TableNotEnabledForNotificationException will be thrown. You can then call the SqlCacheDependencyAdmin.EnableTableForNotifications method passing it the database name and table name.

The following code sample illustrates how to properly configure exception handling when configuring a SQL cache dependency.

try {
    SqlCacheDependency SqlDep = new
    SqlCacheDependency("pubs", "authors");
} catch (DatabaseNotEnabledForNotificationException exDBDis) {
    try {
        SqlCacheDependencyAdmin.EnableNotifications("pubs");
    } catch (UnauthorizedAccessException exPerm) {
        Response.Redirect("ErrorPage.htm");
    }
} catch (TableNotEnabledForNotificationException exTabDis) {
    try {
        SqlCacheDependencyAdmin.EnableTableForNotifications("pubs",
        "authors");
    } catch (System.Data.SqlClient.SqlException exc) {
        Response.Redirect("ErrorPage.htm");
    }
} finally {
    Cache.Insert("SqlSource", Source1, SqlDep);
}

More Information: https://msdn.microsoft.com/library/t9x04ed2.aspx

Query-Based SQL Cache Dependencies (SQL Server 2005 Only)

When using SQL Server 2005 for SQL cache dependency, the polling-based model is not necessary. When used with SQL Server 2005, SQL cache dependencies communicate directly via SQL connections to the SQL Server instance (no further configuration is necessary) using SQL Server 2005 query notifications.

The simplest way to enable query-based notification is to do so declaratively by setting the SqlCacheDependency attribute of the data source object to CommandNotification and setting the EnableCaching attribute to true. Using this method, no code is required. If the result of a command executed against the data source changes, it will invalidate the cache data.

The following example configures a data source control for SQL cache dependency:

<asp:SqlDataSource ID="ProductList" runat="server"
    ConnectionString="<%$ ConnectionStrings:Northwind %>"
    EnableCaching="true"
    SqlCacheDependency="CommandNotification"
    SelectCommand="SELECT * FROM [Products]" />

In this case, if the query specified in the SelectCommand returns a different result than it did originally, the results that are cached are invalidated.

You can also specify that all of your data sources be enabled for SQL cache dependencies by setting the SqlDependency attribute of the @ OutputCache directive to CommandNotification. The example below illustrates this.

<%@ OutputCache SqlDependency="CommandNotification" 
    duration="60" VaryByParam="none" %>

Note

For more information on query notifications in SQL Server 2005, see the SQL Server Books Online.

Another method of configuring a query-based SQL cache dependency is to do so programmatically using the SqlCacheDependency class. The following code sample illustrates how this is accomplished.

string sql = "SELECT ProductName, ProductID FROM Products";
SqlConnection conn = new
SqlConnection(ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
SqlCommand cmd = new SqlCommand(sql, conn);
SqlCacheDependency dep = new SqlCacheDependency(cmd);
Response.AddCacheDependency(dep);

More Information: https://msdn.microsoft.com/library/default.asp?url=/library/enus/dnvs05/html/querynotification.asp

Post-Cache Substitution

Caching a page can dramatically increase the performance of a Web application. However, in some cases you need most of the page to be cached and some fragments within the page to be dynamic. For example, if you create a page of news stories that is entirely static for set periods of time, you can set the entire page to be cached. If you wanted to include a rotating ad banner that changed on every page request, then the part of the page containing the advertisement needs to be dynamic. To allow you to cache a page but substitute some content dynamically, you can use ASP.NET post-cache substitution. With post-cache substitution, the entire page is output cached with specific parts marked as exempt from caching. In the example of the ad banners, the AdRotator control allows you to take advantage of post-cache substitution so that ads dynamically created for each user and for each page refresh.

There are three ways to implement post-cache substitution:

  • Declaratively, using the Substitution control.
  • Programmatically, using the Substitution control API.
  • Implicitly, using the AdRotator control.

Substitution Control

The ASP.NET Substitution control specifies a section of a cached page that is created dynamically rather than cached. You place a Substitution control at the location on the page where you want the dynamic content to appear. At run time, the Substitution control calls a method that you specify with the MethodName property. The method must return a string, which then replaces the content of the Substitution control. The method must be a static method on the containing Page or UserControl control. Using the substitution control causes client-side cacheability to be changed to server cacheability, so that the page will not be cached on the client. This ensures that future requests to the page call the method again to generate dynamic content.

Substitution API

To create dynamic content for a cached page programmatically, you can call the WriteSubstitution method in your page code, passing it the name of a method as a parameter. The method that handles the creation of the dynamic content takes a single HttpContext parameter and returns a string. The return string is the content that will be substituted at the given location. An advantage of calling the WriteSubstitution method instead of using the Substitution control declaratively is that you can call a method of any arbitrary object rather than calling a static method of the Page or the UserControl object.

Calling the WriteSubstitution method causes client-side cacheability to be changed to server cacheability, so that the page will not be cached on the client. This ensures that future requests to the page call the method again to generate dynamic content.

AdRotator Control

The AdRotator server control implements support for post-cache substitution internally. If you place an AdRotator control on your page, it will render unique advertisements on each request, regardless of whether the parent page is cached. As a result, a page that includes an AdRotator control is only cached server-side.

ControlCachePolicy Class

The ControlCachePolicy class allows for the programmatic control of fragment caching using user controls. ASP.NET embeds user controls within a BasePartialCachingControl instance. The BasePartialCachingControl class represents a user control that has output caching enabled.

When you access the BasePartialCachingControl.CachePolicy property of a PartialCachingControl control, you will always receive a valid ControlCachePolicy object. However, if you access the UserControl.CachePolicy property of a UserControl control, you receive a valid ControlCachePolicy object only if the user control is already wrapped by a BasePartialCachingControl control. If it is not wrapped, the ControlCachePolicy object returned by the property will throw exceptions when you attempt to manipulate it because it does not have an associated BasePartialCachingControl. To determine whether a UserControl instance supports caching without generating exceptions, inspect the SupportsCaching property.

Using the ControlCachePolicy class is one of several ways you can enable output caching. The following list describes methods you can use to enable output caching:

  • Use the @ OutputCache directive to enable output caching in declarative scenarios.
  • Use the PartialCachingAttribute attribute to enable caching for a user control in a code-behind file.
  • Use the ControlCachePolicy class to specify cache settings in programmatic scenarios in which you are working with BasePartialCachingControl instances that have been cache-enabled using one of the previous methods and dynamically loaded using the System.Web.UI.TemplateControl.LoadControl method.

A ControlCachePolicy instance can be successfully manipulated only between the Init and PreRender stages of the control life cycle. If you modify a ControlCachePolicy object after the PreRender phase, ASP.NET throws an exception because any changes made after the control is rendered cannot actually affect cache settings (a control is cached during the Render stage). Finally, a user control instance (and therefore its ControlCachePolicy object) is only available for programmatic manipulation when it is actually rendered.

Changes to Caching Configuration - The <caching> Element

There are several changes to caching configuration in ASP.NET 2.0. The <caching> element is new in ASP.NET 2.0 and allows you to make caching configuration changes in the configuration file. The following attributes are available.

Element Description
cache Optional element. Defines global application cache settings.
outputCache Optional element. Specifies application-wide output-cache settings.
outputCacheSettings Optional element. Specifies output-cache settings that can be applied to pages in the application.
sqlCacheDependency Optional element. Configures the SQL cache dependencies for an ASP.NET application.

The <cache> Element

The following attributes are available in the <cache> element:

Attribute Description
disableMemoryCollection Optional Boolean attribute. Gets or sets a value indicating whether the cache memory collection that occurs when the machine is under memory pressure is disabled.
disableExpiration Optional Boolean attribute. Gets or sets a value indicating whether cache expiration is disabled. When disabled, cached items do not expire and background scavenging of expired cache items does not occur.
privateBytesLimit Optional Int64 attribute. Gets or sets a value indicating the maximum size of an application's private bytes before the cache starts flushing expired items and attempting to reclaim memory. This limit includes both memory used by the cache as well as normal memory overhead from the running application. A setting of zero indicates that ASP.NET will use its own heuristics for determining when to start reclaiming memory.
percentagePhysicalMemoryUsedLimit Optional Int32 attribute. Gets or sets a value indicating the maximum percentage of a machine's physical memory that can be consumed by an application before the cache starts flushing expired items and attempting to reclaim memory This memory usage includes both memory used by the cache as well as the normal memory usage of the running application. A setting of zero indicates that ASP.NET will use its own heuristics for determining when to start reclaiming memory.
privateBytesPollTime Optional TimeSpan attribute. Gets or sets a value indicating the time interval between polling for the application's private bytes memory usage.

The <outputCache> Element

The following attributes are available for the <outputCache> element.

Attribute Description
enableOutputCache Optional Boolean attribute. Enables/disables the page output cache. If disabled, no pages are cached regardless of the programmatic or declarative settings. Default value is true.
enableFragmentCache Optional Boolean attribute. Enables/disables the application fragment cache. If disabled, no pages are cached regardless of the @ OutputCache directive or caching profile used. Includes a cache-control header indicating that upstream proxy servers as well as browser clients should not attempt to cache page output. Default value is false.
sendCacheControlHeader Optional Boolean attribute. Gets or sets a value indicating whether the cache-control:private header is sent by the output cache module by default. Default value is false.
omitVaryStar Optional Boolean attribute. Enables/disables sending an Http "Vary: </strong>" header in the response. With the default setting of false, a "*Vary: *" header is sent for output cached pages. When the Vary header is sent, it allows for different versions to be cached based upon what is specified in the Vary header. For example, Vary:User-Agents will store different versions of a page based upon the user agent issuing the request. Default value is **false.

The <outputCacheSettings> Element

The <outputCacheSettings> element allows for the creation of cache profiles as previously described. The only child element for the <outputCacheSettings> element is the <outputCacheProfiles> element for configuring cache profiles.

The <sqlCacheDependency> Element

The following attributes are available for the <sqlCacheDependency> element.

Attribute Description
enabled Required Boolean attribute. Indicates whether or not changes are being polled for.
pollTime Optional Int32 attribute. Sets the frequency with which the SqlCacheDependency polls the database table for changes. This value corresponds to the number of milliseconds between successive pollings. It cannot be set to less than 500 milliseconds. Default value is 1 minute.

More Information

There is some additional information that you should be aware of regarding cache configuration.

  • If the worker process private bytes limit is not set, the cache will use one of the following limits:

    • x86 2GB: 800MB or 60% of physical RAM, whichever is less
    • x86 3GB: 1800MB or 60% of physical RAM, whichever is less
    • x64: 1 terabyte or 60% of physical RAM, whichever is less
  • If both the worker process private bytes limit and <cache privateBytesLimit/> are set, the cache will use the minimum of the two.

  • Just like in 1.x, we drop cache entries and call GC.Collect for two reasons:

    • We are very close to the private bytes limit
    • The available memory is near or less than 10%
  • You can effectively disable trim and cache for low available memory conditions by setting <cache percentagePhysicalMemoryUseLimit/> to 100.

  • Unlike 1.x, 2.0 will suspend the trim and collect calls if the last GC.Collect did not reduce private bytes or the size of the managed heaps by more than 1% of the (cache) memory limit.

Lab1: Custom Cache Dependencies

  1. Create a new Web site.

  2. Add a new XML file called cache.xml and save it to the root of the Web application.

  3. Add the following code to the Page_Load method in the code-behind of default.aspx:

    System.Web.Caching.CacheDependency dep = new
        System.Web.Caching.CacheDependency(Server.MapPath("cache.xml"));
    Response.AddCacheDependency(dep);
    Cache.Insert("time", DateTime.Now.ToString());
    Response.Write(Cache["time"]);
    
  4. Add the following to the top of default.aspx in source view:

    <%@ OutputCache Duration="240" VaryByParam="None" %>
    
  5. Browse Default.aspx. What does the time say?

  6. Refresh the browser. What does the time say?

  7. Open cache.xml and add the following code:

    <anElement></anElement>
    
  8. Save cache.xml.

  9. Refresh your browser. What does the time say?

  10. Explain why the time updated instead of displaying the previously cached values:

Lab 2: Using Polling-Based Cache Dependencies

This lab uses the project you created in the previous module that allows for editing of data in the Northwind database via a GridView and DetailsView control.

  1. Open the project in Visual Studio 2005.

  2. Run the aspnet_regsql utility against the Northwind database to enable the database and the Products table. Use the following command from a Visual Studio Command Prompt:

    aspnet_regsql -S server -ed -d Northwind -E -et -t Products
    
  3. Add the following to your web.config file:

    <caching>
        <sqlCacheDependency enabled = "true" pollTime = "60000" >
            <databases>
                <add name="Northwind" connectionStringName = "Northwind" pollTime = "9000000" />
            </databases>
        </sqlCacheDependency>
    </caching>
    
  4. Add a new webform called showdata.aspx.

  5. Add the following @ outputcache directive to the showdata.aspx page:

    <%@ OutputCache SqlDependency="Northwind:Products" Duration="480" VaryByParam="None"%>
    
  6. Add the following code to the Page_Load of showdata.aspx:

    Response.Write(DateTime.Now.ToString() + "<br><br>");
    
  7. Add a new SqlDataSource control to showdata.aspx and configure it to use the Northwind database connection. Click Next.

  8. Select the ProductName and ProductID checkboxes and click Next.

  9. Click Finish.

  10. Add a new GridView to the showdata.aspx page.

  11. Choose SqlDataSource1 from the dropdown.

  12. Save and browse showdata.aspx. Make note of the time displayed.