18.2 Output CachingOutput caching is the caching of pages or portions of pages that are output to the client. This does not happen automatically. The developer must enable output caching using either the OutputCache page directive or the HttpCachePolicy class. Both methods will be described. Output caching can be applied to an entire page or a portion of the page. To cache only a portion of a page, the caching is applied to a user control contained within the page. This too will be described later in this section. 18.2.1 The OutputCache Page DirectiveThe OutputCache page directive, like all page directives, goes at the top of the page file. (For a complete description of page directives, see Chapter 6.) A typical example of an OutputCache page directive looks something like the following: <%@ OutputCache Duration="60" VaryByParam="*" %> The full syntax is: <%@ OutputCache Duration="number of seconds" VaryByParam="parameter list" Location="location" VaryByControl="control list" VaryByCustom="custom output" VaryByHeader= "header list" %> Only the first two parameters, Duration and VaryByParam, are required. The VaryBy... parameters allow different versions of the cached page to be stored, with each version satisfying the combination of conditions being varied. The various parameters are described in the following sections. 18.2.1.1 DurationThe Duration parameter specifies the number of seconds that the page or user control is cached. Items placed in the output cache are only valid for this specified time period. When the time limit is reached, then the cache is said to be expired. The next request for the cached page or user control after the cache is expired causes the page or user control to be regenerated, and the cache is refilled with the fresh copy. An example will clarify this. Example 18-1 and Example 18-2 show the VB.NET and C# versions, respectively, of a very simple web page with output caching implemented. Each time the page is loaded, it will display the time in a Label control. The HTML is omitted from Example 18-2, since it is identical to that in Example 18-1. Running the page in a browser gives the result shown in Figure 18-1. Example 18-1. Simple output caching in VB.NET, vbOutputCache-01.aspx<%@ Page Language="VB" %>
<%@ OutputCache Duration="10" VaryByParam="*" %>
<script runat="server">
sub Page_Load(ByVal Sender as Object, _
ByVal e as EventArgs)
lblMsg.Text = "This page was loaded at " & _
DateTime.Now.ToString("T")
end sub
</script>
<html>
<body>
<form runat="server">
<h1>Output Caching</h1>
<asp:Label
id="lblMsg"
runat="server"/>
</form>
</body>
</html>
Example 18-2. Simple output caching in C#, csOutputCache-01.aspx<%@ Page Language="C#" %>
<%@ OutputCache Duration="10" VaryByParam="*" %>
<script runat="server">
void Page_Load(Object Source, EventArgs E)
{
lblMsg.Text = "This page was loaded at " +
DateTime.Now.ToString("T");
}
</script>
Figure 18-1. Results of simple caching demoIn Example 18-1 and Example 18-2, the only thing necessary to implement output caching is the second line in the listing, the OutputCache page directive. It specifies a Duration of 10 seconds. (The other parameter, VaryByParam, will be explained in the next section.) This means that if the same page is requested from the server within 10 seconds of the original request, the subsequent request will be served out of the cache, rather than being regenerated by ASP.NET. This is easy to verify. Run the page and note the time. Then quickly refresh the page in the browser. If you refresh within 10 seconds of originally running the page, the displayed time will not have changed. You can refresh the page as many times as you wish, but the displayed time will not change until 10 seconds have passed. 18.2.1.2 VaryByParamThe VaryByParam parameter allows you to cache different versions of the page depending on which parameters are submitted to the server when the page is requested. These parameters are contained in a semicolon-separated list of strings. In the case of a GET request, the strings in the parameter list represent query string values contained in the URL. In the case of a POST request, the strings represent variables sent as part of the form. There are two special values for the VaryByParam parameter:
To see the effects of the VaryByParam parameter, modify the previous example. Add two labels for displaying parameters passed in as a query string as part of the URL in a GET request. Also, change the Duration parameter to 60 seconds to give you more time to explore the effects. The resulting .aspx page is shown in Example 18-3 (for VB.NET) and Example 18-4 (for C#). (The HTML is omitted from Example 18-4 since it is identical to that in Example 18-3.) Example 18-3. Output caching using the VaryByParam parameter in VB.NET, vbOutputCache-02.aspx<%@ Page Language="VB" %> <%@ OutputCache Duration="60" VaryByParam="*" %> <script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) lblMsg.Text = "This page was loaded at " & _ DateTime.Now.ToString("T") lblUserName.Text = Request.Params("username") lblState.Text = Request.Params("state") end sub </script> <html> <body> <form runat="server"> <h1>Output Caching</h1> <asp:Label id="lblMsg" runat="server"/> <br/> <br/> UserName: <asp:Label id="lblUserName" runat="server"/> <br/> State: <asp:Label id="lblState" runat="server"/> </form> </body> </html> Example 18-4. Output caching using the VaryByParam in parameter in C#, csOutputCache-02.aspx<%@ Page Language="C#" %>
<%@ OutputCache Duration="60" VaryByParam="*" %>
<script runat="server">
void Page_Load(Object Source, EventArgs E)
{
lblMsg.Text = "This page was loaded at " +
DateTime.Now.ToString("T");
lblUserName.Text = Request.Params["username"];
lblState.Text = Request.Params["state"];
}
</script>
To test Example 18-3 and Example 18-4, enter the following URL in a browser: http://localhost/progaspnet/vbOutputCache-02.aspx?username=Dan&state=MA This will give the result shown in Figure 18-2. Figure 18-2. Results of caching in VaryByParam demoNow enter the same URL but with different parameters, say username=Jesse and state=NY, as in: http://localhost/progaspnet/vbOutputCache-02.aspx?username=Jesse&state=NY This will give a different time in the resulting page. Now go back and enter the original URL with username=Dan and state=MA. You will see the original time shown in Figure 18-2, assuming 60 seconds have not passed since you first entered the URL. Suppose the previous example was part of an application where the username was needed for login purposes and the state was used to query a database to return information about publicly traded firms in that state. In that case, it would make no sense to cache based on the username, but it would make a lot of sense to cache based on the state parameter. To accomplish this, set VaryByParam equal to the parameter(s) you wish to cache by. So, for example, to cache only by state, use the following OutputCache directive: <%@ OutputCache Duration="60" VaryByParam="state" %> If you need to cache by the unique combination of two parameters, say state and city, use a directive similar to: <%@ OutputCache Duration="60" VaryByParam="state;city" %> 18.2.1.3 LocationThe Location parameter specifies the machine where the cached data is stored. The permissible values for this parameter are contained in the OutputCacheLocation enumeration (see Table 18-1).
The Location parameter is not supported when output caching user controls. 18.2.1.4 VaryByControlThe VaryByControl parameter is used when caching user controls, which will be described in Section 18-2.2 later in this chapter. This parameter is not supported in OutputCache directives in web pages (.aspx files). The values for this parameter consist of a semicolon-separated list of strings. Each string represents a fully qualified property name on a user control. 18.2.1.5 VaryByCustomThe VaryByCustom parameter allows the cache to be varied by browser if the value of the parameter is set to browser. In this case, the cache is varied by browser name and major version. In other words, there will be separate cached versions of the page for IE 4, IE 5, Netscape 6, or any other browser type or version used to access the page. 18.2.1.6 VaryByHeaderThe VaryByHeader parameter allows the cache to by varied by HTTP header. The value of the parameter consists of a semicolon-separated list of HTTP headers. This parameter is not supported in OutputCache directives in user controls. 18.2.2 Fragment Caching: Caching Part of a PageAll the examples shown so far have cached the entire page. Sometimes all you want to cache is part of the page. To do this, wrap that portion of the page you want to cache in a user control and cache just the user control. This is known as fragment caching. (For a complete discussion of user controls, see Chapter 14.) For example, suppose you develop a stock portfolio analysis page, where the top portion of the page displays the contents of the user's stock portfolio, and the bottom portion contains a data grid showing historical data about one specific stock. There would be little benefit in caching the top portion of the page, since it will be different for every user. However, it is likely that in a heavily used web site, many people will be requesting historical information about the same stock, so there would be benefit to caching the bottom portion of the page. This is especially true since generating the historical data requires a relatively expensive database query. In this case, you can wrap the data grid in a user control and cache just that. To demonstrate fragment caching, create the very simple user control shown in Example 18-5 using VB.NET and in Example 18-6 using C#. Example 18-5. Simple user control in VB.NET, vbUserControl.ascx<%@ Control Language="VB" %>
<%@ OutputCache Duration="10" VaryByParam="*" %>
<script runat="server">
sub Page_Load(ByVal Sender as Object, _
ByVal e as EventArgs)
lblMsg.Text = "This User Control was loaded at " & _
DateTime.Now.ToString("T")
end sub
</script>
<hr/>
<h1>User Control</h1>
<asp:Label
id="lblMsg"
runat="server"/>
<hr/>
Example 18-6. Simple user control in C#, csUserControl.ascx<%@ Control Language="C#" %>
<%@ OutputCache Duration="10" VaryByParam="*" %>
<script runat="server">
void Page_Load(Object Source, EventArgs E)
{
lblMsg.Text = "This User Control was loaded at " +
DateTime.Now.ToString("T");
}
</script>
<hr/>
<h1>User Control</h1>
<asp:Label
id="lblMsg"
runat="server"/>
<hr/>
This user control does nothing more than display the time it was loaded. The visible portion of the control is surrounded by horizontal rules (<hr/>) to distinguish it when it is used in a web page. Notice that the OutputCache directive specifies a Duration of 10 seconds. Now create a web page to use this user control, as shown in Example 18-7 and Example 18-8, in VB.NET and C#, respectively. Example 18-7. Fragment caching demo in VB.NET, vbOutputCache-UserControl.aspx<%@ Page Language="vb" %> <%@ Register TagPrefix="SampleUserControl" TagName="LoadTime" Src="vbUserControl.ascx" %> <script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) lblMsg.Text = "This page was loaded at " & _ DateTime.Now.ToString("T") end sub </script> <html> <body> <form runat="server"> <h1>Fragment Caching</h1> <asp:Label id="lblMsg" runat="server"/> <br/> <SampleUserControl:LoadTime runat="server"/> </form> </body> </html> Example 18-8. Fragment caching demo in C#, csOutputCache-UserControl.aspx<%@ Page Language="C#" %>
<%@ Register TagPrefix="SampleUserControl" TagName="LoadTime"
Src="csUserControl.ascx" %>
<script runat="server">
void Page_Load(Object Source, EventArgs E)
{
lblMsg.Text = "This page was loaded at " +
DateTime.Now.ToString("T");
}
</script>
<html>
<body>
<form runat="server">
<h1>Fragment Caching</h1>
<asp:Label
id="lblMsg"
runat="server"/>
<br/>
<SampleUserControl:LoadTime
runat="server"/>
</form>
</body>
</html>
Notice that the web page that uses the user control does not have any caching implemented; there is no OutputCache directive. When you run the web page from Example 18-7 or Example 18-8 in a browser, you will initially see something like Figure 18-3. Figure 18-3. Results of fragment caching demoThe time displayed for both the user control and the containing page are the same. However, if you refresh the view, you will notice that the time the page was loaded will be the current time, while the time the user control was loaded is static until the 10-second cache duration has expired. One caveat to keep in mind when caching user controls is that it is not possible to programmatically manipulate the user control being cached. This is because a user control in cache is only generated dynamically the first time it is requested. After that, the object is not available for the code to interact with. If you need to manipulate the contents of the user control programmatically, the code to do so must be contained within the user control. To demonstrate this, modify the code in Example 18-5 to add a property called UserName to the sample user control. The new user control is shown Example 18-9 (VB.NET) and Example 18-10 (C#). (The HTML is omitted from Example 18-10 since it is identical to that in 18-9.) Example 18-9. User control with UserName property in VB.NET, vbUserControl-02.ascx<%@ Control Language="VB" %> <%@ OutputCache Duration="10" VaryByParam="*" %> <script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) lblMsg.Text = "This User Control was loaded at " & _ DateTime.Now.ToString("T") end sub public property UserName( ) as string get return lblUserName.Text end get set lblUserName.Text = value end set end property </script> <hr/> <h1>User Control</h1> <asp:Label id="lblMsg" runat="server"/> <br/> <asp:Label id="lblUserName" Text="Dan" runat="server"/> <hr/> Example 18-10. User control with UserName property in C#, csUserControl-02.ascx<%@ Control Language="C#" %> <%@ OutputCache Duration="10" VaryByParam="*" %> <script runat="server"> void Page_Load(Object sender, EventArgs e) { lblMsg.Text = "This User Control was loaded at " + DateTime.Now.ToString("T"); } public string UserName { get { return lblUserName.Text; } set { lblUserName.Text = value; } } </script> In Example 18-9 and Example 18-10, a property named UserName was added to the code, with both a Get and a Set method. Also, a label was added to display the UserName. For now, this label is hard-coded to Dan. Now modify the code in Example 18-7 to call this modified user control. The code for this modified page is shown in Example 18-11 (in VB.NET) and in Example 18-12 (in C#). (The HTML is omitted from Example 18-12 since it is identical to that in 18-11.) Example 18-11. Fragment caching demo with a property in VB.NET, vbOutputCache-UserControl-02.aspx<%@ Page Language="vb" %> <%@ Register TagPrefix="SampleUserControl" TagName="LoadTime" Src="vbUserControl-02.ascx" %> <script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) lblMsg.Text = "This page was loaded at " & _ DateTime.Now.ToString("T") lblUserControlText.Text = MyUserControl.UserName end sub sub btn_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs) MyUserControl.UserName = "Jesse" end sub </script> <html> <body> <form runat="server"> <h1>Fragment Caching</h1> <asp:Label id="lblMsg" runat="server"/> <br/> <SampleUserControl:LoadTime ID="MyUserControl" runat="server"/> <br/> <asp:Label id="lblUserControlText" runat="server"/> <br/> <asp:Button id="btn" Text="Change Name to Jesse" OnClick="btn_OnClick" runat="server"/> </form> </body> </html> Example 18-12. Fragment caching demo with a property in C#, csOutputCache-UserControl-02.aspx<%@ Page Language="C#" %>
<%@ Register TagPrefix="SampleUserControl" TagName="LoadTime"
Src="csUserControl-02.ascx" %>
<script runat="server">
void Page_Load(Object sender,
EventArgs e)
{
lblMsg.Text = "This page was loaded at " +
DateTime.Now.ToString("T");
lblUserControlText.Text = MyUserControl.UserName;
}
void btn_OnClick(Object sender,
EventArgs e)
{
MyUserControl.UserName = "Jesse";
}
</script>
The code in Example 18-11 and Example 18-12 adds the highlighted code to populate the lblUserControlText label with the initial value of the lblUserName control contained in the user control. This works fine when the page is first called, giving the result shown in Figure 18-4. Figure 18-4. Results of fragment caching with propertyIt even works as expected if you click the button to change the name to Jesse. This is because the button causes the form to be posted to the server, so everything is regenerated and the request for the user control is not being satisfied from the cache. However, as soon as you refresh the page and ASP.NET attempts to satisfy the request for the user control from the cache, an error occurs. The only way around this is to move all the code that accesses the user control property into the user control itself, as shown in Example 18-13 and Example 18-14 for the user control. The calling page then reverts back to the same page shown in Example 18-7 and Example 18-8. (Be certain to change the name of the Src parameter in the Register directive in Example 18-7 or Example 18-8 to point to the correct user control.) The HTML is omitted from Example 18-14 since it is identical to that in Example 18-13. Example 18-13. User control setting UserName property in VB.NET, vbUserControl-03.ascx<%@ Control Language="VB" %> <%@ OutputCache Duration="10" VaryByParam="*" %> <script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) lblMsg.Text = "This User Control was loaded at " & _ DateTime.Now.ToString("T") end sub public property UserName( ) as string get return lblUserName.Text end get set lblUserName.Text = value end set end property sub btn_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs) lblUserName.Text = "Jesse" end sub </script> <hr/> <h1>User Control</h1> <asp:Label id="lblMsg" runat="server"/> <br/> <asp:Label id="lblUserName" Text="Dan" runat="server"/> <br/> <asp:Button id="btn" Text="Change Name to Jesse" OnClick="btn_OnClick" runat="server"/> <hr/> Example 18-14. User control setting UserName property in C#, csUserControl-03.ascx<%@ Control Language="C#" %>
<%@ OutputCache Duration="10" VaryByParam="*" %>
<script runat="server">
void Page_Load(Object sender,
EventArgs e)
{
lblMsg.Text = "This User Control was loaded at " +
DateTime.Now.ToString("T");
}
public string UserName
{
get
{
return lblUserName.Text;
}
set
{
lblUserName.Text = value;
}
}
void btn_OnClick(Object sender,
EventArgs e)
{
lblUserName.Text = "Jesse";
}
</script>
While this restriction on programmatically modifying user controls that are in the cache might seem significant, as a practical matter it should not be. The entire point of putting user controls in the cache is that they will not change while cached. If that is not the case, then they are probably not a good candidate for caching. |