Developers are always looking for ways to write software they can reuse both within and between their projects. Web developers are no exception, of course, as evidenced by the ubiquitous use of server-side includes and COM objects by ASP developers.
With the release of the .NET Framework and Visual Studio .NET, Web developers using ASP.NET are armed with Web user controls, which make reuse simple, efficient, and object-oriented. In this article, I’ll walk you through the basics of ASP.NET Web user controls and show you how they promote reusability, reduce maintenance, and increase performance.
Uses for Web user controls
Typically, Web developers create a layout to implement on all the pages in their Web application. During this process, it is easy to identify areas of screen real estate that are used on every Web page. Segmenting these areas into encapsulated containers creates a simple reuse scenario. In the ASP world, these were the primary areas that developers converted into server-side include files that were then referenced on each page. Examples of these include headers, footers, menus, and navigation. In ASP.NET, these types of scenarios can easily be converted to Web user controls, and then dragged and dropped on other pages in the site.
Web user controls should not be confused with ASP.NET server controls. The former allow developers to create reusable portions of pages within individual Web applications using the design-time features of VS.NET. The latter must be created programmatically, can be compiled and reused across applications, and usually support multiple browsers. Microsoft ships a number of ASP.NET server controls with VS.NET.
But Web user controls can represent more than just static HTML in headers and footers. They can fully encapsulate composite controls (both HTML and the ASP.NET server controls, such as the DataGrid and Calendar) and expose public methods and properties. As a result, they can create object-oriented, reusable user interface widgets in the same way that ActiveX controls provide reusable UI elements in desktop applications. To assist in this process, Visual Studio .NET allows Web user controls to be designed graphically by dragging and dropping items from the toolbox on a designer surface. Consequently, Web user controls are often used to encapsulate the data editing regions of pages, search and login functionality, and in many cases, the main content area of a Web site.
Web user controls entail greater flexibility since they can be placed on a page either at design-time or programmatically at runtime, so developers can make decisions as to which controls to load based on user input, authentication, or other dynamic information.
In addition to their reusability and flexibility, Web user controls can take advantage of the ASP.NET caching engine to implement what is referred to as “fragment caching.” Essentially, fragment caching allows a Web developer to control the HTML caching of individual controls on a page, including setting the duration and caching various versions of the control based on query string, control properties, browser type, and even custom strings. The ability to cache only portions of pages is powerful since a portion of a page will often access relatively static data stored in a relational database or accessed through an XML Web service, while other portions of the same page manipulate dynamic data. For these static portions, creating a Web user control and setting caching options greatly reduces the number of database round trips, thereby increasing performance. In most instances, using ASP.NET caching judiciously is the single biggest performance improvement that can be made.
For all of these reasons, many developers have opted to create ASP.NET applications that revolve around Web user controls by creating their own framework or page template class that simply loads and displays various Web user controls to make up the entire site.
The anatomy of a Web user control
In ASP.NET, Web user controls are typically created by adding a new Web user control item to a project in VS.NET. Doing so adds a file with an ASCX extension to the site that contains the HTML, along with a codebehind file in your application’s language. By default, the ASCX file is empty and can be edited using the toolbox or by adding HTML directly in the HTML pane in the designer. In this way, Web user controls are no different from standard Web forms implemented as ASPX pages.
Web user controls can be placed on pages simply by dragging and dropping the ASCX file from the Solution Explorer onto a page, creating an element that refers to the page. They can also be loaded dynamically on a page (or another Web user control) using the LoadControl method like this (VB):
Dim c As Control
c = Me.LoadControl("~\uc\menu.ascx")
In this case, the control is first loaded and then added to the Controls collection of the HTML form on the current page, referenced as myForm.
The ~ in the above snippet is shorthand syntax for the Web application’s root directory and can be used in server-side code in ASP.NET.
As with .ASPX pages, the codebehind file created by VS.NET contains a .NET class definition that enables developers to encapsulate the functionality of the control using custom methods, fields, properties, and events. In fact, the class in the codebehind file is inherited from the same class hierarchy as a regular ASPX class file, as shown in Figure A.
|ASP.NET class hierarchies|
The figure shows the Web user control class structure on the left and the Web forms structure on the right. Note that both the UserControl and Page class ultimately inherit from the Control class, so developers can use the same programming model when implementing both—for example, by creating handlers for the Load, Init, PreRender, Render, and Unload events.
Of course, behind the scenes, ASP.NET must make allowances to handle Web user controls. When a page with the ASPX extension is encountered by the ASP.NET runtime, it is processed by an HTTP handler (the PageHandlerFactory class). While processing the page, if a Web user control is encountered, an instance of the control’s class is created dynamically. (Actually, a derived class is created, since the control’s class is marked as abstract in the codebehind file.) The instance is then loaded and processed, and its resulting HTML is added to the stream of HTML to be sent to the browser.
A user control driven site
To demonstrate how Web user controls can maximize reuse, flexibility, and performance in a Web application, I’ll take you through the architecture of an example site that uses Web user Controls extensively.
To begin, the Web developer creates the layout of the site. This is typically accomplished using an HTML table, as shown in Listing A. Notice that the layout of the site consists of a table with cells identified as banner, left, center, and right. The important point to note is that the table cell elements <TD> are marked with the runat=server attribute. This attribute allows the cells to be manipulated programmatically in the codebehind file using the HtmlTableCell object. This basic layout then becomes the template for each page in the site. Although space constraints prohibit a more thorough discussion, many developers incorporate this template into a base class from which they can inherit each of the other pages in the site.
After setting up the site layout, the developer creates Web user controls that can be loaded into the table cells identified in Listing A. To be able to change the layout dynamically, the developer can implement a custom XML file that specifies in which table cells to load the various controls. Both the positioning and content in the table cells can then be changed without modifying the pages or writing extra code. An example of such a configuration file is shown in Listing B.
To read the file and load its contents, the developer could simply parse the XML in the codebehind class for the pages that use the template (or in a base class that implements the template). However, this would be less efficient than reading the XML file only when it changed by implementing a configuration section handler class.
Although the details are beyond the scope of this article, a configuration section handler can parse the XML file in Listing B as shown in Listing C and place the contents in an ArrayList that the section handler class exposes in a shared property. (See this article for a description of how to create a configuration section handler.) You’ll notice that the ArrayList contains custom ControlPointer objects that indicate the path to the Web user control and in which pane it should be loaded.
Finally, the collection of the ControlPointer objects can be iterated and loaded in the codebehind class (or the base class) that implements the page layout. As shown in Listing D, the page can call a private _arrangePage method to read the Controls collection of the Utils class (the configuration section handler class) and then load the Web user controls dynamically.
Each of the controls can be individually cached using the ASP.NET caching engine. The simplest way to do this is to place the OutputCache directive at the top of the ASCX page as shown in Listing E.
The Web user control will be cached as pure HTML, rather than reprocessed each time it is loaded. As mentioned previously, this can speed performance when the control accesses a database. Caching can even be more important when the control accesses an XML Web service, since you often won't have any control over the response time and availability of the Web service. Caching enables you to minimize your dependence on the provider of the service.
ASP.NET Web user controls can be used as an effectively strategy for reuse, increasing the flexibility and maintainability of Web sites. They should be a key component of any Web developer’s toolbox.