Hot Tip
LANGUAGE: C#
ASP.NET VERSIONS: 1.0 | 1.1
Dynamically Load Cached User Controls (and Live to Tell About It)
Learn to recognize - and avoid - this common caching snafu.
By Jeff Prosise
Quick: can you spot the fatal flaw in the following ASPX file and the accompanying user control (ASCX file)? It's a common problem that ASP.NET developers encounter all the time - one that causes the page to throw an exception every time it's loaded. And it's one for which the solution is far from obvious:
<!-- Time.aspx -->
<%@ Import Namespace="System.Drawing" %>
<%@ Register TagPrefix="user" TagName="TimeControl"
Src="Time.ascx" %>
<html>
<body>
<h1><asp:PlaceHolder ID="Here" RunAt="server" /></h1>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
Control control = LoadControl ("Time.ascx");
Here.Controls.Add (control);
((TimeControl) control).BackColor = Color.Yellow;
}
</script>
<!-- Time.ascx -->
<%@ Control ClassName="TimeControl" %>
<%@ Import Namespace="System.Drawing" %>
<%@ OutputCache Duration="5" VaryByParam="None" %>
<asp:Label ID="Output" RunAt="server" />
<script language="C#" runat="server">
public Color BackColor
{
get { return Output.BackColor; }
set { Output.BackColor = value; }
}
void Page_Load (Object sender, EventArgs e)
{
Output.Text = DateTime.Now.ToLongTimeString ();
}
</script>
The problem is that the Page_Load code that loads and initializes the user control assumes the control's type is TimeControl. That's a valid assumption if the control isn't cached, but the presence of an @ OutputCache directive in the ASCX file means that LoadControl returns a reference to a System.Web.UI.PartialCachingControl object rather than a TimeControl object, producing an InvalidCastException in the preceding code.
Here's the proper way to dynamically load and initialize a user control, independent of whether the control is cached. Note how the cached control's BackColor property is accessed: through the TimeControl reference stored in PartialCachingControl's CachedControl property. Also note that Page_Load verifies that CachedControl isn't null before using it. That's because CachedControl holds a valid TimeControl reference when LoadControl actually loads the control, but equals null when LoadControl retrieves the control from the cache:
<!-- Time.aspx -->
<%@ Import Namespace="System.Drawing" %>
<%@ Register TagPrefix="user" TagName="TimeControl"
Src="Time.ascx" %>
<html>
<body>
<h1><asp:PlaceHolder ID="Here" RunAt="server" /></h1>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
Control control = LoadControl ("Time.ascx");
Here.Controls.Add (control);
TimeControl time = null;
if (control is TimeControl)
time = (TimeControl) control;
else if (control is PartialCachingControl &&
((PartialCachingControl) control).CachedControl != null)
time = (TimeControl)
((PartialCachingControl) control).CachedControl;
if (time != null)
time.BackColor = Color.Yellow;
}
</script>
When loading user controls dynamically, structure your code this way and it will work with or without an @ OutputCache directive in the ASCX file. Do it the other way, however, and you're in for a nasty surprise if, after you've tested the page, someone adds a caching directive to the ASCX file.
Jeff Prosise is author of several books, including Programming Microsoft .NET (Microsoft Press). He also is a co-founder of Wintellect (http://www.wintellect.com), a software consulting and education firm that specializes in .NET. Contact Jeff at [email protected].