Provide Rich Functionality With Server Controls
Get better functionality by creating ASP.NET server controls rather than user controls.
by Chris Kinsman


Technology Toolbox: C#, ASP.NET

Some would argue that ASP.NET's coolest feature is the introduction of server controls, an event analogous to the introduction of VBXs in the early days of Visual Basic. Server controls offer a way to encapsulate rich functionality in an easily reusable and redistributable form. In this column, I'll show you how to build several different server controls.

ASP.NET offers two control creation options: user controls and server controls. The most commonly used technique, user controls, doesn't differ that much from creating a page. (In fact, user controls were called pagelets in the early betas.) User controls are easy to create and use in your application quickly, but they lack support for design-time features and are scoped to a single application.

Unlike user controls, server controls don't use the page model for creation, making them a bit harder to author. You can't simply drag and drop various controls onto a design surface to create a server control. Instead, you must create server controls' user interface entirely in code. This isn't impossible to do, but it can be a tough transition if you're used to designing things visually. However, server controls' advantages outweigh these issues. Server controls offer a highly customizable design-time experience, as well as an effective distribution model that allows multiple application scopes to use a single assembly.

Server controls derive from one of two built-in base classes: System.Web.UI.Control or System.Web.UI.WebControls.WebControl. The Control class is the base class upon which all server controls are built. It doesn't implement any user-interface-specific features, so it's the ideal base class for a control that doesn't need to render any information into the page, or needs to render only simple information. The WebControl class derives from the Control class and adds several UI features for handling styles, colors, and the like. Use Control as your base class if you're creating a nonvisual server control. Use WebControl as your base class if your control contains any user interface. In most instances, you'll probably start with the WebControl class as your base class.

Figure 1 Create a Customizable Server Control.

A server control's ultimate mission in life is to render HTML to the browser. To this end, the Control base class provides a Render() method for implementers to override. ASP.NET traverses the page's Controls collection and calls the Render() method on each control when it's finished running the code in the page and is ready to return the page's HTML to the client. Your control determines what will be sent to the user's browser by overriding the Render() method.

As an example, create a server control that inserts a time stamp into a page (see Figure 1). Start Visual Studio .NET and create a new Web Control Library project. This creates the project and starts you with the outline of a class that derives from WebControl. Your newly created TimeStamp server control includes a default Text property and a Render() method that writes the Text property's contents into the page. Alter the definition of the internal member, Text, to initialize it using DateTime.Now.ToString() (see Listing 1). Compile the code and copy the resulting assembly into a Web project's bin directory. Bring up a Web page, select Customize Toolbox, and browse to your newly created assembly. This places the control into the toolbox, from where you can drop it onto a page. If you drop the control onto a Web form instead of a user control's usual gray box, a date/time stamp displays.

Alter the Control's Behavior
Now that you have a server control, you need to be able to configure it so you can alter its behavior. Server controls support properties and methods like any other object. However, when you derive from Control or WebControl, ASP.NET performs some special property handling for you behind the scenes. When ASP.NET instantiates a server control from within a page, it maps any attributes included in the tag to properties within the representative server control class. This is important, because it allows you to create an ASP.NET page and alter a server control's behavior without writing any code. In the preceding example, this means you could have used an HTML tag such as this:

<vsm:timestamp text=
   "Visual Studio Magazine" 
   runat="server" />

This tag would have overwritten your time stamp with the value "Visual Studio Magazine."

If you're curious why the tag would overwrite the time stamp and not the other way around, ASP.NET sets the value of the variable "text" to DateTime.Now() when it instantiates the class that represents the control. After the class has been instantiated, ASP.NET then runs through the attributes on the tags and attempts to match them to properties on the class. If there's a match, ASP.NET calls the property setter to set the value, overwriting what might have been placed there, while initializing variables or even the class's constructor.

You might be wondering how VS.NET knows what tags to drop into the Web form. If you look right above the class definition, you'll see VS.NET placed a ToolboxData attribute on the class when it created the class file. This attribute defines what text VS.NET should drop into the page by default. Make sure to delete your control from the toolbox and re-add it back if you change this attribute. The toolbox caches this information instead of reading it from the control each time. You need to re-add the control to the toolbox to get changes to take effect. This attribute is optional; the base class has all the information required to produce the tag to insert if you remove it. You can use the TagPrefix attribute to control the prefix used in the @Register directive the VS.NET designer inserts into the ASPX file.

You can use HtmlTextWriter's Write() method to render your control's runtime content, but there's a better option. HtmlTextWriter offers several other methods for rendering HTML, with corresponding advantages. The RenderBeginTag() and RenderEndTag() methods provide a stack-based method for rendering HTML. You specify a starting tag with RenderBeginTag(). You call RenderEndTag() without any arguments when it's time to close the tag, and it closes the most recently opened tag automatically. While rendering, these methods format the resulting HTML automatically into multiple lines with appropriate indenting. These methods have the added advantage of rendering styles added to the tags automatically in uplevel/downlevel fashion based on browser support for HTML 4.0 or HTML 3.2.

RenderBeginTag takes the tag's text, or you can use the HtmlTextWriterTag enumeration to specify the tag to render. You can add attributes to a tag by calling the AddAttribute() method prior to RenderBeginTag(). You can specify the attribute's name using a string or the HtmlTextWriterAttribute enumeration. You add styles to a tag by calling the AddStyleAttribute() method prior to RenderBeginTag(). Styles also have an HtmlTextWriterStyle enumeration, or you can use a string. Create another server control and try using HtmlTextWriter to render a table with attributes and styles (see Listing 2).

Build Up the Control's Output
An alternative to using HtmlTextWriter is to build up your server control's output using control composition. The base Web Control class contains a Controls collection to which you can add controls to render. ASP.NET iterates the Controls collection automatically and calls the Render() methods on each control inside the collection. Override the CreateChildControls() method to add the child controls to the Control collection. The base class calls this method whenever it needs to create the child controls.

This is all you need to make the control work at run time, but you need to take one additional step to make the control appear properly at design time. Add a call to EnsureChildControls() in the control's constructor. The EnsureChildControls method checks to see if the Controls collection has been created yet. If not, it calls CreateChildControls automatically (see Listing 3 for an example of the SimpleTable from Listing 2 rendered using composition).

You can place the server control in the Global Assembly Cache (GAC) to reuse the server control in multiple projects without placing it in every application's bin directory. You must sign an assembly to be placed in the GAC. You can create a key using the sn (StrongName) utility using a command line like this:

sn -k MyKey.snk

Once you create the key, update your AssemblyInfo.cs file to point to the public key file like this:

[assembly: 
   AssemblyKeyFile(@"..\..\MyKey.snk")]

Note how the path starts with two relative parent directory references. This assumes that MyKey.snk is in the same directory as your CSPROJ file, and that your build directory is the default bin\debug. When you sign the assembly, you can add it to the GAC using the gacutil utility like this:

Gacutil -i ServerControl.dll

Once the assembly is in the GAC, you can import it into your application by adding these lines to the application's web.config file:

<assemblies>
   <add assembly="ServerControls, 
   Version=1.0.X.X, Culture=neutral, 
   PublicKeyToken=XXXXXXXXXX"/>
</assemblies>

You need to fill in the PublicKeyToken attribute with the public key you used to sign the server control, and the Version attribute with the version of the server control. This causes ASP.NET to load the assembly from the GAC.

As you've seen, building a server control offers significant advantages over creating a user control. Take a look at the user controls you're developing, pick a suitable candidate, and create your first server control.


About the Author
Chris Kinsman is a principal with Deep Training LLC, a training company that offers training by developers. Chris is also the president of Vergent Software, an Internet/.NET consulting firm based in Redmond, Wash. He's the coauthor of several books, including Visual Basic .NET Developer's Guide to ASP.NET, XML, and ADO.NET (Addison-Wesley). Reach him at ckinsman@vergentsoftware.com.