Chapter 25. Caching Application Pages and Data

<feature>

IN THIS CHAPTER

</feature>

If someone put a gun to my head and told me that I had 5 minutes to improve the performance of a website, then I would immediately think of caching. By taking advantage of caching, you can dramatically improve the performance of your web applications.

The slowest operation that you can perform in an ASP.NET page is database access. Opening a database connection and retrieving data is a slow operation. The best way to improve the performance of your data access code is not to access the database at all.

By taking advantage of caching, you can cache your database records in memory. Retrieving data from a database is dog slow. Retrieving data from the cache, on the other hand, is lightning fast.

In this chapter, you learn about the different caching mechanisms supported by the ASP.NET Framework. The ASP.NET Framework provides you with an (almost) overwhelming number of caching options. I attempt to clarify all these caching options over the course of this chapter.

In the final section of this chapter, you learn how to use SQL Cache Dependencies. A SQL Cache Dependency enables you to reload cached data automatically when data changes in a database table. You learn how to use both polling and push SQL Cache Dependencies.

Overview of Caching

The ASP.NET 3.5 Framework supports the following types of caching:

  • Page Output Caching

  • Partial Page Caching

  • DataSource Caching

  • Data Caching

Page Output Caching enables you to cache the entire rendered contents of a page in memory (everything that you see when you select View Source in your web browser). The next time that any user requests the same page, the page is retrieved from the cache.

Page Output Caching caches an entire page. In some situations, this might create problems. For example, if you want to display different banner advertisements randomly in a page, and you cache the entire page, then the same banner advertisement is displayed with each page request.

Note

The AdRotator control included in the ASP.NET Framework takes advantage of a feature called post-cache substitution to randomly display different advertisements even when a page is cached. Post-cache substitution is described later in this chapter.

Partial Page Caching enables you to get around this problem by enabling you to cache only particular regions of a page. By taking advantage of Partial Page Caching, you can apply different caching policies to different areas of a page.

You use DataSource Caching with the different ASP.NET DataSource controls such as the SqlDataSource and ObjectDataSource controls. When you enable caching with a DataSource control, the DataSource control caches the data that it represents.

Finally, Data Caching is the fundamental caching mechanism. Behind the scenes, all the other types of caching use Data Caching. You can use Data Caching to cache arbitrary objects in memory. For example, you can use Data Caching to cache a DataSet across multiple pages in a web application.

In the following sections, you learn how to use each of these different types of caching in detail.

Note

Caching LINQ to SQL queries raises special issues, which are addressed in Chapter 18, “Data Access with LINQ to SQL.”

Note

When configuring and debugging caching, having a tool that enables you to monitor the HTTP traffic between web server and browser is extremely helpful. You can download the free Fiddler tool, which enables you to view the raw request and response HTTP traffic, from www.FiddlerTool.com.

Using Page Output Caching

You enable Page Output Caching by adding an <%@ OutputCache %> directive to a page. For example, the page in Listing 25.1 caches its contents for 15 seconds.

Example 25.1. CachePageOutput.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="15" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        lblTime.Text = DateTime.Now.ToString("T");
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Cache Page Output</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblTime"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 25.1 displays the current server time in a Label control. The page also includes an <%@ OutputCache %> directive. If you refresh the page multiple times, you will notice that the time is not updated until at least 15 seconds have passed.

When you cache a page, the contents of the page are not regenerated each time you request the page. The .NET class that corresponds to the page is not executed with each page request. The rendered contents of the page are cached for every user that requests the page.

The page is cached in multiple locations. By default, the page is cached on the browser, any proxy servers, and on the web server.

In Listing 25.1, the page is cached for 15 seconds. You can assign a much larger number to the duration attribute. For example, if you assign the value 86400 to the duration parameter, then the page is cached for a day.

Note

There is no guarantee that a page will be cached for the amount of time that you specify. When server memory resources become low, items are automatically evicted from the cache.

Varying the Output Cache by Parameter

Imagine that you need to create a separate master and details page. The master page displays a list of movies. When you click a movie title, the details page displays detailed information on the movie selected.

When you create a master/details page, you typically pass a query string parameter between the master and details page to indicate the particular movie to display in the details page. If you cache the output of the details page, however, then everyone will see the first movie selected.

You can get around this problem by using the VaryByParam attribute. The VaryByParam attribute causes a new instance of a page to be cached when a different parameter is passed to the page. (The parameter can be either a query string parameter or a form parameter.)

For example, the page in Listing 25.2 contains a master page that displays a list of movie titles as links.

Example 25.2. Master.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Master</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        AutoGenerateColumns="false"
        ShowHeader="false"
        GridLines="none"
        Runat="server">
        <Columns>
        <asp:HyperLinkField
            DataTextField="Title"
            DataNavigateUrlFields="Id"
            DataNavigateUrlFormatString="~/Details.aspx?id={0}" />
        </Columns>
    </asp:GridView>

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Id,Title FROM Movies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

If you hover your mouse over the links displayed in Listing 25.2, you can see the query string parameter passed by each link in the browser status bar (see Figure 25.1). For example, the first movie link includes a query string parameter with the value 1, the second link includes a query string parameter with the value 2, and so on. When you click a movie link, this query string parameter is passed to the details page in Listing 25.3.

Displaying the Master page.

Figure 25.1. Displaying the Master page.

Example 25.3. Details.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="id" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Details</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>

    <hr />

    <asp:DetailsView
        id="dtlMovie"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT * FROM Movies
            WHERE Id=@Id"
        Runat="server">
        <SelectParameters>
            <asp:QueryStringParameter
                Name="Id"
                Type="int32"
                QueryStringField="Id" />
        </SelectParameters>
    </asp:SqlDataSource>

    </div>
    </form>
</body>
</html>

The page in Listing 25.3 uses a DetailsView to display detailed information on the movie selected from the master page (see Figure 25.2). The DetailsView is bound to a SqlDataSource control that includes a QueryStringParameter SELECT parameter that represents the id query string parameter.

Displaying the Details page.

Figure 25.2. Displaying the Details page.

Notice that the Details.aspx page includes an <%@ OutputCache %> directive. The VaryByParam attribute in the <%@ OutputCache %> directive has the value id. If you request the Details.aspx page with a different value for the id query string parameter, then a different cached version of the page is created.

It is important to understand that using VaryByParam results in more caching and not less caching. Each time a different id parameter is passed to the Details.aspx page, another version of the same page is cached in memory.

The Details.aspx page displays the current time. Notice that the time does not change when you request the Details.aspx page with the same query string parameter.

You can assign two special values to the VaryByParam attribute:

  • noneCauses any query string or form parameters to be ignored. Only one version of the page is cached.

  • *Causes a new cached version of the page to be created whenever there is a change in any query string or form parameter passed to the page.

You also can assign a semicolon-delimited list of parameters to the VaryByParam attribute when you want to create different cached versions of a page, depending on the values of more than one parameter.

Varying the Output Cache by Control

The VaryByControl attribute enables you to generate different cached versions of a page depending on the value of a particular control in the page. This attribute is useful when you need to create a single-page Master/Details form.

For example, the page in Listing 25.4 contains both a DropDownList and GridView control. When you select a new movie category from the DropDownList, a list of matching movies is displayed in the GridView (see Figure 25.3).

Displaying a single-page Master/Details form.

Figure 25.3. Displaying a single-page Master/Details form.

Example 25.4. MasterDetails.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByControl="dropCategories" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Master/Details</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    <asp:DropDownList
        id="dropCategories"
        DataSourceID="srcCategories"
        DataTextField="Name"
        DataValueField="Id"
        Runat="server" />
    <asp:Button
        id="btnSelect"
        Text="Select"
        Runat="server" />

    <br /><br />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        GridLines="none"
        Runat="server" />

    <asp:SqlDataSource
        id="srcCategories"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Id,Name FROM MovieCategories"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Title,Director FROM Movies
            WHERE CategoryId=@CategoryId"
        Runat="server">
        <SelectParameters>
        <asp:ControlParameter
            Name="CategoryId"
            ControlID="dropCategories" />
        </SelectParameters>
    </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

The page in Listing 25.4 contains an <%@ OutputCache %> directive. This directive includes a VaryByControl parameter. The ID of the DropDownList control is assigned to this parameter.

If you neglected to add the VaryByControl attribute, then the same list of movies would be displayed in the GridView regardless of which movie category is selected. The VaryByControl attribute causes different cached versions of the page to be created whenever the DropDownList represents a different value.

Varying the Output Cache by Header

Another option is to use the VaryByHeader attribute to create different cached versions of a page when the value of a particular browser header changes. Several standard browser headers are transmitted with each page request, including

  • Accept-LanguageRepresents a prioritized list of languages that represent the preferred human language of the user making the request.

  • User-AgentRepresents the type of device making the request.

  • CookieRepresents the browser cookies created in the current domain.

For example, the page in Listing 25.5 includes an <%@ OutputCache %> directive that has a VaryByHeader attribute with the value User-Agent. When you request the page with different browsers, different versions of the page are cached.

Example 25.5. VaryByHeader.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="none" VaryByHeader="User-Agent" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Vary By Header</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    <%= Request.UserAgent %>

    </div>
    </form>
</body>
</html>

I don’t recommend using the VaryByHeader attribute with the User-Agent header. The problem with this attribute is that it is too fine-grained. If there is any variation in the User-Agent header, then a different cached version of a page is generated.

Consider the User-Agent header sent by the Internet Explorer browser installed on my computer. It looks like this:

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322;
.NET CLR 2.0.50727)

This header includes the major and minor version of the browser, the platform (Windows XP), a string indicating that Service Pack 2 has been installed (SV1), and the versions of the .NET framework installed on my machine. If someone else requests the same page with a slight difference in the User-Agent header, then a different cached version of the page is generated. In other words, the web server must do more work rather than less, which defeats the point of caching.

Instead of using the VaryByHeader attribute, I recommend that you use the VaryByCustom attribute described in the next two sections.

Varying the Output Cache by Browser

A better way to create different cached versions of a page that depend on the type of browser being used to request the page is to use the VaryByCustom attribute. This attribute accepts the special value browser. When VaryByCustom has the value browser, only two attributes of the browser are considered important: the type of browser and its major version.

For example, a page request from Internet Explorer results in a different cached version of the page than does one from Firefox. A page request from Internet Explorer 5 rather than Internet Explorer 6.5 also results in a different cached version. Any other variations in the User-Agent header are ignored.

The page in Listing 25.6 illustrates how you can use the VaryByCustom attribute with the value browser. The page displays the current time and the value of the User-Agent header. If you request the page with Internet Explorer and request the page with Firefox, different cached versions of the page are created.

Example 25.6. VaryByBrowser.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="none" VaryByCustom="browser" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Vary By Browser</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>

    <hr />

    <%= Request.UserAgent %>

    </div>
    </form>
</body>
</html>

Varying the Output Cache by a Custom Function

The VaryByCustom attribute is named the VaryByCustom attribute for a reason. You can specify a custom function that determines when a different cached version of a page is generated.

You can use any criteria that you please with the custom function. You can create different cached versions of a page depending on the browser minor version, the browser DOM support, the time of day, or even the weather.

You create the custom function in the Global.asax file by overriding the GetVaryByCustomString() method. For example, the Global.asax file in Listing 25.7 illustrates how you can override the GetVaryByCustomString() method to create different cached versions of a page depending on a particular feature of a browser. If the VaryByCustom attribute in a page has the value css, then the function returns a string representing whether the current browser supports Cascading Style Sheets.

Example 25.7. Global.asax

<%@ Application Language="C#" %>
<script runat="server">
    public override string GetVaryByCustomString(HttpContext context, string custom)
    {
        if (String.Compare(custom, "css") == 0)
        {
            return Request.Browser.SupportsCss.ToString();
        }
        return base.GetVaryByCustomString(context, custom);
    }

</script>

The page in Listing 25.8 displays one of two Panel controls. The first Panel contains text formatted with a Cascading Style Sheet style, and the second Panel contains text formatted with (outdated) HTML. Depending on whether a browser supports CSS, either the first or second Panel is displayed.

Example 25.8. VaryByCustom.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="none" VaryByCustom="css" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        if (Request.Browser.SupportsCss)
            pnlCss.Visible = true;
        else
            pnlNotCss.Visible = true;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Vary By Custom</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Panel
        id="pnlCss"
        Visible="false"
        Runat="server">
        <span style="font-weight:bold">Hello!</span>
    </asp:Panel>

    <asp:Panel
        id="pnlNotCss"
        Visible="false"
        Runat="server">
        <b>Hello!</b>
    </asp:Panel>

    </div>
    </form>
</body>
</html>

Note

You can detect browser capabilities by using the HttpBrowserCapabilities class exposed by the Request.Browser property. This class includes dozens of properties that enable you to detect the features of the browser being used to request a page.

The page contains an <%@ OutputCache %> directive with a VaryByCustom attribute set to the value css. Two different cached versions of the same page are generated: one version for CSS browsers and another version for non-CSS browsers.

Specifying the Cache Location

You can use the Location attribute of the <%@ OutputCache %> directive to specify where a page is cached. This attribute accepts the following values:

  • AnyThe page is cached on the browser, proxy servers, and web server (the default value).

  • ClientThe page is cached only on the browser.

  • DownstreamThe page is cached on the browser and any proxy servers but not the web server.

  • NoneThe page is not cached.

  • ServerThe page is cached on the web server but not the browser or any proxy servers.

  • ServerAndClientThe page is cached on the browser and web server, but not on any proxy servers.

By default, when you use Page Output Caching, a page is cached in three locations: web server, any proxy servers, and browser. There are situations in which you might need to modify this default behavior. For example, if you are caching private information, then you don’t want to cache the information on the web server or any proxy servers.

Note

When Windows authentication is enabled in the web configuration file (the default), the Cache-Control header is automatically set to the value private, and the setting of the Location attribute is ignored.

For example, the page in Listing 25.9 caches a page only on the browser and not on any proxy servers or the web server. The page displays a random number (see Figure 25.4).

Caching a page on the browser.

Figure 25.4. Caching a page on the browser.

Example 25.9. CacheLocation.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="none" Location="Client" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        Random rnd = new Random();
        lblRandom.Text = rnd.Next(10).ToString();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Cache Location</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    Your random number is:
    <asp:Label
        id="lblRandom"
        Runat="server" />

    <br /><br />
    <a href="CacheLocation.aspx">Request Page</a>

    </div>
    </form>
</body>
</html>

If you click the link located at the bottom of the page in Listing 25.9 and request the same page, then the page is retrieved from the browser cache and the same random number is displayed. If you reload the page in your web browser by clicking your browser’s Reload button, then the page is reloaded from the web server and a new random number is displayed. The page is cached only in your local browser cache and nowhere else.

Note

Behind the scenes, the ASP.NET Framework uses the Cache-Control HTTP header to specify where a page is cached. This header is defined in RFC 2616, “Hypertext Transfer Protocol—HTTP/1.1.”

Creating a Page Output Cache File Dependency

You can create a dependency between a cached page and a file (or set of files) on your hard drive. When the file is modified, the cached page is automatically dropped and regenerated with the next page request.

For example, the page in Listing 25.10 displays the contents of an XML file in a GridView. The page is cached until the XML file is modified (see Figure 25.5).

Caching a page with a file dependency.

Figure 25.5. Caching a page with a file dependency.

Example 25.10. OutputCacheFileDependency.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="9999" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        Response.AddFileDependency(MapPath("Movies.xml"));
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Output Cache File Dependency</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <%= DateTime.Now.ToString("T") %>
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:XmlDataSource
        id="srcMovies"
        DataFile="Movies.xml"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 25.10 displays the current time. Notice that the time does not change until you modify the Movies.xml XML file.

The page in Listing 25.10 uses the Response.AddFileDependency() method to create a dependency between the cached page and a single file on disk. If you need to create a dependency on multiple files, then you can use the AddFileDependencies() method instead.

Expiring the Page Output Cache Programmatically

You can remove a page from the cache programmatically by using the Response.RemoveOutputCacheItem() method. For example, imagine that you are caching a page that displays a list of products. Furthermore, imagine that your website includes a separate page for adding a new product. In that case, you’ll want to remove the first page programmatically from the cache when the list of products is updated.

The page in Listing 25.11 uses a GridView control to display a list of movies. The page is cached for 1 hour with an <%@ OutputCache %> directive.

Example 25.11. MovieList.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Movie List</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Title, Director FROM Movies"
        Runat="server" />

    <br /><br />
    <a href="AddMovie.aspx">Add Movie</a>

    </div>
    </form>
</body>
</html>

The page in Listing 25.12 contains a DetailsView control that enables you to add a new movie. When you insert a new movie into the database, the Response.RemoveOutputCacheItem() method is called to remove the MovieList.aspx page from the cache. Because this method accepts only a “virtual absolute” path, the Page.ResolveUrl() method is used to convert the tilde into the application root path.

Example 25.12. AddMovie.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void dtlMovie_ItemInserted(object sender, DetailsViewInsertedEventArgs e)
    {
        HttpResponse.RemoveOutputCacheItem(Page.ResolveUrl("~/MovieList.aspx"));
        Response.Redirect("~/MovieList.aspx");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Add Movie</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>Add Movie</h1>

    <asp:DetailsView
        id="dtlMovie"
        DefaultMode="Insert"
        DataSourceID="srcMovies"
        AutoGenerateRows="false"
        AutoGenerateInsertButton="true"
        Runat="server" OnItemInserted="dtlMovie_ItemInserted">
        <Fields>
        <asp:BoundField
            DataField="Title"
            HeaderText="Title:" />
        <asp:BoundField
            DataField="Director"
            HeaderText="Director:" />
        </Fields>
    </asp:DetailsView>

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        InsertCommand="INSERT Movies (Title, Director)
            VALUES (@Title, @Director)"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The Response.RemoveOutputCacheItem() method enables you to remove only one page from the cache at a time. If you need to remove multiple pages, then you can create something called a key dependency. A key dependency enables you to create a dependency between one item in the cache and another item. When the second item is removed from the cache, the first item is removed automatically.

For example, the page in Listing 25.13 also displays a list of movies. However, the page is cached with a dependency on an item in the cache named Movies.

Example 25.13. MovieListKeyDependency.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void Page_Load(object sender, EventArgs e)
    {
        Cache.Insert("Movies", DateTime.Now);
        Response.AddCacheItemDependency("Movies");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Movie List Key Dependency</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Title, Director FROM Movies"
        Runat="server" />

   <br /><br />
   <a href="AddMovieKeyDependency.aspx">Add Movie</a>
    </div>
    </form>
</body>
</html>

The page in Listing 25.14 enables you to add a new movie to the Movies database table. When the new movie is inserted, the Movies item is removed and any pages that are dependent on the Movies item are dropped from the cache automatically.

Example 25.14. AddMovieKeyDependency.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void dtlMovie_ItemInserted(object sender, DetailsViewInsertedEventArgs e)
    {
        Cache.Remove("Movies");
        Response.Redirect("~/MovieListKeyDependency.aspx");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Add Movie Key Dependency</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>Add Movie</h1>

    <asp:DetailsView
        id="dtlMovie"
        DefaultMode="Insert"
        DataSourceID="srcMovies"
        AutoGenerateRows="false"
        AutoGenerateInsertButton="true"
        Runat="server" OnItemInserted="dtlMovie_ItemInserted">
        <Fields>
        <asp:BoundField
            DataField="Title"
            HeaderText="Title:" />
        <asp:BoundField
            DataField="Director"
            HeaderText="Director:" />
        </Fields>
    </asp:DetailsView>

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        InsertCommand="INSERT Movies (Title, Director)
            VALUES (@Title, @Director)"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Manipulating the Page Output Cache Programmatically

If you need more control over how the ASP.NET Framework caches pages, then you can work directly with the HttpCachePolicy class. This class is exposed by the Response.Cache property.

The HttpCachePolicy class includes properties and methods that enable you to perform programmatically all the tasks that you can perform with the <%@ OutputCache %> directive. You also can use the methods of this class to manipulate the HTTP cache headers that are sent to proxy servers and browsers.

This class supports the following properties:

  • VaryByHeadersGets the list of headers that are used to vary cache output.

  • VaryByParamsGets the list of query string and form parameters that are used to vary cache output.

The HttpCachePolicy class also supports the following methods:

  • AddValidationCallbackEnables you to create a method that is called automatically before a page is retrieved from the cache.

  • AppendCacheExtensionEnables you to add custom text to the Cache-Control HTTP header.

  • SetAllowResponseInBrowserHistoryEnables you to prevent a page from appearing in the browser history cache.

  • SetCacheabilityEnables you to set the Cache-Control header and the server cache.

  • SetETagEnables you to set the ETag HTTP header.

  • SetETagFromFileDependenciesEnables you to set the ETag HTTP header from the time stamps of all files on which the page is dependent.

  • SetExpiresEnables you to set the Expires HTTP header.

  • SetLastModifiedEnables you to set the Last-Modified HTTP header.

  • SetLastModifiedFromFileDependenciesEnables you to set the Last-Modified HTTP header from the time stamps of all files on which the page is dependent.

  • SetMaxAgeEnables you to set the Cache-Control:max-age HTTP header.

  • SetNoServerCachingEnables you to disable web server caching.

  • SetNoStoreEnables you to send a Cache-Control:no-store HTTP header.

  • SetNoTransformEnables you to send a Cache-Control:no-transform HTTP header.

  • SetOmitVaryStarEnables you to not send the vary:* HTTP header.

  • SetProxyMaxAgeEnables you to set the Cache-Control:s-maxage HTTP header.

  • SetRevalidationEnables you to set the Cache-Control HTTP header to either must-revalidation or proxy-revalidate.

  • SetSlidingExpirationEnables you to set a sliding expiration policy.

  • SetValidUntilExpiresEnables you to prevent a page from expiring from the web server cache when a browser sends a Cache-Control header.

  • SetVaryByCustomEnables you to set the string passed to the GetVaryByCustomString() method in the Global.asax file.

For example, the page in Listing 25.15 programmatically places a page in the output cache. The page is cached on the browser, proxy servers, and web server for 15 seconds.

Example 25.15. ProgramOutputCache.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        Response.Cache.SetCacheability(HttpCacheability.Public);
        Response.Cache.SetExpires(DateTime.Now.AddSeconds(15));
        Response.Cache.SetMaxAge(TimeSpan.FromSeconds(15));
        Response.Cache.SetValidUntilExpires(true);
        Response.Cache.SetLastModified(DateTime.Now);
        Response.Cache.SetOmitVaryStar(true);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Program OutputCache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>

    <br /><br />
    <a href="ProgramOutputCache.aspx">Request this Page</a>

    </div>
    </form>
</body>
</html>

Clearly, it is more difficult to enable Page Output Caching programmatically than declaratively. You need to call many methods to cache a page in the same way as you can with a single <%@ OutputCache %> directive. However, programmatically manipulating the cache provides you with fine-grained control over the HTTP headers sent to proxy servers and browsers.

Creating Page Output Cache Profiles

Instead of configuring Page Output Caching for each page in an application, you can configure Page Output Caching in a web configuration file and apply the settings to multiple pages. You can create something called a Cache Profile. Creating Cache Profiles makes your website easier to manage.

For example, the web configuration file in Listing 25.16 contains the definition for a Cache Profile named Cache1Hour that caches a page for one hour.

Example 25.16. Web.Config

<configuration>
  <system.web>
    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="Cache1Hour" duration="3600" varyByParam="none" />
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
  </system.web>
</configuration>

The page in Listing 25.17 uses the Cache1Hour profile. This profile is set with the <%@ OutputCache %> directive’s CacheProfile attribute.

Example 25.17. OutputCacheProfile.aspx

<%@ Page Language="C#" %>
<%@ OutputCache CacheProfile="Cache1Hour" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Output Cache Profile</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>

    </div>
    </form>
</body>
</html>

You can set the same caching properties in a Cache Profile as you can set in an individual page’s <%@ OutputCache %> directive. For example, you can set varyByParam, varyByControl, varyByHeader, and even varyByCustom attributes in a Cache Profile.

Using Partial Page Caching

In the previous section of this chapter, you learned how to cache the entire output of a page. In this section, you learn how to take advantage of Partial Page Caching to cache particular regions of a page.

Partial Page Caching makes sense when a page contains both dynamic and static content. For example, you might want to cache a set of database records displayed in a page, but not cache a random list of news items displayed in the same page.

In this section, you learn about two methods for enabling Partial Page Caching. You can use post-cache substitution to cache an entire page except for a particular region. You can use User Controls to cache particular regions in a page, but not the entire page.

Using Post-Cache Substitution

In some cases, you might want to cache an entire page except for one small area. For example, you might want to display the current username dynamically at the top of a page but cache the remainder of a page. In these cases, you can take advantage of a feature of the ASP.NET Framework called post-cache substitution.

Post-cache substitution is used internally by the AdRotator control. Even when you use Page Output Caching to cache a page that contains an AdRotator control, the content rendered by the AdRotator control is not cached.

You can use post-cache substitution either declaratively or programmatically. If you want to use post-cache substitution declaratively, then you can use the ASP.NET Substitution control. For example, the page in Listing 25.18 uses the Substitution control to display the current time on a page that has been output cached (see Figure 25.6).

Using the Substitution control.

Figure 25.6. Using the Substitution control.

Example 25.18. SubstitutionControl.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="15" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    public static string GetTime(HttpContext context)
    {
        return DateTime.Now.ToString("T");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Substitution Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    The cached time is: <%= DateTime.Now.ToString("T") %>
    <hr />
    The substitution time is:
    <asp:Substitution
        id="Substitution1"
        MethodName="GetTime"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.18, the time is displayed twice. The time displayed in the body of the page is output cached. The time displayed by the Substitution control is not cached.

The Substitution control has one important property: MethodName. The MethodName property accepts the name of a method defined in the page. The method must be a shared (static) method because an instance of the class is not created when the page is output cached.

Alternatively, you can use post-cache substitution programmatically by using the Response.WriteSubstitution() method. This method is illustrated in the page in Listing 25.19.

Example 25.19. ShowWriteSubstitution.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="15" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    public static string GetTime(HttpContext context)
    {
        return DateTime.Now.ToString("T");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show WriteSubstitution</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    The cached time is: <%= DateTime.Now.ToString("T") %>
    <hr />
    The substitution time is:
    <% Response.WriteSubstitution(GetTime); %>

    </div>
    </form>
</body>
</html>

There are two advantages to using the WriteSubstitution() method. First, the method referenced by the WriteSubstitution() method does not have to be a method of the current class. The method can be either an instance or shared method on any class.

The second advantage of the WriteSubstitution() method is that you can use it within a custom control to perform post-cache substitutions. For example, the NewsRotator control in Listing 25.20 uses the WriteSubstitution() method when displaying a random news item. If you use this control in a page that has been output cached, the NewsRotator control continues to display news items randomly.

Example 25.20. NewsRotator.cs

using System;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;
namespace myControls
{
    public class NewsRotator : WebControl
    {

        public static string GetNews(HttpContext context)
        {
            List<String> news = new List<string>();
            news.Add("Martians attack!");
            news.Add("Moon collides with earth!");
            news.Add("Life on Jupiter!");

            Random rnd = new Random();
            return news[rnd.Next(news.Count)];
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            Context.Response.WriteSubstitution(GetNews);
        }

    }
}

Note

Building custom controls is discussed in detail in Chapter 29, “Building Custom Controls.”

The CD that accompanies this book includes a page named ShowNewsRotator.aspx. If you open this page, all the content of the page is cached except for the random news item displayed by the NewsRotator control (see Figure 25.7).

Displaying dynamic news items in a cached page.

Figure 25.7. Displaying dynamic news items in a cached page.

When you use post-cache substitution (declaratively or programmatically) then caching no longer happens beyond the web server. Using post-cache substitution causes a Cache-Control:no-cache HTTP header to be included in the HTTP response, which disables caching on proxy servers and browsers. This limitation is understandable because the substitution content must be generated dynamically with each page request.

Caching with a User Control

Using post-cache substitution is appropriate only when working with a string of text or HTML. If you need to perform more complex partial page caching, then you should take advantage of User Controls.

You can cache the rendered contents of a User Control in memory in the same way as you can cache an ASP.NET page. When you add an <%@ OutputCache %> directive to a User Control, the rendered output of the User Control is cached.

Note

When you cache a User Control, the content is cached on the web server and not on any proxy servers or web browsers. When a web browser or proxy server caches a page, it always caches an entire page.

For example, the Movies User Control in Listing 25.21 displays all the rows from the Movies database table. Furthermore, it includes an OutputCache directive, which causes the contents of the User Control to be cached in memory for a maximum of 10 minutes (600 seconds).

Example 25.21. Movies.ascx

<%@ Control Language="C#" ClassName="Movies" %>
<%@ OutputCache Duration="600" VaryByParam="none" %>

User Control Time:
<%= DateTime.Now.ToString("T") %>
<asp:GridView
    id="grdMovies"
    DataSourceID="srcMovies"
    Runat="server" />

<asp:SqlDataSource
    id="srcMovies"
    ConnectionString="<%$ ConnectionStrings:Movies %>"
    SelectCommand="SELECT Title,Director FROM Movies"
    Runat="server" />

The User Control in Listing 25.21 displays the records from the Movies database table with a GridView control. It also displays the current time. Because the control includes an OutputCache directive, the entire rendered output of the control is cached in memory.

The page in Listing 25.22 includes the Movies User Control in the body of the page. It also displays the current time at the top of the page. When you refresh the page, the time displayed by the Movies control changes, but not the time displayed in the body of the page (see Figure 25.8).

Caching the output of a User Control.

Figure 25.8. Caching the output of a User Control.

Example 25.22. ShowUserControlCache.aspx

<%@ Page Language="C#" %>
<%@ Register TagPrefix="user" TagName="Movies" Src="~/Movies.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show User Control Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    Page Time:
    <%= DateTime.Now.ToString("T") %>
    <hr />

    <user:Movies
        id="Movies1"
        Runat="server" />

    </div>
    </form>
</body>
</html>

You can use the following attributes with an <%@ OutputCache %> directive declared in a User Control:

  • DurationThe amount of time in seconds that the rendered content of the User Control is cached.

  • SharedEnables you to share the same cached version of the User Control across multiple pages.

  • VaryByParamEnables you to create different cached versions of a User Control, depending on the values of one or more query string or form parameters. You can specify multiple parameters by supplying a semicolon-delimited list of query string or form parameter names.

  • VaryByControlEnables you to create different cached versions of a User Control, depending on the value of a control. You can specify multiple controls by supplying a semicolon-delimited list of control IDs.

  • VaryByCustomEnables you to specify a custom string used by a custom cache policy. (You also can supply the special value browser, which causes different cached versions of the control to be created when the type and major version of the browser differs.)

Because each User Control that you add to a page can have different caching policies, and because you can nest User Controls with different caching policies, you can build pages that have fiendishly complex caching policies. There is nothing wrong with doing this. In fact, you should take advantage of this caching functionality whenever possible to improve the performance of your applications.

Warning

Be careful when setting properties of a cached User Control. If you attempt to set the property of a User Control programmatically when the content of the control is served from the cache, you get a NullReference exception. Before setting a property of a cached control, first check whether the control actually exists like this:

if (myControl != null)
  myControl.SomeProperty = "some value";

Sharing a User Control Output Cache

By default, instances of the same User Control located on different pages do not share the same cache. For example, if you add the same Movies User Control to more than one page, then the contents of each user control is cached separately.

If you want to cache the same User Control content across multiple pages, then you need to include the Shared attribute when adding the <%@ OutputCache %> directive to a User Control. For example, the modified Movies User Control in Listing 25.23 includes the Shared attribute.

Example 25.23. SharedMovies.ascx

<%@ Control Language="C#" ClassName="SharedMovies"
%> <%@ OutputCache Duration="600" VaryByParam="none" Shared="true" %>

User Control Time:
<%= DateTime.Now.ToString() %>

<asp:GridView
    id="grdMovies"
    DataSourceID="srcMovies"
    Runat="server" />

<asp:SqlDataSource
    id="srcMovies"
    ConnectionString="<%$ ConnectionStrings:Movies %>"
    SelectCommand="SELECT Title,Director FROM Movies"
    Runat="server" />

Using the Shared attribute is almost always a good idea. You can save a significant amount of server memory by taking advantage of this attribute.

Manipulating a User Control Cache Programmatically

When you include an <%@ OutputCache %> directive in a User Control, you can modify programmatically how the User Control is cached. The User Control CachePolicy property exposes an instance of the ControlCachePolicy class, which supports the following properties:

  • CachedEnables you to enable or disable caching.

  • DependencyEnables you to get or set a cache dependency for the User Control.

  • DurationEnables you to get or set the amount of time (in seconds) that content is cached.

  • SupportsCachingEnables you to check whether the control supports caching.

  • VaryByControlEnables you to create different cached versions of the control, depending on the value of a control.

  • VaryByParamsEnables you to create different cached versions of the control, depending on the value of a query string or form parameter.

The ControlCachePolicy class also supports the following methods:

  • SetExpiresEnables you to set the expiration time for the cache.

  • SetSlidingExpirationEnables you to set a sliding expiration cache policy.

  • SetVaryByCustomEnables you to specify a custom string used by a custom cache policy. (You also can supply the special value browser, which causes different cached versions of the control to be created when the type and major version of the browser differs.)

For example, the User Control in Listing 25.24 uses a sliding expiration policy of one minute. When you specify a sliding expiration policy, a User Control is cached just as long as you continue to request the User Control within the specified interval of time.

Example 25.24. SlidingUserCache.ascx

<%@ Control Language="C#" ClassName="SlidingUserCache" %>
<%@ OutputCache Duration="10" VaryByParam="none" %>
<script runat="server">

    void Page_Load()
    {
       CachePolicy.SetSlidingExpiration(true);
       CachePolicy.Duration = TimeSpan.FromMinutes(1);
    }

</script>

User Control Time:
<%= DateTime.Now.ToString("T") %>

The CD that accompanies this book includes a page named ShowSlidingUserCache.aspx, which contains the SlidingUserCache control. If you keep requesting this page and do not let more than 1 minute pass between requests, then the User Control isn’t dropped from the cache.

Creating a User Control Cache File Dependency

You can use the CacheControlPolicy.Dependency property to create a dependency between a cached User Control and a file (or set of files) on the file system. When the file is modified, the User Control is dropped from the cache automatically and reloaded with the next page request.

For example, the User Control in Listing 25.25 displays all the movies from the Movies.xml file in a GridView control. Notice that the User Control includes a Page_Load() handler that creates a dependency on the Movies.xml file.

Example 25.25. MovieFileDependency.ascx

<%@ Control Language="C#" ClassName="MovieFileDependency"
%> <%@ OutputCache Duration="9999" VaryByParam="none" %>
<script runat="server">

    void Page_Load()
    {
        CacheDependency depend = new CacheDependency(MapPath("~/Movies.xml"));
        this.CachePolicy.Dependency = depend;
    }
</script>
User Control Time:
<%= DateTime.Now.ToString("T") %>
<hr />

<asp:GridView
    id="grdMovies"
    DataSourceID="srcMovies"
    Runat="server" />
<asp:XmlDataSource
    id="srcMovies"
    DataFile="Movies.xml"
    Runat="server" />

The CD that accompanies this book includes a page named ShowMovieFileDependency, which displays the MovieFileDependency User Control (see Figure 25.9). If you open the page, then the User Control is automatically cached until you modify the Movies.xml file.

Displaying a User Control with a file dependency.

Figure 25.9. Displaying a User Control with a file dependency.

Caching Dynamically Loaded User Controls

You can load a User Control dynamically by using the Page.LoadControl() method. You can cache dynamically loaded User Controls in the same way that you can cache User Controls declared in a page. If a User Control includes an <%@ OutputCache %> directive, then the User Control will be cached regardless of whether the control was added to a page declaratively or programmatically.

However, you need to be aware that when a cached User Control is loaded dynamically, the ASP.NET Framework automatically wraps the User Control in an instance of the PartialCachingControl class. Therefore, you need to cast the control returned by the Page.LoadControl() method to an instance of the PartialCachingControl class.

For example, the page in Listing 25.26 dynamically adds the Movies User Control in its Page_Load() event handler. The Page_Load() method overrides the default cache duration specified in the User Control’s <%@ OutputCache %> directive. The cache duration is changed to 15 seconds (see Figure 25.10).

Programmatically caching a User Control.

Figure 25.10. Programmatically caching a User Control.

Example 25.26. ShowDynamicUserControl.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    void Page_Load()
    {
        // Load the control
        PartialCachingControl cacheMe = (PartialCachingControl)Page.LoadControl("Movies.ascx");

        // Change cache duration to 15 seconds
        cacheMe.CachePolicy.SetExpires(DateTime.Now.AddSeconds(15));

        // Add control to page
        PlaceHolder1.Controls.Add(cacheMe);
        // Display control cache duration
        lblCacheDuration.Text = cacheMe.CachePolicy.Duration.ToString();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Dynamic User Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    Cache Duration:
    <asp:Label
        id="lblCacheDuration"
        Runat="server" />
    <hr />

    <asp:PlaceHolder
        id="PlaceHolder1"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.26, the default cache duration is modified by modifying the PartialCachingControl’s CachePolicy property. This property returns an instance of the same ControlCachePolicy class described in the two previous sections of this chapter.

You can refer to the User Control contained with an instance of the PartialCachingControl class by using the class’s CachedControl property. Normally, this property returns the value Nothing (null) because when the User Control is cached, it is never actually created.

Using DataSource Caching

Instead of caching at the page or User Control level, you can cache at the level of a DataSource control. Three of the four standard ASP.NET DataSource controls—the SqlDataSource, ObjectDataSource, and XmlDataSource controls—include properties that enable you to cache the data that the DataSource control represents (The LinqDataSource control does not support caching).

One advantage of using the DataSource controls when caching is that the DataSource controls can reload data automatically when the data is updated. For example, if you use a SqlDataSource control to both select and update a set of database records, then the SqlDataSource control is smart enough to reload the cached data after an update.

The DataSource controls are also smart enough to share the same data across multiple pages. For example, when using the SqlDataSource control, a unique entry is created in the Cache object for each combination of the following SqlDataSource properties: SelectCommand, SelectParameters, and ConnectionString. If these properties are identical for two SqlDataSource controls located on two different pages, then the two controls share the same cached data.

Note

DataSource caching does not work with LINQ to SQL queries. To learn about caching LINQ to SQL queries, see Chapter 18.

In this section, you learn how to use the SqlDataSource, ObjectDataSource, and XmlDataSource controls to cache data. You learn how to set either an absolute or sliding expiration policy. Finally, you learn how to create a cache key dependency that you can use to expire the cache programmatically.

Using an Absolute Cache Expiration Policy

When you use an absolute cache expiration policy, the data that a DataSource represents is cached in memory for a particular duration of time. Using an absolute cache expiration policy is useful when you know that your data does not change that often. For example, if you know that the records contained in a database table are modified only once a day, then there is no reason to keep grabbing the same records every time someone requests a web page.

Warning

When caching with the SqlDataSource control, the SqlDataSource control’s DataSourceMode property must be set to the value DataSet (the default value) rather than DataReader.

The page in Listing 25.27 displays a list of movies that are cached in memory. The page uses a SqlDataSource control to cache the data.

Example 25.27. DataSourceAbsoluteCache.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>DataSource Absolute Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        EnableCaching="True"
        CacheDuration="3600"
        SelectCommand="SELECT * FROM Movies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.27, two properties of the SqlDataSource control related to caching are set. First, the EnableCaching property is set to the value True. Next, the CacheDuration property is set to the value 3,600 seconds (1 hour). The movies are cached in memory for a maximum of 1 hour. If you don’t supply a value for the CacheDuration property, the default value is Infinite.

It is important to understand that there is no guarantee that the SqlDataSource control will cache data for the amount of time specified by its CacheDuration property. Behind the scenes, DataSource controls use the Cache object for caching. This object supports scavenging. When memory resources become low, the Cache object automatically removes items from the cache.

You can test whether the page in Listing 25.27 is working by opening the page and temporarily turning off your database server. You can turn off SQL Server Express by opening the SQL Configuration Manager located in the Microsoft SQL Server 2005 program group and stopping the SQL Server service (see Figure 25.11). If you refresh the page, the data is displayed even though the database server is unavailable.

The SQL Configuration Manager.

Figure 25.11. The SQL Configuration Manager.

Using a Sliding Cache Expiration Policy

If you need to cache a lot of data, then it makes more sense to use a sliding expiration policy rather than an absolute expiration policy. When you use a sliding expiration policy, data remains in the cache as long as the data continues to be requested within a certain interval.

For example, imagine that you have been asked to rewrite the Amazon website with ASP.NET. The Amazon website displays information on billions of books. You couldn’t cache all this book information in memory. However, if you use a sliding expiration policy, then you can cache the most frequently requested books automatically.

The page in Listing 25.28 illustrates how you can enable a sliding cache expiration policy. The cache duration is set to 15 seconds. As long as no more than 15 seconds pass before you request the page, the movies are kept cached in memory.

Example 25.28. DataSourceSlidingCache.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">
    protected void srcMovies_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
    {
        lblMessage.Text = "Selecting data from database";
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>DataSource Sliding Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <p>
    <asp:Label
        id="lblMessage"
        EnableViewState="false"
        Runat="server" />
    </p>

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        EnableCaching="True"
        CacheExpirationPolicy="Sliding"
        CacheDuration="15"
        SelectCommand="SELECT * FROM Movies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        OnSelecting="srcMovies_Selecting"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Notice that the page in Listing 25.28 includes a srcMovies_Selecting() event handler. This handler is called only when the movies are retrieved from the database rather than from memory. In other words, you can use this event handler to detect when the movies are dropped from the cache (see Figure 25.12).

Using a sliding expiration policy with a DataSource control.

Figure 25.12. Using a sliding expiration policy with a DataSource control.

Caching with the ObjectDataSource Control

The ObjectDataSource control supports the same caching properties as the SqlDataSource control. You can cache the data that an ObjectDataSource control represents by setting its EnableCaching, CacheDuration, and (optionally) CacheExpirationPolicy properties.

Note

Multiple ObjectDataSource controls can share the same cached data. To share the same cache, the ObjectDataSource controls must have identical TypeName, SelectMethod, and SelectParameters properties.

For example, the page in Listing 25.29 uses an ObjectDataSource control to represent the Movies database table. The ObjectDataSource is bound to a component named Movie that includes a method named GetMovies() that returns all of the records from the Movies database table.

Example 25.29. ShowObjectDataSourceCaching.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void srcMovies_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
    {
        lblMessage.Text = "Selecting data from component";
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show ObjectDataSource Caching</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        EnableViewState="false"
        Runat="server" />
    <br /><br />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:ObjectDataSource
        id="srcMovies"
        EnableCaching="true"
        CacheDuration="15"
        TypeName="Movie"
        SelectMethod="GetMovies"
        OnSelecting="srcMovies_Selecting"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The ObjectDataSource control in Listing 25.29 includes an event handler for its Selecting event. The event handler displays a message in a Label control. Because the Selecting event is not raised when data is retrieved from the cache, you can use this method to determine when data is retrieved from the cache or the Movie component.

The Movie component is contained in Listing 25.30.

Example 25.30. Movie.cs

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

public class Movie
{

    public static DataTable GetMovies()
    {
        string conString = WebConfigurationManager.ConnectionStrings["Movies"]. ConnectionString;
        SqlDataAdapter dad = new SqlDataAdapter("SELECT Title,Director FROM Movies", conString);
        DataTable movies = new DataTable();
        dad.Fill(movies);
        return movies;
    }
}

Notice that the GetMovies() method returns a DataTable. When using the ObjectDataSource control, you can cache certain types of data but not others. For example, you can cache data represented with a DataSet, DataTable, DataView, or collection. However, you cannot cache data represented by a DataReader. If you attempt to bind to a method that returns a DataReader, then an exception is thrown.

Caching with the XmlDataSource Control

Unlike the SqlDataSource and ObjectDataSource controls, the XmlDataSource control has caching enabled by default. The XmlDataSource automatically creates a file dependency on the XML file that it represents. If the XML file is modified, the XmlDataSource control automatically reloads the modified XML file.

For example, the page in Listing 25.31 contains an XmlDataSource control that represents the Movies.xml file. If you modify the Movies.xml file, then the contents of the files are automatically reloaded.

Example 25.31. ShowXmlDataSourceCaching.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show XmlDataSource Caching</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:XmlDataSource
        id="srcMovies"
        DataFile="Movies.xml"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Creating a DataSource Control Key Dependency

Imagine that your web application has multiple pages that display different sets of records from the Movies database table. However, you have one page that enables a user to enter a new movie. In that case, you need some method of signaling to all your DataSource controls that the Movies database table has changed.

You can create a key dependency between the DataSource controls in your application and an item in the cache. That way, if you remove the item from the cache, all the DataSource controls will reload their data.

The page in Listing 25.32 contains a SqlDataSource control that displays the contents of the Movies database table. The SqlDataSource caches its data for an infinite duration.

Example 25.32. DataSourceKeyDependency.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    protected void srcMovies_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
    {
        lblMessage.Text = "Selecting data from database";
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>DataSource Key Dependency</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <p>
    <asp:Label
        id="lblMessage"
        EnableViewState="false"
        Runat="server" />
    </p>

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        EnableCaching="True"
        CacheDuration="Infinite"
        CacheKeyDependency="MovieKey"
        SelectCommand="SELECT * FROM Movies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        OnSelecting="srcMovies_Selecting"
        Runat="server" />

    <br /><br />
    <a href="AddMovieDataSourceKeyDependency.aspx">Add Movie</a>

    </div>
    </form>
</body>
</html>

Notice that the SqlDataSource control in Listing 25.32 includes a CacheKeyDependency property that has the value MovieKey. This property creates a dependency between the DataSource control’s cached data and an item in the cache named MovieKey.

The Global.asax file in Listing 25.33 creates the initial MovieKey cache item. The value of the cache item doesn’t really matter. In Listing 25.33, the MovieKey cache item is set to the current date and time.

Example 25.33. Global.asax

<%@ Application Language="C#" %>
<script runat="server">

    void Application_Start(object sender, EventArgs e)
    {
        HttpContext context = HttpContext.Current;
        context.Cache.Insert(
            "MovieKey",
            DateTime.Now,
            null,
            DateTime.MaxValue,
            Cache.NoSlidingExpiration,
            CacheItemPriority.NotRemovable,
            null);
    }
</script>

The page in Listing 25.34 contains a DetailsView control that enables you to insert a new record. Notice that the DetailsView control’s ItemInserted event is handled. When you insert a new record, the MovieKey item is reinserted into the cache and every DataSource control that is dependent on this key is reloaded automatically.

Example 25.34. AddMovieDataSourceKeyDependency.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void dtlMovie_ItemInserted(object sender, DetailsViewInsertedEventArgs e)
    {
        Cache.Insert("MovieKey", DateTime.Now);
        Response.Redirect("~/DataSourceKeyDependency.aspx");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Add Movie Key Dependency</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>Add Movie</h1>

    <asp:DetailsView
        id="dtlMovie"
        DefaultMode="Insert"
        DataSourceID="srcMovies"
        AutoGenerateRows="false"
        AutoGenerateInsertButton="true"
        OnItemInserted="dtlMovie_ItemInserted"
        Runat="server">
        <Fields>
        <asp:BoundField
            DataField="Title"
            HeaderText="Title:" />
        <asp:BoundField
            DataField="Director"
            HeaderText="Director:" />
        </Fields>
    </asp:DetailsView>

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        InsertCommand="INSERT Movies (Title, Director)
            VALUES (@Title, @Director)"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Using Data Caching

Behind the scenes, all the various caching mechanisms included in the ASP.NET Framework use the Cache object. In other words, the Cache object is the fundamental mechanism for all caching in the ASP.NET Framework.

One instance of the Cache object is created for each ASP.NET application. Any items you add to the cache can be accessed by any other page, control, or component contained in the same application (virtual directory).

In this section, you learn how to use the properties and methods of the Cache object. You learn how to add items to the cache, set cache expiration policies, and create cache item dependencies.

Using the Cache Application Programming Interface

The Cache object exposes the main application programming interface for caching. This object supports the following properties:

  • CountRepresents the number of items in the cache.

  • EffectivePrivateBytesLimitRepresents the size of the cache in kilobytes.

The Cache object also supports the following methods:

  • AddEnables you to add a new item to the cache. If the item already exists, this method fails.

  • GetEnables you to return a particular item from the cache.

  • GetEnumeratorEnables you to iterate through all the items in the cache.

  • InsertEnables you to insert a new item into the cache. If the item already exists, this method replaces it.

  • RemoveEnables you to remove an item from the cache.

For example, the page in Listing 25.35 displays all the items currently contained in the cache (see Figure 25.13).

Example 25.35. EnumerateCache.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    public class CacheItem
    {
        private string _key;
        private object _value;
        public string Key
        {
            get { return _key; }
        }

        public string Value
        {
            get { return _value.ToString(); }
        }

        public CacheItem(string key, object value)
        {
            _key = key;
            _value = value;
        }
    }

    void Page_Load()
    {
        ArrayList items = new ArrayList();
        foreach (DictionaryEntry item in Cache)
            items.Add(new CacheItem(item.Key.ToString(),item.Value));

        grdCache.DataSource = items;
        grdCache.DataBind();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .grid td, .grid th
        {
            padding:5px;
        }
    </style>
    <title>Enumerate Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdCache"
        CssClass="grid"
        Runat="server" />

    </div>
    </form>
</body>
</html>
Displaying the cache’s contents.

Figure 25.13. Displaying the cache’s contents.

The page in Listing 25.35 displays only items that have been added to the cache by the methods of the Cache object. For example, it does not display a list of pages that have been output cached. Output cached pages are stored in the internal cache (the secret cache maintained by the ASP.NET Framework).

Adding Items to the Cache

You can add items to the cache by using the Insert() method. There are several overloaded versions of the Insert() method. The maximally overloaded version of the Insert() method accepts the following parameters:

  • keyEnables you to specify the name of the new item.

  • valueEnables you to specify the value of the new item.

  • dependenciesEnables you to specify one or more cache dependencies, such as a file, key, or SQL dependency.

  • absoluteExpirationEnables you to specify an absolute expiration time for the cached item. If you don’t need to specify a value for this property, use the static field Cache.NoAbsoluteExpiration.

  • slidingExpirationEnables you to specify a sliding expiration interval for the cached item. If you don’t need to specify a value for this property, use the static field Cache.NoSlidingExpiration.

  • priorityEnables you to specify the priority of the cached item. Possible values are AboveNormal, BelowNormal, Default, High, Low, Normal, and NotRemovable.

  • onRemoveCallbackEnables you to specify a method that is called automatically before the item is removed from the cache.

When using the cache, it is important to understand that items that you add to the cache might not be there when you attempt to retrieve the item in the future. The cache supports scavenging. When memory resources become low, items are automatically evicted from the cache.

Before using any item that you retrieve from the cache, you should always check whether the item is Nothing (null). If an item has been removed, then you’ll retrieve Nothing when you attempt to retrieve it from the cache in the future.

You can add almost any object to the cache. For example, you can add custom components, DataSets, DataTables, ArrayLists, and Lists to the cache.

You shouldn’t add items to the cache that depend on an external resource. For example, it does not make sense to add a SqlDataReader or a FileStream to the cache. When using a SqlDataReader, you need to copy the contents of the SqlDataReader into a static representation such as an ArrayList or List collection.

Adding Items with an Absolute Expiration Policy

When you insert items in the cache, you can specify a time when the item will expire. If you want an item to remain in the cache for an extended period of time, then you should always specify an expiration time for the item.

The page in Listing 25.36 illustrates how you can add an item to the cache with an absolute expiration policy. The item is added to the cache for 1 hour.

Example 25.36. ShowAbsoluteExpiration.aspx

<%@ Page Language="C#" Trace="true" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    void Page_Load()
    {
        // Get movies from Cache
        DataTable movies = (DataTable)Cache["Movies"];

        // If movies not in cache, recreate movies
        if (movies == null)
        {
            movies = GetMoviesFromDB();
            Cache.Insert("Movies", movies, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
        }

        grdMovies.DataSource = movies;
        grdMovies.DataBind();
    }

    private DataTable GetMoviesFromDB()
    {
        Trace.Warn("Getting movies from database");
        string conString = WebConfigurationManager.ConnectionStrings ["Movies"].ConnectionString;
        SqlDataAdapter dad = new SqlDataAdapter("SELECT Title,Director FROM Movies", conString);
        DataTable movies = new DataTable();
        dad.Fill(movies);
        return movies;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Absolute Expiration</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The first time the page in Listing 25.36 is requested, nothing is retrieved from the cache. In that case, a new DataTable is created that represents the Movies database table. The DataTable is inserted into the cache. The next time the page is requested, the DataTable can be retrieved from the cache and there is no need to access the database.

The DataTable will remain in the cache for 1 hour or until memory pressures force the DataTable to be evicted from the cache. In either case, the logic of the page dictates that the DataTable will be added back to the cache when the page is next requested.

Tracing is enabled for the page in Listing 25.36 so that you can see when the Movies database table is loaded from the cache and when the table is loaded from the database. The GetMoviesFromDB() method writes a Trace message whenever it executes (see Figure 25.14).

Adding an item to the cache with an absolute expiration policy.

Figure 25.14. Adding an item to the cache with an absolute expiration policy.

Adding Items with a Sliding Expiration Policy

When you specify a sliding expiration policy, items remain in the cache just as long as they continue to be requested within a specified interval of time. For example, if you specify a sliding expiration policy of 5 minutes, then the item remains in the Cache just as long as no more than 5 minutes pass without the item being requested.

Using a sliding expiration policy makes sense when you have too many items to add to the cache. A sliding expiration policy keeps the most requested items in memory and the remaining items are dropped from memory automatically.

The page in Listing 25.37 illustrates how you can add a DataSet to the cache with a sliding expiration policy of 5 minutes.

Example 25.37. ShowSlidingExpiration.aspx

<%@ Page Language="C#" Trace="true" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        // Get movies from Cache
        DataSet movies = (DataSet)Cache["Movies"];

        // If movies not in cache, recreate movies
        if (movies == null)
        {
            movies = GetMoviesFromDB();
            Cache.Insert("Movies", movies, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5));
        }

        grdMovies.DataSource = movies;
        grdMovies.DataBind();
    }

    private DataSet GetMoviesFromDB()
    {
        Trace.Warn("Getting movies from database");
        string conString = WebConfigurationManager.ConnectionStrings ["Movies"].ConnectionString;
        SqlDataAdapter dad = new SqlDataAdapter("SELECT Title,Director FROM Movies", conString);
        DataSet movies = new DataSet();
        dad.Fill(movies);
        return movies;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Sliding Expiration</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.37, when the DataSet is added to the cache with the Insert() method, its absoluteExpiration parameter is set to the value Cache.NoAbsoluteExpiration, and its slidingExpiration parameter is set to an interval of 5 minutes.

Adding Items with Dependencies

When you add an item to the Cache object, you can make the item dependent on an external object. If the external object is modified, then the item is automatically dropped from the cache.

The ASP.NET Framework includes three cache dependency classes:

  • CacheDependencyEnables you to create a dependency on a file or other cache key.

  • SqlCacheDependencyEnables you to create a dependency on a Microsoft SQL Server database table or the result of a SQL Server 2005 query.

  • AggregateCacheDependencyEnables you to create a dependency using multiple CacheDependency objects. For example, you can combine file and SQL dependencies with this object.

The CacheDependency class is the base class. The other two classes derive from this class. The CacheDependency class supports the following properties:

  • HasChangedEnables you to detect when the dependency object has changed.

  • UtcLastModifiedEnables you to retrieve the time when the dependency object last changed.

The CacheDependency object also supports the following method:

  • GetUniqueIDEnables you to retrieve a unique identifier for the dependency object.

Note

You can create a custom cache dependency class by deriving a new class from the base CacheDependency class.

The SqlCacheDependency class is discussed in detail in the final section of this chapter. In this section, I want to show you how you can use the base CacheDependency class to create a file dependency on an XML file.

The page in Listing 25.38 creates a dependency on an XML file named Movies.xml. If you modify the Movies.xml file, the cache is reloaded with the modified file automatically.

Example 25.38. ShowFileDependency.aspx

<%@ Page Language="C#" Trace="true" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        DataSet movies = (DataSet)Cache["Movies"];
        if (movies == null)
        {
            Trace.Warn("Retrieving movies from file system");
            movies = new DataSet();
            movies.ReadXml(MapPath("~/Movies.xml"));
            CacheDependency fileDepend = new CacheDependency(MapPath("~/Movies.xml"));
            Cache.Insert("Movies", movies, fileDepend);
        }
        grdMovies.DataSource = movies;
        grdMovies.DataBind();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show File Dependency</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Specifying Cache Item Priorities

When you add an item to the Cache, you can specify a particular priority for the item. Specifying a priority provides you with some control over when an item gets evicted from the Cache. For example, you can indicate that one cached item is more important than other cache items so that when memory resources become low, the important item is not evicted as quickly as other items.

You can specify any of the following values of the CacheItemPriority enumeration to indicate the priority of a cached item:

  • AboveNormal

  • BelowNormal

  • Default

  • High

  • Low

  • Normal

  • NotRemovable

For example, the following line of code adds an item to the cache with a maximum absolute expiration time and a cache item priority of NotRemovable:

Cache.Insert("ImportantItem", DateTime.Now, null, DateTime.MaxValue,
Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);

Configuring the Cache

You can configure the size of the cache by using the web configuration file. You specify cache settings with the cache element. This element supports the following attributes:

  • disableMemoryCollectionEnables you to prevent items from being removed from the cache when memory resources become low.

  • disableExpirationEnables you to prevent items from being removed from the cache when the items expire.

  • privateBytesLimitEnables you to specify the total amount of memory that can be consumed by your application and its cache before items are removed.

  • percentagePhysicalMemoryUsedLimitEnables you to specify the total percentage of memory that can be consumed by your application and its cache before items are removed.

  • privateBytesPollTimeEnables you to specify the time interval for checking the application’s memory usage.

Notice that you can’t set the size of the cache directly. However, you can specify limits on the overall memory that your application consumes, which indirectly limits the size of the cache.

By default, both the privateBytesLimit and percentPhysicalMemoryUsedLimit attributes have the value 0, which indicates that the ASP.NET Framework should determine the correct values for these attributes automatically.

The web configuration file in Listing 25.39 changes the memory limit of your application to 100,000 kilobytes and disables the expiration of items in the cache.

Example 25.39. Web.Config

<configuration>
    <system.web>
      <caching>
        <cache privateBytesLimit="100000" disableExpiration="true"/>
      </caching>
    </system.web>
</configuration>

The page in Listing 25.40 displays your application’s current private bytes limit (see Figure 25.15):

Example 25.40. ShowPrivateBytesLimit.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        lblPrivateBytes.Text = Cache.EffectivePrivateBytesLimit.ToString("n0");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Private Bytes Limit</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    Effective Private Bytes Limit:
    <asp:Label
        id="lblPrivateBytes"
        Runat="server" />

    </div>
    </form>
</body>
</html>
Displaying the maximum application and cache size.

Figure 25.15. Displaying the maximum application and cache size.

Using SQL Cache Dependencies

One of the most powerful features supported by the ASP.NET Framework is SQL cache dependencies. This feature enables you to reload cached database data automatically whenever the data in the underlying databases changes.

There is a tradeoff when you use either an absolute or sliding cache expiration policy. The tradeoff is between performance and stale data. For example, if you cache data in memory for 20 seconds, then the data that is displayed on your web pages might be 20 seconds out of date.

In the case of most applications, displaying slightly stale data does not really matter. For example, if you are building a discussion forum, then everyone can live with the fact that new posts might not appear immediately.

However, there are certain types of applications in which you cannot afford to display any stale data at all. For example, if you are creating a stock trading website or an auction website, then every second might count.

The ASP.NET Framework’s support for SQL cache dependencies enables you to take advantage of caching but minimize stale data. When you use a SQL cache dependency, you can automatically detect when data has changed in the underlying database and refresh the data in the cache.

The ASP.NET Framework supports two types of SQL cache dependencies: Polling and Push. You can use Polling SQL cache dependencies with any recent version of Microsoft SQL Server, including Microsoft SQL Server 2005 Express, Microsoft SQL Server 2000, and Microsoft SQL Server 7.0. The second type of cache dependency, Push SQL cache dependency, works with only Microsoft SQL Server 2005 or Microsoft SQL Server 2005 Express because it requires the SQL Server 2005 Service Broker.

You can use either type of SQL cache dependencies with Page Output Caching, DataSource Control Caching, and Data Caching. The following sections examine each scenario.

Using Polling SQL Cache Dependencies

A Polling SQL cache dependency is the most flexible type of SQL cache dependency, and I recommend that you use Polling rather than Push SQL cache dependencies for most applications. You can use a Polling SQL cache dependency to detect any type of modification to a database table.

Behind the scenes, a Polling SQL cache dependency uses a database trigger. When a table is modified, the trigger fires and a row in a database table named AspNet_SqlCacheTablesForChangeNotification is updated to record the fact that the table has been changed.

The ASP.NET Framework uses a background thread to poll this database table for changes on a periodic basis. If there has been a change, then any item in the cache that is dependent on the database table is dropped from the cache.

If you use a Polling SQL cache dependency, then you can eliminate the majority of your database traffic. Unless a database table changes, the only traffic between your web server and the database server is the query that checks for changes in the AspNet_SqlCacheTablesForChangeNotification table.

Because a Polling SQL cache dependency must poll the database for changes, an item cached with a SQL Polling cache dependency won’t be dropped from the cache immediately after there is a change in the database. The polling interval determines the staleness of your cached data. You can configure the polling interval to be any value you need.

Configuring Polling SQL Cache Dependencies

Before you can use a Polling SQL cache dependency, you must perform two configuration steps:

  1. Enable SQL cache dependencies for a database and one or more database tables.

  2. Configure SQL cache dependencies in your web configuration file.

Let’s examine each of these steps.

Configuring a Database for Polling SQL Cache Dependencies

You can configure a SQL Server database to support Polling SQL cache dependencies by using a class in the framework named the SqlCacheDependencyAdmin class. This class has the following methods:

  • DisableNotificationsEnables you to disable a database for Polling SQL cache dependencies; removes all tables and stored procedures used by Polling SQL cache dependencies.

  • DisableTableForNotificationEnables you to disable a particular database table for Polling SQL cache dependencies.

  • EnableNotificationsEnables a database for Polling SQL cache dependencies by adding all the necessary database objects.

  • EnableTableForNotificationsEnables a particular database table for Polling SQL cache dependencies.

  • GetTablesEnabledForNotificationsEnables you to retrieve all tables enabled for Polling SQL cache dependencies.

You should not use the SqlCacheDependencyAdmin class in an ASP.NET page because calling the methods of this class requires database permissions to create tables, stored procedures, and triggers. For security reasons, the ASP.NET process should not be given these permissions. Instead, you should use the SqlCacheDependencyAdmin class in a command-line tool.

The ASP.NET Framework includes a command-line tool named aspnet_regsql that enables you to configure a database to support Polling SQL cache dependencies. This tool works with Microsoft SQL Server 7.0, Microsoft SQL Server 2000, and Microsoft SQL Server 2005. Unfortunately, the aspnet_regsql command-line tool does not work with a local instance of Microsoft SQL Server 2005. (But we’ll fix this limitation in a moment.)

The aspnet_regsql tool is located in the following folder:

c:WindowsMicrosoft.NETFrameworkv2.0.50727

Note

If you open the SDK Command Prompt from the Microsoft .NET Framework SDK Program group, then you do not need to navigate to the Microsoft.NET folder to execute the aspnet_regsql command-line tool.

Executing the following command enables the Pubs database for SQL cache dependencies:

aspnet_regsql -C "Data Source=localhost;Integrated Security=True;Initial
Catalog=Pubs" -ed

This command creates the AspNet_SqlCacheTablesForChangeNotification database table and adds a set of stored procedures to the database specified in the connection string.

After you enable a database, you can enable a particular table for SQL cache dependencies with the following command:

aspnet_regsql -C "Data Source=localhost;Integrated Security=True;Initial
Catalog=Pubs" -et -t Titles

This command enables the Titles database table for SQL cache dependencies. It creates a new trigger for the Titles database table and adds a new entry in the AspNet_SqlCacheTablesForChangeNotification table.

Unfortunately, you cannot use the standard aspnet_regsql tool to enable a local SQL Server 2005 Express database for Polling SQL cache dependencies. The aspnet_regsql tool does not allow you to use the AttachDBFileName parameter in the connection string.

To get around this limitation, I’ve written a custom command-line tool named enableNotifications that works with a local SQL Express database. This tool is included on the CD that accompanies this book.

To use the enableNotifications tool, you need to open a command prompt and navigate to the folder that contains your local SQL Express database table. Next, execute the command with the name of the database file and the name of the database table that you want to enable for Polling SQL cache dependencies. For example, the following command enables the Movies database table located in the MyDatabase.mdf database:

enableNotifications "MyDatabase.mdf" "Movies"

The enableNotifications tool works only with a local instance of Microsoft SQL Server Express 2005. You cannot use the tool with other versions of Microsoft SQL Server.

Warning

When using the enableNotifications tool, you must navigate to the same folder as the database that you want to enable for Polling SQL cache dependencies.

Configuring an Application for Polling SQL Cache Dependencies

After you set up a database to support Polling SQL cache dependencies, you must configure your application to poll the database. You configure Polling SQL cache dependencies with the sqlCacheDependency subelement of the caching element in the web configuration file.

For example, the file in Listing 25.41 causes your application to poll the AspNet_SqlCacheTablesForChangeNotification table every 5 seconds (5000 milliseconds) for changes.

Example 25.41. Web.Config

<configuration>

  <connectionStrings>
    <add name="Movies" connectionString="Data Source=.SQLEXPRESS;
      AttachDbFilename=|DataDirectory|MyDatabase.mdf;Integrated Security=True;
User Instance=True" />
  </connectionStrings>

  <system.web>
    <caching>
      <sqlCacheDependency enabled="true" pollTime="5000">
        <databases>
          <add
            name="MyDatabase"
            connectionStringName="Movies" />
        </databases>
      </sqlCacheDependency>
    </caching>
  </system.web>
</configuration>

Using Polling SQL Cache Dependencies with Page Output Caching

After you configure Polling SQL cache dependencies, you can use a SQL dependency with Page Output Caching. For example, the page in Listing 25.42 is output cached until you modify the Movies database table.

Example 25.42. PollingSQLOutputCache.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="9999" VaryByParam="none"
  SqlDependency="MyDatabase:Movies" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Polling SQL Output Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Title, Director FROM Movies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 25.42 includes an <%@ OutputCache %> directive with a SqlDependency attribute. The value of the SqlDependency attribute is the name of the database enabled for SQL dependencies in the web configuration file, followed by the name of a database table.

If you open the page in Listing 25.42 in your browser and click your browser’s Reload button multiple times, then you’ll notice that the time displayed does not change. The page is output cached (see Figure 25.16).

Using Page Output Caching with a Polling SQL cache dependency.

Figure 25.16. Using Page Output Caching with a Polling SQL cache dependency.

However, if you modify the Movies database, then the page is dropped from the cache automatically (within 5 seconds). The next time you click the Reload button, the modified data is displayed.

If you want to make a page dependent on multiple database tables, then you can assign a semicolon-delimited list of database and table names to the SqlDependency attribute.

Note

You also can use Polling SQL cache dependencies with an <%@ OutputCache %> directive included in a User Control. In other words, you can use Polling SQL cache dependencies with Partial Page Caching.

Using Polling SQL Cache Dependencies with DataSource Caching

You can use Polling SQL cache dependencies with both the SqlDataSource and ObjectDataSource controls by setting the SqlCacheDependency property. For example, the page in Listing 25.43 caches the output of a SqlDataSource control until the Movies database table is modified.

Example 25.43. PollingSQLDataSourceCache.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void srcMovies_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
    {
        lblMessage.Text = "Retrieving data from database";
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Polling SQL DataSource Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        EnableViewState="false"
        Runat="server" />
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:Movies %>"
        SelectCommand="SELECT Title, Director FROM Movies"
        EnableCaching="true"
        SqlCacheDependency="MyDatabase:Movies"
        OnSelecting="srcMovies_Selecting"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.43, the SqlDataSource control includes both an EnableCaching property and a SqlCacheDependency property. A database name and table name are assigned to the SqlCacheDependency property. (The database name must correspond to the database name configured in the <sqlCacheDependency> section of the web configuration file.)

If you need to monitor multiple database tables, you can assign a semicolon-delimited list of database and table names to the SqlCacheDependency property.

Using Polling SQL Cache Dependencies with Data Caching

You also can use Polling SQL cache dependencies when working with the Cache object. You represent a Polling SQL cache dependency with the SqlCacheDependency object.

For example, the page in Listing 25.44 creates a SqlCacheDependency object that represents the Movies database table. When a DataTable is added to the Cache object, the DataTable is added with the SqlCacheDependency object.

Example 25.44. PollingSQLDataCache.aspx

<%@ Page Language="C#" Trace="true" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        DataTable movies = (DataTable)Cache["Movies"];
        if (movies == null)
        {
            movies = GetMoviesFromDB();
            SqlCacheDependency sqlDepend = new SqlCacheDependency("MyDatabase", "Movies");
            Cache.Insert("Movies", movies, sqlDepend);
        }
        grdMovies.DataSource = movies;
        grdMovies.DataBind();
    }

    private DataTable GetMoviesFromDB()
    {
        Trace.Warn("Retrieving data from database");
        string conString = WebConfigurationManager.ConnectionStrings ["Movies"].ConnectionString;
        SqlDataAdapter dad = new SqlDataAdapter("SELECT Title,Director FROM Movies", conString);
        DataTable movies = new DataTable();
        dad.Fill(movies);
        return movies;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Polling SQL Data Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.44, an instance of the SqlCacheDependency class is created. A database name and table name are passed to the constructor for the SqlCacheDependency class. This class is used as a parameter with the Cache.Insert() method when the DataTable is added to the Cache.

Note

If you need to create dependencies on multiple database tables, then you need to create multiple SqlCacheDependency objects and represent the multiple dependencies with an instance of the AggregateCacheDependency class.

Using Push SQL Cache Dependencies

When using Microsoft SQL Server 2005, you have the option of using Push SQL cache dependencies rather than Polling SQL cache dependencies. Microsoft SQL Server 2005 includes a feature called query notifications, which use the Microsoft SQL Server 2005 Service Broker in the background. The Service Broker can automatically send a message to an application when data changes in the database.

Warning

You can create two types of databases with SQL Server Express: a Local or a Server database. You should not use Push dependencies with a Local database. You should use Push dependencies only with a Server database.

You cannot create new Server databases when using Visual Web Developer. You can create a Server database by using the full version of Visual Studio 2008 or by downloading Microsoft SQL Server Management Studio Express from the Microsoft MSDN website (msdn.microsoft.com).

The advantage of using Push dependencies rather than Polling dependencies is that your ASP.NET application does not need to continuously poll your database for changes. When a change happens, your database is responsible for notifying your application of the change.

Now the bad news. There are significant limitations on the types of queries that you can use with Push dependencies. Here are some of the more significant limitations:

  • The query must use two-part table names (for example, dbo.Movies instead of Movies) to refer to tables.

  • The query must contain an explicit list of column names (you cannot use *).

  • The query cannot reference a view, derived table, temporary table, or table variable.

  • The query cannot reference large object types such as Text, NText, and Image columns.

  • The query cannot contain a subquery, outer join, or self join.

  • The query cannot use the DISTINCT, COMPUTE, COMPUTE BY, or INSERT keywords.

  • The query cannot use many aggregate functions including AVG, COUNT(*), MAX, and MIN.

This is not a complete list of query limitations. For the complete list, refer to the Creating a Query for Notification topic in the SQL Server 2005 Books Online or the MSDN website (msdn.Microsoft.com).

For example, the following simple query won’t work:

SELECT * FROM Movies

This query won’t work for two reasons. First, you cannot use the asterisk (*) to represent columns. Second, you must supply a two-part table name. The following query, on the other hand, will work:

SELECT Title, Director FROM dbo.Movies

You can use Push SQL cache dependencies with stored procedures. However, each SELECT statement in the stored procedure must meet all the requirements just listed.

Configuring Push SQL Cache Dependencies

You must perform two configuration steps to enable Push SQL cache dependencies:

  1. You must configure your database by enabling the SQL Server 2005 Service Broker.

  2. You must configure your application by starting the notification listener.

In this section, you learn how to perform both of these configuration steps.

Warning

Unfortunately, when a Push SQL cache dependency fails, it fails silently, without adding an error message to the Event Log. This makes the situation especially difficult to debug. I recommend that after you make the configuration changes discussed in this section that you restart both your web server and database server.

Configuring a Database for Push SQL Cache Dependencies

Before you can use Push SQL cache dependencies, you must enable the Microsoft SQL Server 2005 Service Broker. You can check whether the Service Broker is activated for a particular database by executing the following SQL query:

SELECT name, is_broker_enabled FROM sys.databases

If the Service Broker is not enabled for a database, then you can enable it by executing an ALTER DATABASE command. For example, the following SQL command enables the Service Broker for a database named MyMovies:

ALTER DATABASE MyMovies SET ENABLE_BROKER

Finally, the ASP.NET process must be supplied with adequate permissions to subscribe to query notifications. When an ASP.NET page is served from Internet Information Server, the page executes in the context of the NT AuthorityNETWORK SERVICE account (in the case of Microsoft Windows Server 2003 or Vista) or the ASPNET account (in the case of other operating systems such as Windows XP).

Executing the following SQL command provides the local ASPNET account on a server named YOURSERVER with the required permissions:

GRANT SUBSCRIBE QUERY NOTIFICATIONS TO "YOURSERVERASPNET"

When you request an ASP.NET page when using the Visual Web Developer web server, an ASP.NET page executes in the security context of your current user account. Therefore, when using a file system website, you’ll need to grant SUBSCRIBE QUERY NOTIFICATIONS permissions to your current account.

Note

Push SQL cache dependencies do not use the SQL Server 2005 Notification Services.

Configuring an Application for Push SQL Cache Dependencies

Before you can receive change notifications in your application, you must enable the query notification listener. You can enable the listener with the Global.asax file in Listing 25.45.

Example 25.45. Global.asax

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">

    void Application_Start(object sender, EventArgs e)
    {
        // Enable Push SQL cache dependencies
        string conString = WebConfigurationManager.ConnectionStrings ["MyMovies"].ConnectionString;
        SqlDependency.Start(conString);
    }
</script>

The Application_Start handler executes once when your application first starts. In Listing 25.45, the SqlDependency.Start() method is called with a connection string to a SQL Express server database named MyMovies.

Warning

The code in Listing 25.45 is commented out in the Global.asax file on the CD that accompanies this book so that it won’t interfere with all the previous code samples discussed in this chapter. You’ll need to remove the comments to use the code samples in the following sections.

Using Push SQL Cache Dependencies with Page Output Caching

You can use Push SQL cache dependencies when caching an entire ASP.NET page. If the results of any SQL command contained in the page changes, then the page is dropped automatically from the cache.

The SqlCommand object includes a property named the NotificationAutoEnlist property. This property has the value True by default. When NotificationAutoEnlist is enabled, a Push cache dependency is created between the page and the command automatically.

For example, the page in Listing 25.46 includes an <%@ OutputCache %> directive that includes a SqlDependency attribute. This attribute is set to the special value CommandNotification.

Example 25.46. PushSQLOutputCache.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="9999" VaryByParam="none"
  SqlDependency="CommandNotification" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Push SQL Output Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now.ToString("T") %>
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:MyMovies %>"
        SelectCommand="SELECT Title, Director FROM dbo.Movies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 25.46 includes a SqlDataSource control that retrieves all the records from the Movies database table. Notice that the SqlDataSource control uses a SQL query that explicitly lists column names and uses a two-part table name. These are requirements when using Push dependencies.

The page in Listing 25.46 displays the current time. If you request the page in your browser, and refresh the page, the time does not change. The time does not change until you modify the Movies database table.

Warning

The page in Listing 25.46 connects to a Server database named MyMovies. You should not use Push dependencies with a Local SQL Express database. The page uses a database table named Movies, which was created with the following SQL command:

CREATE TABLE Movies
(
  Id int IDENTITY NOT NULL,
  Title nvarchar(100) NOT NULL,
  Director nvarchar(50) NOT NULL,
  EntryDate datetime NOT NULL DEFAULT GetDate()
)

Warning

You cannot use Push SQL cache dependencies with an <%@ OutputCache %> directive included in a User Control. In other words, you cannot use Push SQL cache dependencies with Partial Page Caching.

Using Push SQL Cache Dependencies with DataSource Caching

You also can use Push SQL cache dependencies with both the SqlDataSource and ObjectDataSource controls by setting the SqlCacheDependency property. When using Push rather than Polling dependencies, you need to set the SqlCacheDependency property to the value CommandNotification.

For example, the page in Listing 25.47 contains a SqlDataSource control that has both its EnableCaching and SqlDependency properties set.

Example 25.47. PushSQLDataSourceCache.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void srcMovies_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
    {
        lblMessage.Text = "Retrieving data from database";
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Push SQL DataSource Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        EnableViewState="false"
        Runat="server" />
    <hr />

    <asp:GridView
        id="grdMovies"
        DataSourceID="srcMovies"
        Runat="server" />

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString="<%$ ConnectionStrings:MyMovies %>"
        SelectCommand="SELECT Title, Director FROM dbo.Movies"
        EnableCaching="true"
        SqlCacheDependency="CommandNotification"
        OnSelecting="srcMovies_Selecting"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In Listing 25.47, the SqlDataSource control includes a Selecting event handler. Because this event is raised when the data cannot be retrieved from the cache, you can use this event to determine when the data is retrieved from the cache or the database server (see Figure 25.17).

Using Push SQL cache dependencies with a DataSource control.

Figure 25.17. Using Push SQL cache dependencies with a DataSource control.

Warning

The page in Listing 25.47 connects to a Server database named MyMovies. You should not use Push dependencies with a Local SQL Express database. The page uses a database table named Movies, which was created with the following SQL command:

  CREATE TABLE Movies
  (
    Id int IDENTITY NOT NULL,
    Title nvarchar(100) NOT NULL,
    Director nvarchar(50) NOT NULL,
    EntryDate datetime NOT NULL DEFAULT GetDate()
  )

Using Push SQL Cache Dependencies with Data Caching

You can use Push SQL cache dependencies when working with the Cache object. You represent a Push SQL cache dependency with an instance of the SqlCacheDependency class.

For example, in the Page_Load() handler in Listing 25.48, a DataTable is added to the cache that represents the contents of the Movies database table. The DataTable is displayed in a GridView control.

Example 25.48. PushSQLDataCache.aspx

<%@ Page Language="C#" Trace="true" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        DataTable movies = (DataTable)Cache["Movies"];
        if (movies == null)
        {
            Trace.Warn("Retrieving data from database");
            string conString = WebConfigurationManager.ConnectionStrings ["MyMovies"].ConnectionString;
            SqlDataAdapter dad = new SqlDataAdapter("SELECT Title,Director FROM dbo.Movies", conString);
            SqlCacheDependency sqlDepend = new SqlCacheDependency(dad.SelectCommand);
            movies = new DataTable();
            dad.Fill(movies);

            Cache.Insert("Movies", movies, sqlDepend);
        }
        grdMovies.DataSource = movies;
        grdMovies.DataBind();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Push SQL Data Cache</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Notice that an instance of the SqlCacheDependency class is created. A SqlCommand object is passed to the constructor for the SqlCacheDependency class. If the results of the SqlCommand changes, then the DataTable will be dropped automatically from the cache.

The order of the commands here is important. You need to create the SqlCacheDependency object before you execute the command. If you call the Fill() method before you create the SqlCacheDependency object, then the dependency is ignored.

Warning

The page in Listing 25.48 connects to a Server database named MyMovies. You should not use Push dependencies with a Local SQL Express database. The page uses a database table named Movies, which was created with the following SQL command:

  CREATE TABLE Movies
  (
    Id int IDENTITY NOT NULL,
    Title nvarchar(100) NOT NULL,
    Director nvarchar(50) NOT NULL,
    EntryDate datetime NOT NULL DEFAULT GetDate()
  )

Summary

In this chapter, you learned how to improve the performance of your ASP.NET applications by taking advantage of caching. In the first part of this chapter, you learned how to use each of the different types of caching technologies supported by the ASP.NET Framework.

First, you learned how to use Page Output Caching to cache the entire rendered contents of a page. You learned how to create different cached versions of the same page when the page is requested with different parameters, headers, and browsers. You also learned how to remove pages programmatically from the Page Output Cache. Finally, we discussed how you can define Cache Profiles in a web configuration file.

Next, you learned how to use Partial Page Caching to apply different caching policies to different regions in a page. You learned how to use post-cache substitution to dynamically inject content into a page that has been output cached. You also learned how to use User Controls to cache different areas of a page.

We also discussed how you can cache data by using the different DataSource controls. You learned how to enable caching when working with the SqlDataSource, ObjectDataSource, and XmlDataSource controls.

Next, you learned how to use the Cache object to cache items programmatically. You learned how to add items to the cache with different expiration policies and dependencies. You also learned how to configure the maximum size of the cache in the web configuration file.

Finally, we discussed SQL cache dependencies. You learned how to use SQL cache dependencies to reload database data in the cache automatically when the data in the underlying database changes. You learned how to use both Polling and Push SQL cache dependencies with Page Output Caching, DataSource Caching, and the Cache object.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.162.26