4. Caching Portions of ASP.NET Pages
The capability of caching the output of Web
pages adds a lot of power to your programming arsenal, but sometimes
caching the entire content of a page is not possible or it’s just
impractical. Some pages, in fact, are made of pieces with radically
different features as far as cacheability is concerned. In these
situations, being able to cache portions of a page is an incredible
added value.
If caching the entire page is impractical, you
can always opt for partial caching. Partial caching leverages the
concept of ASP.NET user controls—that is, small, nested pages that
inherit several features of the page. In particular, user controls can
be cached individually based on the browser, GET and POST parameters, and the value of a particular set of properties.
What’s a User Control, Anyway?
A user control is a Web form saved to a distinct file with an .ascx
extension. The similarity between user controls and pages is not
coincidental. You create a user control in much the same way you create a
Web form, and a user control is made of any combination of server and
client controls sewn together with server and client script code. Once
created, the user control can be inserted in an ASP.NET page like any
other server control. ASP.NET pages see the user control as an atomic,
encapsulated component and work with it as with any other built-in Web
control.
The internal content of the user control is
hidden to the host page. However, the user control can define a public
programming interface and filter access to its constituent controls via
properties, methods, and events.
User controls and pages have so much in common
that transforming a page, or a part of it, into a user control is no big
deal. You copy the portion of the page of interest to a new .ascx file and make sure the user control does not contain any of the following tags: <html>, <body>, or <form>. You complete the work by associating a code-behind file (or a <script runat=”server”> block) to code the expected behavior of the user control. Finally, you add a @Control directive in lieu of the @Page directive. Here’s an example of a user control:
<%@ Control Language="C#" CodeFile="Message.ascx.cs" Inherits="Message" %>
<span style="color:<%= ForeColor%>"><%= Text%></span>
Here’s the related code-behind class:
public partial class Message : System.Web.UI.UserControl
{
public string ForeColor;
public string Text;
}
To insert a user control into an ASP.NET page,
you drag it from the project onto the Web form, when in design mode.
Visual Studio .NET registers the user control with the page and prepares
the environment for you to start adding code.
<%@ Page Language="C#" CodeFile="Test.aspx.cs" Inherits="TestUserCtl" %>
<%@ Register Src="Message.ascx" TagName="Message" TagPrefix="x" %>
<html><body>
<form id="form1" runat="server">
<x:Message ID="Message1" runat="server" />
</form>
</body></html>
In the page code-behind class, you work the Message1 variable as you would do with any other server control:
protected void Page_Load(object sender, EventArgs e)
{
Message1.ForeColor = "blue";
Message1.Text = "Hello world";
}
Caching the Output of User Controls
User controls are not only good at modularizing
your user interface, they’re also great at caching portions of Web
pages. User controls, therefore, fully support the @OutputCache directive, although they do so with some differences with ASP.NET pages, as outlined in Table 16-7.
A page that contains some dynamic sections
cannot be cached entirely. What if the page also contains sections that
are both heavy to compute and seldom updated? In this case, you move
static contents to one or more user controls and use the user control’s @OutputCache directive to set up output caching.
To make a user control cacheable, you declare the @OutputCache
attribute using almost the same set of attributes we discussed earlier
for pages. For example, the following code snippet caches the output of
the control that embeds it for one minute:
<% @OutputCache Duration="60" VaryByParam="None" %>
The Location
attribute is not supported because all controls in the page share the
same location. So if you need to specify the cache location, do that at
the page level and it will work for all embedded controls. The same
holds true for the VaryByHeader attribute.
The output of a user control can vary by custom
strings and form parameters. More often, though, you’ll want to vary
the output of a control by property values. In this case, use the new VaryByControl attribute.
Vary by Controls
The VaryByControl
attribute allows you to vary the cache for each specified control
property. For user controls, the property is mandatory unless the VaryByParam attribute has been specified. You can indicate both VaryByParam and VaryByControl, but at least one of them is required.
The following user control displays a grid with
all the customers in a given country. The country is specified by the
user control’s Country property.
<%@ Control Language="C#" CodeFile="CustomersGrid.ascx.cs"
Inherits="CustomersGridByCtl" %>
<%@ OutputCache Duration="30" VaryByControl="Country" %>
<h3><%= DateTime.Now.ToString() %></h3>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
TypeName="Core35.DAL.Customers"
SelectMethod="LoadByCountry">
</asp:ObjectDataSource>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" />
<asp:BoundField DataField="CompanyName" HeaderText="Company" />
<asp:BoundField DataField="ContactName" HeaderText="Contact" />
<asp:BoundField DataField="Country" HeaderText="Country" />
</Columns>
</asp:GridView>
Here is the code file of the user control:
public partial class CustomersGridByCtl : System.Web.UI.UserControl
{
public string Country;
protected void Page_Load(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(Country))
{
ObjectDataSource1.SelectParameters.Add("country", Country);
GridView1.DataSourceID = "ObjectDataSource1";
}
}
}
The @OutputCache directive caches a different copy of the user control output based on the different values of the Country property. Figure 2 shows it in action.
The strings you assign to VaryByControl
can be properties of the user controls as well as ID property values
for contained controls. In this case, you’ll get a distinct cached copy
for each distinct combination of property values on the specified
control.
The Shared Attribute
In Figure 16-8,
you see two instances of the same page sharing the cached output of a
user control. Try the following simple experiment. Make a plain copy of
the page (say, page1.aspx) and give it another name (say, page2.aspx).
You should have two distinct pages that generate identical output. In
particular, both pages contain an instance of the same cacheable user
control. Let’s say that the cache duration of the user control is 30
seconds.
As the next step of the experiment, you open
both pages at different times while the cache is still valid. Let’s say
you open the second page ten seconds later than the first. Interestingly
enough, the two pages are no longer sharing the same copy of user
control output, as Figure 3 documents.
By default, distinct pages don’t share the
output of the same cacheable user control. Each page will maintain its
own copy of the user control response, instead. Implemented to guarantee
total separation and avoid any sort of conflicts, this feature is far
more dangerous than one might think at first. It might lead to flooding
the Web server memory with copies and copies of the user control
responses—one for each varying parameter or control property and one set
for each page that uses the control.
To allow distinct pages to share the same output of a common user control, you need to set the Shared attribute to true in the user control’s @OutputCache directive:
<%@ OutputCache Duration="30" VaryByParam="None" Shared="true" %>
Tip
To avoid memory
problems, you should put a limit on the total amount of memory available
to IIS. It is set to 60 percent of the physical memory, but you should
keep it under 800 MB per Microsoft recommendations. Setting the IIS 6.0
Maximum Used Memory parameter is important especially if output cache is
used aggressively. You’ll set the parameter on a per-application-pool
basis by selecting the IIS 6.0 application pool where your application
is configured to run and opening its Properties dialog box. |
Fragment Caching in Cacheable Pages
If
you plan to cache user controls—that is, you’re trying for partial
caching—it’s probably because you just don’t want to, or cannot, cache
the entire page. However, a good question to ask is: what happens if
user controls are cached within a cacheable page?
Both the page and the controls are cached
individually, meaning that both the page’s raw response and the
control’s raw responses are cached. However, if the cache duration is
different, the page duration wins and user controls are refreshed only
when the page is refreshed.
A cacheable user control can be embedded both in a cacheable page and in a wrapper cacheable user control.
Warning
Cacheable user controls
should be handled with extreme care in the page’s code. Unlike regular
controls, a user control marked with the @OutputCache
directive is not guaranteed to be there when your code tries to access
it. If the user control is retrieved from the cache, the property that
references it in the code-behind page class is just null. if (CustomerGrid1 != null)
CustomerGrid1.Country = "USA";
To avoid bad surprises, you should always check the control reference against the null value before executing any code. |