User controls represent an interesting and powerful level of page reusability in ASP.NET. You can build user controls by extracting and combining blocks of code from existing pages. You can also assemble ASP.NET controls together in a sort of embeddable form for use in Web pages. In this article, you'll get a crash-course on how to design, write and use ASP.NET user controls.

When designing the new version of ASP, Microsoft put a lot of effort into the individuation of a powerful and effective extensibility model. The Extensible Stylesheet Language (XSL) was the first option considered. For some time, the XML/XSL pair looked like the natural way to extend and improve ASP. You describe the page content using XML and render it to a potentially infinite number of target devices using XSL transformations (XSLT). Clearly in this way the core of your code would have been expressed with XSLT instructions, thus making the throughput of the XSLT layer an extremely critical factor capable of affecting the response time and the performance of the server.

Planned for use on a large scale, the problems with XSLT can be narrowed down to two areas. The first area deals with the quirkiness of XSLT syntax. Maintenance certainly becomes a major issue if not a nightmare as soon as you base your code all on XSLT. Secondly, the inability for users to control exactly what the XSLT processor is doing is another problem.

As a result, Microsoft discarded the XSLT option. Instead, they redesigned the ASP environment making it completely component-based. In doing so, they killed two birds with one stone. For one thing, they integrated ASP.NET with the surrounding .NET environment and framework. In addition, they came up with a stable and consolidated model for extending applications?using inheritance and other object-oriented programming techniques. Enter ASP.NET and its families of controls!

New Controls in ASP.NET

At the highest level of abstraction, ASP.NET provides two ways to create new and custom controls. You can create a new control class inheriting from a base class (i.e., WebControl) or you can group a few controls together in a sort of embeddable child page. Although the second approach can appear not particularly object-oriented at first, the class you come up with is built atop the base UserControl class. The first approach is referred to as building custom controls. The second type of controls are said to be user controls or pagelets.

A custom control is primarily a class built to behave like a control and provide a given set of functionalities, such as a textbox or drop-down list. A user control is a form made of any combination of server and client controls sewn together with server and client script code. A user control has a rich user interface but is programmable as a monolithic component. In other words, like a Visual Basic ActiveX control, all of its constituent controls are protected and inaccessible if not through a public programming interface.

An embeddable Web form is the definition that best fits what a user control actually is. You create a user control in much the same way you create a Web form. When you're done with code and layout, you give the resulting file an .ascx extension. This enables you to use the control with any ASP.NET page. Pages see it as a monolithic component and work with it as with any other system Web control. If you are familiar with Dynamic HTML (DHTML) Scriptlets, user controls are just their ASP.NET counterpart.

Writing a DateBox User Control

Let's see what a user control is all about with an example (the source code for this article can be found at: http://www.code-magazine.com/downloads/sepoct2002aspdotnet.zip). The idea is to build an input control that takes a date and splits it into its constituent elements: year, month and day. These date elements are then displayed as individual textboxes. Just as an ASP.NET page, a user control file is made of three main sections: directives, script and layout.

<%@ Control ClassName="DateBox" Language="C#"%>

<script runat="server">
public string Separator = "/";
</script>

<table><tr><td>
   <asp:textbox runat="server" id="_txtMonth" 
columns="2" maxlength="2"
style="font-family:courier new;" />
<%=Separator%>
   <asp:textbox runat="server" id="_txtDay" 
      columns="2" maxlength="2" 
style="font-family:courier new;" />
<%=Separator%>
   <asp:textbox runat="server" id="_txtYear" 
      columns="4" maxlength="4" 
style="font-family:courier new;" />
</td></tr></table>

The @Control directive is very similar to @Page and lets you set general-purpose directives for the control. The most important difference with @Page is that you cannot set the Trace and the Debug attribute, whose values are inherited from the host page. In @Control, you normally set the language of the control (attribute Language) and the public name of the class you're developing (attribute ClassName).

Another interesting directive you might want to set for user controls is @OutputCache to control the caching features of a portion of the page.

The <script> block contains all the server-side code that has to do with the behavior of the embedded controls. Each public entity defined in the <script> block is accessible from the external callers. Here you define the control's methods, properties and events.

Finally, the layout section contains the user interface of the control. You can use any ASP.NET control, including other pagelets and custom controls. In addition, you can make use of data-binding expressions as well as ASP-style <% ... %> code blocks.

The code above creates a control made of three child textbox controls separated by a separator character. The separator character is represented by a string property that is inserted in the layout using a code block.

Another interesting similarity between user controls and Web pages is that you can write user controls using code-behind technology. Code-behind gives you the ability to place all the code for your control into a separate C# or a Visual Basic class. A practical result of this programming approach is that you implement your component in two distinct and physically separated files?one .ascx for directives and layout and one .cs (or .vb) with the business logic. If you choose the code-behind approach, there is no need to have a script block in the ASCX file. Here's how the directive changes for code-behind:

&lt;%@ Control ClassName="DateBox" Language="C#"
Inherits="CodeMagazine.BaseDateBox" 
Src="DateBox.cs"%&gt;

The Inherits attribute indicates the actual base class?it is UserControl by default. The Src attribute points to the source code of the base class. The class is compiled into an assembly on the fly and the assembly linked to the ASP.NET application.

As an alternative, you can take the base class for the pagelet from an existing assembly. In this case, use the Assembly attribute. In any case, the user control must derive directly or indirectly from UserControl.

Other than for writing more modular and encapsulated code, code-behind is important because it lets you use strongly-typed references to the control in client pages.

Building the Control's Programming Interface

In Listing 1, you can see the C# implementation of the properties. Notice the use of the get/set accessors to control how the property value is read and written.

Listing 2 details the methods of the control.

The method SetDate has two overloads and can accept the date both in a MM/DD/YYYY format and as a DateTime object?the official date and time object in the .NET Framework. When a new date is set, the month, day and year components are used to refresh the control's user interface. As shown earlier, the layout of the control is made of three textbox control called _txtMonth, _txtDay and _txtYear, respectively, as shown in Figure 1. These controls are not externally accessible from the host page unless you deliberately decide to expose them using properties.

Figure 1: The DateBox control in action in a sample page. The three textbox controls have a different look-and-feel in Internet Explorer and old versions of Netscape's Communicator.
Figure 1: The DateBox control in action in a sample page. The three textbox controls have a different look-and-feel in Internet Explorer and old versions of Netscape's Communicator.

A Pagelet's View of the Interiors

Notice that the DateBox control can freely access any objects of the Web page infrastructure, including Session, Request, Cache and ViewState. From the host page's viewpoint, the user control is like any other control. Therefore, the pagelet's viewstate flows into the page's viewstate. On the other hand, the pagelet is a page-like container for embedded controls. Consequently, the viewstate of each constituent control is also flushed in the pagelet's viewstate.

When you develop a user control in code-behind mode, the class must define a property member for each ASP.NET control that appears in the pagelet layout. The name of the class member must match the ID property of the control. For example, if you a have a textbox called _txtYear, then you should include a property of type TextBox with the same name.

You don't need to do this for all controls but only for those that you plan to use programmatically. More importantly, these properties must be qualified as public or protected. In no way can they be private. If you mark the properties as private, then the code-behind code cannot access them any longer and an exception is thrown.

This happens because the instance of the user control that ASP.NET pages employ corresponds to the class type set with the ClassName attribute in the @Control directive. If no ClassName attribute has been set, then the name is algorithmically determined from the name of the user control file. The name of the class is file_ascx if file.ascx is the source file. The ASP.NET runtime builds the class dynamically to serve the pagelet declaration in the body of the host page. The newly created class inherits from the code-behind class and, as such, cannot access any internal member that is declared private.

Registering User Controls with a Page

All ASP.NET controls must be linked to the host page. The controls in the .NET Framework belong to the <asp> system-provided namespace. Custom controls, including user controls, need to have a prefix and a tag name so that the ASP.NET parser can correctly map tags with a living instance of the control.

You register a user control for use with an ASP.NET page by using the @Register directive:

&lt;%@ Register TagPrefix="expo" TagName="DateBox"
             Src="datebox.ascx" %&gt;

The TagPrefix and TagName attributes can take arbitrary text that you then use to insert an instance of the control in the page. The Src attribute indicates the URL from which the source of the control must be downloaded. Listing 3 shows a sample ASP.NET page that uses the DateBox control.

The default date of the control can be set in two ways: either using the SelectedDate property or the SetDate method. The property can also be set declaratively in the control's declaration. The method, instead, provides a great deal of flexibility since you can use it with code.

&lt;expo:datebox runat="server" id="theDateBox" 
separator="-" selecteddate="7/1/2002" /&gt;

In Listing 3, the default date is set programmatically in the Page_Load event. When you click the link button, the control retrieves the current date and displays it through the page. Notice that the control does not cache the current date in internal variables. It just reads the current date directly from the textbox controls whenever needed.

Strong-Typed References

As long as you write standalone ASP.NET pages, in which all the code is resident in the <script> block, you normally don't need to strictly reference the user control in a strongly typed manner. Strong-typing, though, provides you with cool facilities like IntelliSense support. To create a strong-typed reference, you must also add the @Reference directive to the page.

&lt;%@ Reference Control="DateBox.ascx" %&gt;

Notice that @Reference does not replace @Register, so use both if you want strong typing. The @Reference directive tells the ASP.NET runtime that a user control source file should be dynamically linked against the page. At this point, you can cast the reference to the control to a variable of the correct type.

DateBox d = (DateBox) theDateBox;
if (!IsPostBack)
d.SetDate("2/2/2002");

A little problem may arise if you want to use a strong-typed reference to a user control in the context of a code-behind ASP.NET page. In this case, how can you link the user control class to a C# or Visual Basic code-behind class? You need a trick like the @Reference directive. However, the directive works by forcing the runtime to create an assembly which is then linked to the application represented by the page. The trick is simple: write the user control in code-behind mode, compile the control's code-behind class and then reference the assembly.

Conclusion

User controls are good at prototyping new controls that have a complex user interface. They are the perfect solution when you need to combine two or more controls. As long as the structure of the new controls is stable during the lifetime of the program, user controls offer you an excellent mix of programming ease and productivity. The architectural principle behind a user control is aggregation rather than inheritance. If you need only an enhanced or stripped-down version of an existing control, you are better off writing a new class that inherits from it.

Further Readings

Dino Esposito, Building Web Solutions with ASP.NET and ADO.NET, Microsoft Press, 2002

Listing 1?Properties of the DateBox User Control

// PROPERTY: Separator (read/write)
public string Separator = "/";

// PROPERTY: Month (read-only)
public int Month
{
get {return Convert.ToInt32(_txtMonth.Text);}
}

// PROPERTY: Day (read-only)
public int Day
{
get {return Convert.ToInt32(_txtDay.Text);}
}
// PROPERTY: Year (read-only)
public int Year
{
get {return Convert.ToInt32(_txtYear.Text);}
}

// PROPERTY: SelectedDate (read/write)
public string SelectedDate
{
get {return GetDateAsString();}
set {SetDate(value);}
}

Listing 2?Methods of the DateBox User Control

// METHOD: SetDate
public void SetDate(string date)
{
DateTime dt;
try {dt = Convert.ToDateTime(date);} 
catch {dt = DateTime.Now;}
SetDate(dt);
}

public void SetDate(DateTime date)
{
__RefreshDate(date);
}

private void __RefreshDate(DateTime dt)
{
_txtMonth.Text = String.Format("{0:00}", dt.Month);
_txtDay.Text = String.Format("{0:00}", dt.Day);
_txtYear.Text = String.Format("{0:0000}", dt.Year);
}

// METHOD: GetDateAsString
public string GetDateAsString()
{
return _txtMonth.Text + Separator +
       _txtDay.Text + Separator + 
       _txtYear.Text;
}

// METHOD: GetDate
public DateTime GetDate()
{
DateTime dt = new DateTime(Year, Month, Day);
return dt;
}

Listing 3?A Sample ASP.NET Page Using the DateBox Control

&lt;%@ Page Language="C#" Trace="true" %&gt;

&lt;script runat="server"&gt;
void Page_Load() {
   if (!IsPostBack)
      theDateBox.SetDate("2/8/2002");
}

void ShowDate(object sender, EventArgs e) {
info.Text = theDateBox.GetDateAsString();
}
&lt;/script&gt;

&lt;html&gt;
&lt;body&gt;
   &lt;form runat="server"&gt;
&lt;expo:datebox runat="server" id="theDateBox" 
separator="-" /&gt;
      &lt;hr&gt;
      &lt;asp:linkbutton runat="server" onclick="ShowDate" 
                      text="Get current date..." /&gt;
      &lt;asp:label runat="server" id="info" /&gt;
   &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;

Table 1: The programming interface of the DateBox user control.

PropertyDescription
DayReturns an integer denoting the day of the currently set date
MonthReturns an integer denoting the month of the currently set date
SelectedDateGets or sets the current date as a MM/DD/YYYY string
SeparatorGets or sets the character(s) to be used as the date separator
YearReturns an integer denoting the year of the currently set date
MethodDescription
GetDateReturns the current date as a DateTime object
GetDateAsStringSimilar to the SelectedDate property, return the date as a string
SetDateSets the current date. Accepts both a string and a DateTime object