ASP.NET 2.0's New and Improved Caching Features

The ASP.NET 1.x Cache API was a revolutionary feature that provided capabilities such as declarative output caching, programmatic output caching, and invalidation of cached items when the contents of an XML file or another cached item change. Even though all these increased the performance of Web applications, ASP.NET 1.x did not provide a mechanism for invalidating the data in a cache object when the data in a database changes. This much sought after feature will finally ship with ASP.NET 2.0. Apart from this, ASP.NET 2.0 will also provide functionalities to cache the output of a SqlDataSource control, which enable you to take advantage of caching without writing a single line of code. This article discusses some of these new caching features and demonstrates how you can employ them in your Web applications.

Introduction

In ASP.NET 2.0, caching has improved in a couple of notable ways. Probably the most interesting improvement is the introduction of database-triggered cache invalidation. In ASP.NET 1.x, you can invalidate a cached item based on some pre-defined conditions, such as change in an XML file, change in another cache item, and so on. By using this feature, you can remove or invalidate an item from the cache when the data or another cached item changes. However, the ASP.NET 1.x Cache API does not allow you to invalidate an item in the cache when data in a SQL Server database changes, even though most applications require this capability. ASP.NET 2.0 addresses this by providing the database-triggered cache invalidation capability that allows you to ensure that items in the cache are kept up to date with the changes in the database.

Another important caching feature in ASP.NET 2.0 is the ability to enable caching at the SqlDataSource level. The SqlDataSource control is designed to work with SQL Server, OLE DB, ODBC, and Oracle databases. As the name suggests, this control enables you to select, update, delete, and insert data using SQL commands. With the ability to set caching attributes at the SqlDataSource control level, you now have a finer level of control over the cached data.

ASP.NET 2.0 also provides a new control named Substitution, which you can use to inject dynamic content in an otherwise cached Web page. If you have a page with output-caching but still want to display dynamic content (that needs to be generated every time the page is requested), consider using the Substitution control.

The following sections provide examples of the above features.

Time-Based Cache Invalidation in a SqlDataSource Control

Caching in ASP.NET is a powerful feature that can increase the performance of a Web application. In fact, the most dramatic way to improve the performance of a database-driven Web application is through caching. Retrieving data from a database is one of the slowest Web site operations that you can perform. However, if you can cache the database data in memory and avoid accessing the database with every page request, you can dramatically increase the performance of your application.

ASP.NET 2.0 provides a number of enhancements to the caching feature set in ASP.NET 1.x. One new feature is the ability to specify the caching attributes as part of the data source control declarations. The new data source controls in ASP.NET 2.0 work seamlessly with the new caching features of ASP.NET 2.0, enabling you to set the caching attributes as part of the SqlDataSource control declaration.

You can set the following two properties in the SqlDataSource control to enable caching:

  • EnableCaching—By setting this attribute to true, you enable caching in a SqlDataSource control.
  • CacheDuration—This property allows you to set or get the duration of the cached data in the SqlDataSource control. This attribute is specified in terms of seconds.

For the purposes of this example, consider a categories and products table in the Northwind database. It displays all the categories in a DropDownList and the products that belong to a specific category in a GridView control. Start by creating a new Web site named Caching in Visual Studio 2005. Next, add a Web page named TimeBasedCaching.aspx to it. Modify the code in the TimeBasedCaching.aspx file to look like the following:

<%@ Page Language="C#" %>
<html>
<head>
  <title>SqlDataSource Control Caching And Parameters</title>
</head>
<body>
  <form id="form1" runat="server">
    <asp:DropDownList DataValueField="CategoryID"
      DataTextField="CategoryName"
      DataSourceID="CategoriesDataSource"
      ID="DropDownList1" Runat="server" AutoPostBack="True">
    </asp:DropDownList>
    <br/><br/>
    <asp:GridView ID="GridView1" Runat="server"
       DataSourceID="ProductsDataSource"
       DataKeyNames="ProductID" AutoGenerateColumns="False">
       <Columns>
         <asp:BoundField HeaderText="ProductID" DataField="ProductID"
            SortExpression="ProductID" />
  <asp:BoundField HeaderText="Timestamp" DataField="Timestamp"
            SortExpression="Timestamp" />
         <asp:BoundField HeaderText="ProductName"
                         DataField="ProductName"
            SortExpression="ProductName" />
         <asp:BoundField HeaderText="QuantityPerUnit"
            DataField="QuantityPerUnit"
            SortExpression="QuantityPerUnit" />
         <asp:BoundField HeaderText="UnitPrice" DataField="UnitPrice"
            SortExpression="UnitPrice" />
       </Columns>
    </asp:GridView>
    <asp:SqlDataSource ID="ProductsDataSource" Runat="server"
       SelectCommand="SELECT DatePart(second, GetDate())
                      As Timestamp, *
       FROM [Products] where CategoryID = @CategoryID"
       ConnectionString="<%$ ConnectionStrings:Northwind %>"
       EnableCaching="True" CacheDuration="10">
       <SelectParameters>
         <asp:ControlParameter Name="CategoryID"
                               ControlID="DropDownList1"
           PropertyName="SelectedValue" />
       </SelectParameters>
    </asp:SqlDataSource>
    <asp:SqlDataSource ID="CategoriesDataSource" Runat="server"
       SelectCommand="SELECT * FROM [Categories]"
                               ConnectionString="<%$
       ConnectionStrings:Northwind %>" 
       EnableCaching="True" CacheDuration="10"/>
  </form>
</body>
</html>

In the preceding code, the connection string to the database is retrieved from the web.config file. The web.config file contains the following connectionStrings element:

<connectionStrings>
    <add name="Pubs"
         connectionString="server=localhost;database=Pubs;
         trusted_connection=true"/>
    <add name="Northwind"
         connectionString="server=localhost;database=Pubs;
         trusted_connection=true"/>
</connectionStrings>

Now that the required connection string is defined in the web.config file, the SqlDataSource control can use that connection string by using the following declaration:

<%$ ConnectionStrings:Northwind %>

The above code retrieves the connection string value defined in the connectionString attribute of the Northwind connection string element.

The SqlDataSource control also has the EnableCaching property set to true, which results in the SqlDataSource automatically caching the data retrieved by the SelectCommand. The CacheDuration property enables you to specify (in seconds) how long the data should be cached before it is refreshed from the database. By default, the SqlDataSource will cache data using an absolute expiration policy, meaning that the data will be refreshed every so many seconds as specified in the CacheDuration property.

You also have the option of configuring the SqlDataSource to use a sliding expiration policy, by which the data is not dropped as long as it continues to be accessed. Employing a sliding expiration policy is useful whenever you have a large number of items that need to be cached, because the policy enables you to keep only the most frequently accessed items in memory. In the above example, you cached the results of the SQL query for 10 seconds by setting the EnableCaching and CacheDuration attributes to True and 10, respectively (see the output screenshot below).

As you can see, the values in the timestamp column remain the same for 10 seconds. After that, they will be refreshed with the new timestamp values from the database.

ASP.NET 2.0's New and Improved Caching Features

SQL Cache Invalidation in a SqlDataSource Control

So far, you have seen how to implement the cache invalidation based on the time duration set in the CacheDuration property of the SqlDataSource control. This section explains the steps involved in implementing a cache invalidation mechanism based on data in SQL Server tables. This cache invalidation mechanism is a new feature in ASP.NET 2.0 wherein a cached page or a data source control can be tied to a particular table in a SQL Server database. Once you perform this initial association, changing the contents of the table results in the cached object being automatically invalidated. You should consider using SQL cache invalidation with the data source controls when you need to work with the same database data in multiple pages.

The SQL Server-based cache invalidation mechanism works with SQL Server 7.0 and above. However, SQL Server 7.0 and 2000 support only the table-level cache invalidation mechanism. This means that the cached items are automatically invalidated any time data in the table changes. The next release of SQL Server (SQL Server 2005) will also feature a row-level cache invalidation mechanism that provides a finer level of accuracy over the cached data.

In SQL Server 7 and SQL Server 2000, table-level cache invalidation is supported using a polling system. The ASP.NET process polls the database (pull model) every so many seconds to monitor which tables have changed since it last checked. Even though the pull model works for most cases, it is not an efficient approach. However, this will be enhanced in SQL Server 2005 to have SQL Server actually notify (Push model) ASP.NET whenever a particular row of data has been modified. SQL Server 2005 accomplishes this by using a feature named Notification Delivery Services (that uses port 80), which directly interacts with HTTP.SYS of IIS 6.0 to notify the Web server of updates to specific rows. This article considers SQL Server 7 and 2000 and explains how to configure caching for those versions.

Before you can establish cache dependency with SQL Server 7 or SQL Server 2000, you need to perform the following steps:

  • Configure SQL Server to support SQL Cache invalidation. This is a one-time setup of the tables or databases in the SQL Server database that you want to monitor.
  • Add the necessary configuration information to the web.config file.

Let's walk through the above steps in detail, starting with a look at the configuration of SQL Server.

Configuration of SQL Server to Support SQL Cache Invalidation

You can perform the configuration of SQL Server 2000 to support SQL Cache invalidation in two ways:

  1. Using the aspnet_regsql utility
  2. Using the EnableTableForNotifications method of the SqlCacheDependencyAdmin class

For this article, consider the first method. Basically, the aspnet_regsql utility creates an extra table named AspNet_SqlCacheTablesForChangeNotification that is used to keep track of the changes to all the monitored tables in the database. It also creates a number of triggers and stored procedures to enable this capability. To run the aspnet_regsql utility, open up the Visual Studio command prompt and enter the command shown in the following screenshot.

The command enables the Pubs database to support SQL cache invalidation:

  • S—Name of the Server
  • U—User ID to use to connect to the SQL Server
  • P—Password to use to connect to the SQL Server
  • d—The name of the database
  • ed—Enables the database for SQL Server-triggered cache invalidation

Once this is done at the database level, you need to enable cache invalidation at the individual table level. This is shown in the following screenshot.

In the above command:

  • t—Specifies the name of the table
  • et—Enables the table for SQL Server-triggered cache invalidation

The preceding screenshot shows how to enable SQL cache invalidation for the authors table in the Pubs database. Once you configure the authors table to send notifications, any time data in the table changes, it notifies ASP.NET to invalidate the corresponding item in the cache.

Web Configuration Settings for SQL Cache Invalidation

The next step, before you can use SQL cache invalidation in your ASP.NET application, is to update the Web configuration file. You need to instruct the ASP.NET framework to poll the databases that you have enabled for SQL cache invalidation. The following Web configuration file contains the necessary configuration information to poll the Pubs database at periodic intervals:

<configuration>
  <connectionStrings>
    <add name="Pubs"
         connectionString="Server=localhost;Database=Pubs;uid=sa;
                           pwd=thiru" />
  </connectionStrings>
  <system.web>
    <caching>
       <sqlCacheDependency enabled="true">
          <databases>
          <add name="Pubs"
             connectionStringName="Pubs"
             pollTime="60000" />
          </databases>
</sqlCacheDependency>
    </caching>
  </system.web>
</configuration>

The preceding Web configuration file contains two sections, <connectionStrings> and <caching>. The connectionStrings section creates a database connection string to the Pubs database named "Pubs". The caching section configures the SQL cache invalidation polling. Within the databases subsection, you list one or more databases that you want to poll for changes. The add section inside the databases section indicates that the database represented by "Pubs" is polled once a minute (every 60,000 milliseconds). You can specify different polling intervals for different databases. Remember, the server must do a little bit of work every time the database is polled for changes. If you don't expect the data in the database to change very often, you should increase the polling interval.

Implementing SQL Cache Invalidation in a SqlDataSource Control

Now that you have performed the required configurations, you are ready to take advantage of the SQL cache invalidation feature in your ASP.NET Web page. Add a new Web form named SqlCacheInvalidation.aspx to your Web site. The following is the code for the SqlCacheInvalidation.aspx:

<%@ Page Language="C#" %>
<html>
<head>
    <title>SQL Cache Invalidation</title>
</head>
<body>
<form id="form1" runat="server">
    <asp:GridView ID="GridView1" Runat="server"
DataSourceID="SqlDataSource1" DataKeyNames="au_id"
AutoGenerateColumns="False">
       <Columns>
          <asp:CommandField ShowEditButton="True" />
          <asp:BoundField ReadOnly="True" HeaderText="timestamp"
            DataField="timestamp" SortExpression="timestamp" />
          <asp:BoundField ReadOnly="True" HeaderText="au_id" 
            DataField="au_id" SortExpression="au_id" />
          <asp:BoundField HeaderText="au_lname" DataField="au_lname"
            SortExpression="au_lname" />
          <asp:BoundField HeaderText="au_fname" DataField="au_fname"
            SortExpression="au_fname" />
          <asp:BoundField HeaderText="phone" DataField="phone"
            SortExpression="phone" />
          <asp:BoundField HeaderText="address" DataField="address"
            SortExpression="address" />
          <asp:BoundField HeaderText="city" DataField="city"
            SortExpression="city" />
          <asp:BoundField HeaderText="state" DataField="state"
            SortExpression="state" />
          <asp:BoundField HeaderText="zip" DataField="zip"
            SortExpression="zip" />
          <asp:CheckBoxField HeaderText="contract"
                             SortExpression="contract"
            DataField="contract" />
        </Columns>
    </asp:GridView>
    <asp:SqlDataSource ID="SqlDataSource1" Runat="server"
      SelectCommand="SELECT DatePart(second, GetDate())
      As timestamp, * FROM [authors]" ConnectionString="<%$
      ConnectionStrings:Pubs %>" EnableCaching="True"
      DeleteCommand="DELETE FROM [authors]
      WHERE [au_id] = @original_au_id"
      InsertCommand="INSERT INTO [authors] ([au_id], [au_lname],
      [au_fname], [phone], [address], [city], [state], [zip],
      [contract]) VALUES (@au_id, @au_lname, @au_fname, @phone,
      @address, @city, @state, @zip, @contract)"
      UpdateCommand="UPDATE [authors] SET [au_lname] = @au_lname,
      [au_fname] = @au_fname, [phone] = @phone,
      [address] = @address, [city] = @city, [state] = @state,
      [zip] = @zip, [contract] = @contract
      WHERE [au_id] = @original_au_id"
      SqlCacheDependency="Pubs:authors">
      <DeleteParameters>
        <asp:Parameter Name="original_au_id" />
      </DeleteParameters>
      <UpdateParameters>
        <asp:Parameter Type="String" Name="au_lname" />
        <asp:Parameter Type="String" Name="au_fname" />
        <asp:Parameter Type="String" Name="phone" />
        <asp:Parameter Type="String" Name="address" />
        <asp:Parameter Type="String" Name="city" />
        <asp:Parameter Type="String" Name="state" />
        <asp:Parameter Type="String" Name="zip" />
        <asp:Parameter Type="Boolean" Name="contract" />
        <asp:Parameter Name="original_au_id" />
      </UpdateParameters>
      <InsertParameters>
        <asp:Parameter Type="String" Name="au_id" />
        <asp:Parameter Type="String" Name="au_lname" />
        <asp:Parameter Type="String" Name="au_fname" />
        <asp:Parameter Type="String" Name="phone" />
        <asp:Parameter Type="String" Name="address" />
        <asp:Parameter Type="String" Name="city" />
        <asp:Parameter Type="String" Name="state" />
        <asp:Parameter Type="String" Name="zip" />
        <asp:Parameter Type="Boolean" Name="contract" />
      </InsertParameters>
    </asp:SqlDataSource>
</form>
</body>
</html>

The preceding code uses the SQL cache invalidation with the SqlDataSource control. As you can see, the SqlDataSource control contains both EnableCaching and SqlCacheDependency attributes. The SqlCacheDependency property uses the following syntax:

SqlCacheDependency="Pubs:authors"

ASP.NET 2.0's New and Improved Caching Features

In this attribute declaration, you list the name of the database, followed by the name of the database table. Because of this attribute, any time data in the authors table of the Pubs database changes, the cached data is automatically invalidated. The database name that you are specifying here should already be defined in the connectionStrings section of the web.config file.

As part of the SqlDataSource control declaration, you also specify the SelectCommand, InsertCommand, DeleteCommand, UpdateCommand, and ConnectionString properties. The xxxCommand attributes allow you to specify the SQL command to be executed, and the ConnectionString attribute allows you to specify the connection string to use for connecting to the Pubs database. Again, this value is also retrieved from the web.config file. Because the xxxCommand attributes also contain SQL query parameters, they are specified inside the DeleteParameters, UpdateParameters, and InsertParameters templates.

Finally, declare a GridView control and bind that GridView control to the SqlDataSource control using the DataSourceID attribute. By setting the DataSourceID attribute of the GridView to the ID of the SqlDataSource control, you automatically display the data returned from the SqlDataSource.

Now that you understand the code, test the caching functionality by navigating your browser to the page. You will see an output that is similar to the following:

The timestamp column displays the "seconds" portion of the date from the database. If you refresh the page again, you will see the same time in the displayed output, because you enabled caching on the SqlDataSource control.

Now, to test the SQL Server-based trigger invalidation, click the Edit hyperlink and update the author's information. You should see a change in the timestamp displayed in the above page. This clearly shows that your SQL Server-based trigger invalidation mechanism automatically invalidates the cached contents of the SqlDataSource control as soon as the data in the authors table changes.

Partial Page Caching

So far, you have seen how to use caching with the SqlDataSource control. This section shows how to use post-cache substitution to replace part of the cached content with refreshed content. This is called partial page caching or page fragment caching. It is a powerful feature that allows an application to use page-level caching, even if part of the page is dynamically generated.

Page fragment caching involves the caching of a fragment of the page, as opposed to the entire page. Sometimes, full-page output caching is not feasible—for example, when portions of the page need to be dynamically created for each user request. In such cases, it can be worthwhile to identify portions of the page or controls that do not often change and that take considerable time and server resources to create. After you identify these portions, you can wrap them in a Web Forms user control and cache the control so that these portions of the page don't need to be recreated each time. This was the only way to implement page fragment caching prior to ASP.NET 2.0. With ASP.NET 2.0's new post cache substitution feature, you can inform the ASP.NET runtime that a particular element, while present on a cached page, should be re-evaluated before the page is presented to the user. You can accomplish this in two ways:

  • Call the new Response.WriteSubstitution method, passing a reference to the substitution callback function.
  • Add an <asp:substitution> control to the Web page and set the MethodName attribute to the name of the callback function.

For either option, you need a @OutputCache directive at the top of the page that specifies the duration and other parameters of the dependency that should be added to the page. For this example, consider the Substitution control.

The Substitution control has one important property named MethodName, which is used to specify the method that will be invoked to provide the dynamic content. The method that the Substitution control calls must be a static method (or Shared method in VB.NET). Furthermore, the method must have one parameter that represents the current HttpContext. To demonstrate this, create a new Web form named PartialPageCaching.aspx and add it to the Web site. The following is the code for the PartialPageCaching.aspx:

<%@ Page Language="C#" %>
<%@ OutputCache Duration="6000" VaryByParam="none" %>
<script runat="server">
    public static string GetRandomNumber(HttpContext context)
    {
        int randomNumber;
        randomNumber = new System.Random().Next(1, 10000);
        return randomNumber.ToString();
    }
</script>
<html>
<head>
  <title>Partial Page Caching using Substitution control</title>
</head>
<body>
  <form id="form1" runat="server">
   The random number generated is:
   <asp:Substitution ID="Substitution1" MethodName="GetRandomNumber"
      Runat="Server"/>
   <p>
   The current time is
   <%= DateTime.Now.ToString("t") %>. It never changes since the
      page is cached.
   </p>
  </form>
</body>
</html>

At the top of the page, the OutputCache directive caches the contents of the page in memory. In the OutputCache directive, you set the Duration attribute to 6,000 milliseconds. The VaryByParam attribute indicates whether or not ASP.NET should take into consideration page parameters when caching. When VaryByParam is set to none, no parameters will be considered; all users will receive the same page no matter what additional parameters are supplied (see the output below).

The MethodName attribute of the Substitution control is set to a method named GetRandomNumber, which simply returns a random number between 1 and 10,000. When you make a request for the page, the displayed current time always remains the same, whereas the portion of the page that is generated by the substitution control keeps changing every time. In this case, it displays a random number between 1 and 10,000 every time someone requests the page.

Building on the Cache API Foundation

The Cache API in ASP.NET 2.0 builds on the foundation provided by the one in ASP.NET 1.0 and makes building high-performance ASP.NET applications extremely easy and seamless. The new DataSource controls include properties that make it easy to cache database data in memory. By taking advantage of these controls, you can retrieve and cache database data without writing a single line of code. Being able to invalidate a cached item when the data in the database changes is a capability that can go a long way in revolutionizing the way ASP.NET applications are built and deployed. Finally, the new Substitution control enables you to easily inject dynamic content in a cached page.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • Confused by all the agile advice? Relax! With the Agile for Dummies eBook by your side you'll learn the fundamentals of agile and how to increase the productivity of your software teams while enabling them to produce higher-quality solutions that better fulfill customer needs much faster.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds