
Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
518 C H A P T E R 1 4 ■ U S E R C O N T R O L S
Notice that you need to use a read-only property, because it’s not possible for the web page to replace the control with something different.
Now this is how you would set the foreground color in the hosting page:
TimeDisplay1.InnerLink.ForeColor = System.Drawing.Color.Green;
Keep in mind that when you use this practice, you expose all the details of the inner control. This means the web page can call methods and receive events from that control. This approach gives unlimited flexibility, but it reduces the reusability of the code. It also increases the chance that your web page will become tightly coupled to the internal details of the current implementation of your control, thereby making it less likely that you can revise or enhance the user control without disrupting the web pages that use it. As a general rule, it’s always better to create dedicated methods, events, and properties to expose just the functionality you need, rather than opening a back door that could be used to create messy workarounds.
Dynamically Loading User Controls
So far you’ve seen how you can add server controls to a page by registering the type of user control and adding the corresponding tag. You can also create user controls dynamically—in other words, create them on the fly using nothing but a little web-page code.
This technique is similar to the technique you used to add ordinary web controls dynamically (as described in Chapter 3). As with ordinary controls, you should do the following:
•Add user controls when the Page.Load event fires (so that your user control can properly restore its state and receive postback event).
•Use container controls and the PlaceHolder control to make sure the user controls end up exactly where you want.
•Give the user control a unique name by settings its ID property. You can use this information to retrieve a reference to the control when you need it with the Page.FindControl() method.
This has one additional wrinkle. You can’t create a user control object directly, like you can with an ordinary control. That’s because user controls aren’t entirely based on code—they also require the control tags that are defined in the .ascx file. To use a user control, ASP.NET needs to process this file and initialize the corresponding child control objects.
To perform this step, you need to call the Page.LoadControl() method. When you call LoadControl(), you pass the filename of the .ascx user control markup file. LoadControl() returns a UserControl object, which you can then add to the page and cast to the specific class type to access control-specific functionality.
Here’s an example that loads the TimeDisplay user control dynamically and adds it to the page using a PlaceHolder control:
TimeDisplay ctrl = (TimeDisplay)Page.LoadControl("TimeDisplay.ascx");
PlaceHolder1.Controls.Add(ctrl);
Despite this slightly awkward detail, dynamically loading is a powerful technique when used in conjunction with user controls. It’s commonly used to create highly configurable portal frameworks.


520 C H A P T E R 1 4 ■ U S E R C O N T R O L S
Here’s the code that loads the selected control:
protected void Page_Load(object sender, System.EventArgs e)
{
//Remember that the control must be loaded in the Page.Load event handler.
//The DropDownList.SelectedIndexChanged event fires too late.
string ctrlName = listControls.SelectedItem.Value; if (ctrlName.EndsWith(".ascx"))
{
placeHolder.Controls.Add(Page.LoadControl(ctrlName));
}
lbl.Text = "Loaded..." + ctrlName;
}
This example demonstrates a number of interesting features. First, because the PlaceHolder is stored in a formatted container, the user controls you load automatically acquire the container’s font, background color, and so on (unless they explicitly define their own fonts and colors).
Best of all, because you’re loading these controls when the Page.Load event fires, the control objects are able to handle their own events. You can try this by loading the TimeDisplay user control and then clicking the link to refresh the time.
■Note Because the TimeDisplay control isn’t loaded until the page is posted back at least once, it won’t show the time until you click the link at least once. Instead, it will start with the generic control name text. You can solve this problem in a number of ways, including calling the RefreshTime() method from your web page when the control is loaded. An even better approach is to create an interface for all your user controls that defines certain basic methods, such as InitializeControl(). That way, you can initialize any control generically. Most portal frameworks use interfaces to provide this type of standardization.
It’s not too difficult to extend this example to provide an entire configurable web page. All you need to do is create more panels and organize them on your web page (possibly using tables and other panels to group them). This might seem like a tedious task, but you can actually use it quite effectively by writing some generic code that deals with all the panels on your page. One option is to create a user control that loads other user controls. Another approach is a custom method, as shown here, which handles user control loading for three panels:
protected void Page_Load(object sender, System.EventArgs e)
{
LoadControls(div1);
LoadControls(div2);
LoadControls(div3);
}
private void LoadControls(Control container)
{
DropDownList list = null; PlaceHolder ph = null; Label lbl = null;
// Find the controls for this panel. foreach (Control ctrl in container.Controls)
{
if (ctrl is DropDownList)
{
list = (DropDownList)ctrl;

C H A P T E R 1 4 ■ U S E R C O N T R O L S |
521 |
}
else if (ctrl is PlaceHolder)
{
ph = (PlaceHolder)ctrl;
}
else if (ctrl is Label)
{
lbl = (Label)ctrl;
}
}
// Load the dynamic content into this panel. string ctrlName = list.SelectedItem.Value; if (ctrlName.EndsWith(".ascx"))
{
ph.Controls.Add(Page.LoadControl(ctrlName));
}
lbl.Text = "Loaded..." + ctrlName;
}
Figure 14-8 shows this example in action.
Figure 14-8. A dynamic web page with multiple user controls
Using this technique to build an entire web portal framework is possible, but it requires significant work before it would be practical. Creating this framework is a tedious, time-consuming task. In Chapter 31 you’ll learn about a web parts, a native ASP.NET solution for building web portals that doesn’t force you to reinvent the wheel. Web parts are based, at least in part, on user controls.

522 C H A P T E R 1 4 ■ U S E R C O N T R O L S
Partial Page Caching
In Chapter 11, you learned how you can cache a web page by adding the OutputCache directive to the .aspx page. This type of caching, called output caching, caches a rendered HTML version of the page, which ASP.NET can reuse automatically for future requests without executing any of your page code.
One of the drawbacks with response caching is that it works on an all-or-nothing basis. It doesn’t work if you need to render a portion of your page dynamically. For example, you might want to cache a table that’s filled with records read from a data source so that you can limit the round-trips to the database server, but you might still need to get a fresh output for the rest of the page. If that’s your situation, user controls can provide exactly what you’re looking for because they can cache their own output. This feature is called partial caching, or fragment caching, and it works in almost the same way as output caching. The only difference is that you add the OutputCache directive to the user control, instead of the page.
To test this feature, add the following line to the .ascx portion of a user control such as the TimeDisplay:
<%@ OutputCache Duration="10" VaryByParam="None" %>
Now in the hosting page you’ll see that the displayed time won’t change for ten seconds. Refreshing the page has no effect. The VaryByParam parameter has the same meaning as it did with web pages—it allows to you to generate and cache fresh HTML output when the parameters in the query string portion of the URL change.
Alternatively, you can enable caching by adding the following attribute to the declaration of your user control class:
[PartialCaching(10)]
public class MyUserControl : UserControl { ... }
There’s one caveat when using fragment caching. When a user control is cached, the user control essentially becomes a block of static HTML. As a result, the user control object won’t be available to your web-page code. Instead, ASP.NET instantiates one of two more generic object types, depending on how the user control was created. If the user control was created declaratively (by adding a user tag to the web page), a StaticPartialCachingControl object is added. If the user control was created programmatically (using the LoadControl() method), a PartialCachingControl object is added. ASP.NET places the object into the logical position that a user control would occupy in the page’s control hierarchy if it were not cached. However, these objects are just placeholders— they won’t allow you to interact with the user control through its properties or methods. If you aren’t sure if caching is in effect, you should test for a null reference before you attempt to use the user control object.
VaryByControl
If your user control contains input controls, it’s difficult to use caching. The problem occurs if the content in the input controls affects the cached content that the user control displays. With ordinary caching, you’re stuck reusing the same copy of the user control, regardless what the user types into an input control. (A similar problem exists with web pages, which is why it seldom makes sense to cache a web page that includes input controls.)
The VaryByControl property solves this problem. VaryByControl takes a semicolon-delimited string of control names that are used to vary the cached content in much the same way that VaryByParameter varies the cached content for query string values.

C H A P T E R 1 4 ■ U S E R C O N T R O L S |
523 |
For example, consider the following user control:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="VaryByControl.ascx.cs" Inherits="VaryByControl" %>
<asp:DropDownList id="lstMode" runat="server" Width="187px"> <asp:ListItem>Large</asp:ListItem> <asp:ListItem>Small</asp:ListItem> <asp:ListItem>Medium</asp:ListItem>
</asp:DropDownList> <br />
<asp:button ID="Button1" text="Submit" OnClick="SubmitBtn_Click" runat=server/> <br /><br />
Control generated at:<br /> <asp:label id="TimeMsg" runat="server" />
When the button is clicked, it displays the current date in one of three formats.
protected void Page_Load(object sender, EventArgs e)
{
switch (lstMode.SelectedIndex)
{
case 0:
TimeMsg.Font.Size = FontUnit.Large; break;
case 1:
TimeMsg.Font.Size = FontUnit.Small; break;
case 2:
TimeMsg.Font.Size = FontUnit.Medium; break;
}
TimeMsg.Text = DateTime.Now.ToString("F");
}
It’s not sufficient to keep one cached copy of this page, because the display format changes depending on the selection in the lstMode control (see Figure 14-9).
Figure 14-9. Content that varies by control selection
You can handle this using the VaryByControl attribute and referring specifically to this control:
<%@ OutputCache Duration="30" VaryByControl="lstMode" %>
When you try this example, you’ll see a different date for each option, which emphasizes that ASP.NET maintains a separate cached copy for each list selection.

524 C H A P T E R 1 4 ■ U S E R C O N T R O L S
Sharing Cached Controls
If you use the same user control in ten different pages, ASP.NET will cache ten separate versions of that control. This gives each page the chance to customize the user control the first time is executed, before the user control is cached. However, in many cases you might find that you reuse the same user control on multiple pages and you don’t need to introduce page-specific customizations. In this case, you can save memory by telling ASP.NET to share the cached copy of the control.
ASP.NET enables this scenario through the Shared property of the OutputCache directive. The Shared property works only when you are applying the directive to a user control, not a web form. Here’s an example:
<%@ OutputCache Duration="10" VaryByParam="None" Shared="True" %>
You can also make the same request by adding the PartialCaching attribute to the class declaration for the user control:
[PartialCaching(10, null, null, null, true)] public class MyUserControl : UserControl
{ ... }
The null parameters here represent VaryByParameter, VaryByControl, and VaryByCustom.
Summary
In this chapter, you learned how to create some simple and some sophisticated user controls. You also saw how to load user controls dynamically and how to cache them. Though user controls are easy to create, they don’t solve every custom control challenge. In fact, user controls are quite limited in scope (they can’t be easily shared across applications), and they have limited design-time support (for example, you can’t attach event handlers in the Properties window). User controls also lack advanced features and aren’t well suited to rendering HTML and JavaScript on the fly. To improve on this situation, you can step up to custom controls, which are much more sophisticated and quite a bit more complicated to create. Chapter 27 describes custom controls.
■Note Although server controls are more powerful than user controls, most of the concepts you’ve learned in this chapter apply to server controls in the same way that they apply to user controls. For example, you can create server controls that include properties and methods, use custom objects, fire events, and expose child controls.

C H A P T E R 1 5
■ ■ ■
Themes and Master Pages
Building a professional web application involves much more than designing individual web pages. You also need the tools to integrate your web pages into a complete, unified website. In this chapter, you’ll consider two new ASP.NET features that let you do that.
First up is a feature called themes, which let you define the formatting details for various controls and seamlessly reuse these formats in multiple pages. Themes make it much easier to standardize your website’s look and feel and tweak it later. Once a theme is in place, you can give your entire website a face-lift just by changing the theme definition.
A more impressive innovation is master pages, which let you create reusable page templates. Using a master page, you can define the layout for your website pages, complete with all the usual details such as headers, menu bars, and ad banners. Once you’ve formalized this structure, you can use the master page throughout your website, ensuring that all pages have the same design. Visitors can then surf from one section to another without noticing any change.
In this chapter, you’ll learn how to use themes and master pages, two features that are new in ASP.NET 2.0, to standardize your websites.
Standardizing Website Formatting
The first step you can follow to integrate your website is to adopt a consistent visual style. In other words, standardize ruthlessly. If you want to tweak the font or border of a button, make sure you change it for every button you include.
Being consistent isn’t always easy. To help manage the details, you can use CSS or themes.
Cascading Style Sheets
One of the most common ways to apply standardized formatting is to use CSS. CSS provides a cross-platform solution for formatting web pages that works in conjunction with HTML 4 and is supported by virtually all modern browsers. In fact, early versions of Visual Studio automatically generated a Styles.css file for you to use in your website. (Later versions of Visual Studio abandoned this practice in favor of less clutter.)
■Tip You can get the technical lowdown on CSS at http://www.w3.org/Style/CSS, or you can visit http://www.w3schools.com/css for a thorough tutorial.
With CSS, you use a stylesheet to define a set of formatting presets. You then link this stylesheet |
|
to the appropriate control using the CssClass property. To try it and add an (almost) empty |
|
stylesheet to your web project, choose Website Add New Item in Visual Studio. Then select Style |
|
Sheet, and click OK. |
525 |

526 C H A P T E R 1 5 ■ T H E M E S A N D M A S T E R PA G E S
Stylesheets consist of rules. Each rule defines how a single ingredient in your web page should be formatted. For example, if you want to define a rule for formatting headings, you start by defining a rule with a descriptive name, like this:
.heading1
{
}
Each rule name has two parts. The portion before the period indicates the tag to which the rule applies. In this example, nothing appears before the period, which means the rule can apply to any tag. The portion after the period is a unique name (called the CSS class name) that you choose to identify your rule.
Once you’ve defined a rule, you can add the appropriate formatting information. Here’s an example the sets the heading1 style to a large sans-serif font with a green foreground color:
.heading1
{
font-weight: bold; font-size: large; color: limegreen;
font-family: Verdana, Arial, Sans-Serif;
}
■Tip If hand-writing CSS rules seems like too much work, don’t worry—Visual Studio allows you to build a style rule using the same designer you use to format HTML tags. To use this feature, start by adding your rule declaration. Then, right-click between the two curly braces, and select Build Style. You’ll see the familiar Style Builder dialog box where you can point and click your way to custom fonts, borders, backgrounds, and alignment.
A typical stylesheet defines a slew of rules. In fact, stylesheets are often used to formally define the formatting for every significant piece of a website’s user interface. The following stylesheet serves this purpose. It defines three rules (heading1, heading2, and blockText) and a body rule that is applied to all tags automatically as a baseline for formatting.
body
{
font-family: Verdana, Arial, Sans-Serif; font-size: small;
}
.heading1
{
font-weight: bold; font-size: large; color: limegreen;
}
.heading2
{
font-weight: bold; font-size: medium; font-style: italic; color: darkkhaki;
}
