ControlFreak
LANGUAGES:
VB.NET | C#
ASP.NET
VERSIONS: 1+
Custom Control Attributes
Add a Layer of Professional Polish to Your Control Creations
by Taking Advantage of Design-time Attributes
By Steve C. Orr
Some hardcore developers prefer to write their code with Notepad
rather than deal with the sometimes sluggish performance of Visual Studio. However,
the majority of .NET developers clearly appreciate the powerful interactive
design-time development experience that Visual Studio can provide. To parlay this
rich experience into the realm of custom Web controls, control developers
should familiarize themselves with the key attributes that allow Visual Studio
to do such impressive work.
Control Attributes
The code listed in Figure 1A should look rather ordinary
to developers who have created custom Web controls. The first line of code
specifies a design-time attribute that will be applied to the control
definition that follows. As you can see, attributes are delimited by angle
brackets in VB.NET. In C#, attributes are instead delimited by square brackets,
as is shown in Figure 1B.
<ToolboxData("<{0}:AttCtrl runat=server></{0}:AttCtrl>")>
_
Public Class AttCtrl
Inherits WebControl
'TODO: implement
control internals...
End Class
Figure 1A: Here,
the ToolboxData attribute decorates this control s class definition.
[ToolboxData("<{0}:AttCtrl runat=server></{0}:AttCtrl>")]
public class AttCtrl : WebControl
{
//TODO: implement control
internals...
}
Figure 1B: In
contrast to VB.NET, attributes written in C# are delimited by square brackets
and do not require line-continuation characters.
To provide a consistent development experience, the
ToolboxData attribute must be attached to every custom Web control s class
definition. The attribute s only parameter specifies the string that gets
rendered to a Web form s ASPX definition each time application developers drag
the control onto a page from the Visual Studio toolbox.
Additionally, there are a few other common control
attributes often found decorating control definitions. For example, each
control may optionally define one default event and property. Visual Studio
keys off these attributes to provide an optimized development experience for
the developers consuming the control. These attributes are summarized in Figure
2, along with the ToolboxBitmap attribute, which can be used to customize the
control s Visual Studio toolbox icon.
|
Attribute Name
|
Description
|
|
DefaultEvent
|
Defines the control s most commonly consumed
programmatic event (if any).
|
|
DefaultProperty
|
Defines the control s most commonly used design-time
property (if any).
|
|
ToolboxData
|
Specifies the HTML tag that is generated when a new
instance of the control is dragged from the toolbox onto a Web form.
|
|
ToolboxBitmap
|
Provided by the System.Drawing namespace as a way to
specify the icon that should represent the control within the Visual Studio
toolbox.
|
Figure 2: Common
control attributes.
Niceties like this aren t strictly required in order to
create a functional, reusable control that other developers find valuable. However,
professional touches like these are what separates amateurs from professionals.
Property Attributes
Design-time attributes aren t limited to top-level control
definitions. Each property exposed from within the control definition may also
benefit from specially tuned design-time attributes. Figure 3 summarizes the
design-time attributes most commonly applied to control properties.
|
Attribute Name
|
Description
|
|
Description
|
Used for describing the associated property.
|
|
Category
|
Specifies the category under which the associated
property should appear in the Properties window.
|
|
Bindable
|
Enables/disables data-binding capabilities for the
specified property.
|
|
Localizable
|
Enables/disables localization for the associated property.
|
|
DefaultValue
|
Specifies the property value that should be displayed
when a custom value has not yet been set.
|
|
ReadOnly
|
Toggles the editability of the related property.
|
|
Browsable
|
When set to False, the associated property will not be
displayed in the Properties window.
|
|
Obsolete
|
Provides a non-disruptive notification to consuming
application developers that use of this property has been deprecated.
|
|
RefreshProperties
|
Commands the Properties window to fully refresh itself
whenever the associated property value has changed.
|
|
MergableProperty
|
Allows the property to be changed in bulk when multiple
instances of the control are selected.
|
|
UrlProperty
|
Enables invocation of the standard address chooser popup
dialog box, which provides a more user friendly Web address selection
experience.
|
|
NotifyParentProperty
|
Child properties can be configured to notify parent
properties of any value modifications.
|
|
TypeConverter
|
Allows run-time conversion of value types.
|
|
DesignerSerializationVisibility
|
Configures the type of persistence that is applied upon
serialization to the ASPX source document.
|
Figure 3: Common control
property attributes.
The Description attribute is used to provide additional
details about the intended use of the related property. The syntax is demonstrated
in Figure 4.
<Description("This is the property description.") >
_
Property Text() As String
Get
'TODO...
End Get
Set(ByVal Value As
String)
'TODO...
End Set
End Property
Figure 4: The
Description attribute accepts a single string parameter.
When the property is selected in Visual Studio s Properties
window, the specified description is displayed at the bottom of that window. Figure
5 illustrates this concept.
Figure 5: The value supplied by the
Description attribute gets displayed at the bottom of the Properties window.
The Category attribute can be used to group related
properties together in the Properties window. Commonly used property categories
include Appearance, Behavior, and Data. For example, properties relating to
colors, styles, and fonts are typically grouped together under the Appearance
category. Because developers now habitually look for properties underneath
these common categories, it is wise to place your custom properties in these
categories, as well. However, there is nothing preventing the use of custom
categories when the need arises. For example, the UserName property listed in
Figure 6 will be grouped under a new custom category named User.
<Description("The full name of the user."), _
Category("User"), Bindable(True)> _
Property UserName() As String
Get
'TODO...
End Get
Set(ByVal Value As
String)
'TODO...
End Set
End Property
Figure 6: Multiple
attributes may be applied to a single property, as long as they are separated
by commas.
Figure 6 also demonstrates how multiple attributes may be
applied to a single property, as long as all attributes are delineated by
commas. It also should be noted that the entire attribute definition list
(along with the first line of the property definition) must be written as a
single logical line of code. This detail is inconsequential to C# developers,
but to VB.NET developers it means making copious use of the line continuation
character ( _ ) to keep code readable.
The Bindable attribute is used to specify that the
associated property is a suitable target for data binding. If this attribute is
excluded (or it is explicitly set to False), then the property will not be able
to be programmatically manipulated via standard data binding techniques.
Localizable is another Boolean attribute that may be
applied to control properties. If a property is decorated with this attribute
(and the value of True is supplied as its sole parameter), then that property
will participate normally in all localization-related activities. Otherwise,
the property will not be localized. For example, a property such as JobTitle
would likely make a good localization target, but attempts to localize a
CustomerID property value would typically be an unnecessary waste of resources.
The DefaultValue attribute can be used to inform the Properties
window what to display when the underlying property value has not yet been set.
The attribute s heavily overloaded parameter list provides great flexibility.
Some properties are meant to be referenced, but not
altered. Such properties can be decorated with the ReadOnly attribute, which
expects a single Boolean parameter. Setting the ReadOnly design-time attribute
to True will prevent the associated property from being modifiable in the Properties
window at design time. However, it should be noted that this attribute has no
effect on the run-time behavior of the property.
Some properties are relevant only under specific
circumstances. For example, a HitCounter property may provide valuable metric
data at run time, but at design time its value is moot. Therefore, it may be
best to hide the property at design time by setting its Browsable attribute to
False. This will prevent the property from appearing in the Properties window
at design time, even though the property will be in place and fully functional
at run time.
Soothe Growing Pains
As applications evolve, business rules change and
underlying data structures often must be transformed to accommodate current
needs. This may necessitate that preexisting control properties be added,
deleted, or changed. Deleting an obsolete control property might sound harmless
at first, but consider what happens when the new version of the control is
deployed. Potentially, countless unsuspecting application developers may no
longer be able to compile because their existing code references a property
that no longer exists. Suddenly, a simple upgrade turns into a show-stopping
integration incident that distracts everyone from their usual duties.
A better approach is to leave the old field as-is for a
while, and provide a new property to support the new requirements. The old
property then can be decorated with the Obsolete attribute, and upgrade
instructions can be supplied as its parameter (see Figure 7). This will allow
everybody s applications to continue compiling and running normally, even
though they ll get nagging compile-time warnings as long as their code
continues to reference the deprecated property. As time permits, each application
developer will eventually modify their code to comply with the new standard,
and the obsolete warnings will go away.
<Obsolete("Please use the FirstName and LastName
fields")> _
Property UserName() As String
Get
Return _userName
End Get
Set(ByVal Value As
String)
_userName = Value
End Set
End Property
Figure 7: Using
the Obsolete attribute, deprecated properties can be eased out of production
gradually to avoid jarring disruptions.
Advanced Tips & Tricks
Sometimes a control s properties are related to each other
in deep and complex ways. Changing just a single property can trigger a cascade
of events that may affect the values of many other properties. After a
potentially complicated sequence of events like this it can be difficult to
determine if the Properties window accurately reflects the current internal
state of the control. In such circumstances, the RefreshProperties attribute
can come in handy (see Figure 8). Any time the value changes of a property that s
been decorated with this attribute, the Properties window will refresh. Therefore,
decorating each relevant property with this attribute should eliminate any
concerns about the Properties window getting out of sync.
<RefreshProperties(RefreshProperties.All), _
MergableProperty(True)> _
Property MyCustomProperty() As String
.
.
.
End Property
Figure 8: Example
use of the RefreshProperties and MergableProperty attributes.
By default, if an application developer selects three
instances of your custom control and tries to set a property value for all of
them at once, the attempt will fail. For complex scenarios where control
instances may share interdependent state, this is a reasonable default. However,
for simple scenarios (like setting a common background color for all selected
controls) it is good practice to allow the control consumer to edit as freely
as they desire. The solution is to decorate the custom property with the
MergableProperty attribute (again, see Figure 8). This will successfully allow
changes to this property s value to propagate across each selected instance of
the control.
Most property values can be displayed and manipulated
directly in the Properties window. However, some kinds of data (like a series
of charting data points) require more complex interactions than the simple
property grid can support. Even relatively simple data entry chores can
sometimes benefit from a well designed popup dialog box to help speed things
along. For example, properties intended to hold URLs can benefit from the
UrlProperty attribute. When a property decorated with this attribute is
selected in the Properties window at design time, an ellipsis (...) button will
appear. When clicked, the familiar dialog box shown in Figure 9 appears.
Figure 9: A single line of code is
all it takes to invoke this common dialog box.
Conclusion
When it comes to creating and distributing custom Web
controls, a little polish can go a long way. By decorating a control s classes
and properties with design-time attributes, usability can be improved by leaps
and bounds. Professional touches like these will make your control creations
easier to use, resulting in saved time and money for everyone involved. It just
makes good business sense.
Steve C. Orr is an
ASPInsider, MCSD, Certified ScrumMaster, Microsoft MVP in ASP.NET, and author
of Beginning ASP.NET 2.0 AJAX by Wrox. He s
been developing software solutions for leading companies in the Seattle area
for more than a decade. When he s not busy designing software systems or
writing about them, he often can be found loitering at local user groups and
habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:Steve@SteveOrr.net.