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.
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.
|