ptg 1364 CHAPTER 29 Caching Application Pages and Data LISTING 29.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 29.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. LISTING 29.20 NewsRotator.cs using System; using System.Data; using System.Web; From the Library of Wow! eBook ptg 1365 Using Partial Page Caching 29 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 36, “Building Custom Controls.” The book’s website 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 29.7). From the Library of Wow! eBook ptg 1366 CHAPTER 29 Caching Application Pages and Data When you use post-cache substitution (declaratively or programmatically) 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, you should take advan- tage 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. FIGURE 29.7 Displaying dynamic news items in a cached page. From the Library of Wow! eBook ptg 1367 Using Partial Page Caching 29 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 29.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). LISTING 29.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 29.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 29.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 29.8). From the Library of Wow! eBook ptg 1368 CHAPTER 29 Caching Application Pages and Data LISTING 29.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” /> FIGURE 29.8 Caching the output of a User Control. From the Library of Wow! eBook ptg 1369 Using Partial Page Caching 29 </div> </form> </body> </html> You can use the following attributes with an <%@ OutputCache %> directive declared in a User Control: . Duration—The amount of time in seconds that the rendered content of the User Control is cached. . Shared—Enables you to share the same cached version of the User Control across multiple pages. . VaryByParam—Enables 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. . VaryByControl—Enables 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. . VaryByCustom—Enables you to specify a custom string used by a custom cache poli- cy. (You also can supply the special value browser, which causes different cached ver- sions 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; 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”; From the Library of Wow! eBook ptg 1370 CHAPTER 29 Caching Application Pages and Data 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, the contents of each user control is cached separately. If you want to cache the same User Control content across multiple pages, 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 29.23 includes the Shared attribute. LISTING 29.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: . Cached—Enables you to enable or disable caching. . Dependency—Enables you to get or set a cache dependency for the User Control. . Duration—Enables you to get or set the amount of time (in seconds) that content is cached. From the Library of Wow! eBook ptg 1371 Using Partial Page Caching 29 . SupportsCaching—Enables you to check whether the control supports caching. . VaryByControl—Enables you to create different cached versions of the control, depending on the value of a control. . VaryByParams—Enables 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: . SetExpires—Enables you to set the expiration time for the cache. . SetSlidingExpiration—Enables you to set a sliding expiration cache policy. . SetVaryByCustom—Enables 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 29.24 uses a sliding expiration policy of 1 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. LISTING 29.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 book’s website 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, the User Control isn’t dropped from the cache. From the Library of Wow! eBook ptg 1372 CHAPTER 29 Caching Application Pages and Data 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 29.25 displays all the movies from the Movies.xml file in a GridView control. The User Control includes a Page_Load() handler that creates a dependency on the Movies.xml file. LISTING 29.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 code download on the website that accompanies this book includes a page named ShowMovieFileDependency, which displays the MovieFileDependency User Control (see Figure 29.9). If you open the page, the User Control is automatically cached until you modify the Movies.xml file. From the Library of Wow! eBook ptg 1373 Using Partial Page Caching 29 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, 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, 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 29.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 29.10). FIGURE 29.9 Displaying a User Control with a file dependency. From the Library of Wow! eBook . ptg 13 64 CHAPTER 29 Caching Application Pages and Data LISTING 29.19 ShowWriteSubstitution.aspx <%@ Page Language=”C#” %> <%@ OutputCache Duration=”15” VaryByParam=”none”. 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. %> <script runat=”server”> void Page_Load() { CacheDependency depend = new CacheDependency(MapPath(“~/Movies.xml”)); this.CachePolicy.Dependency = depend; } </script> User Control