Creating Web Custom Controls With ASP.Net 1.1 - Part III - Using Metadata Attributes
Metadata attributes are attributes that are assigned to classes, variables, structures, enums, procedures, functions and properties (and no doubt others that I haven’t come across). They are usually used to inform the IDE how to handle the objects that the metadata attributes are assigned to. They can also be used by run-time code to handle additional information.
Metadata attributes are defined on items with an < > combination before the declaration of the Item. For example, defining Category and Description attributes to a Text property would look like this:
< _
Category("Appearance"), _
Description("The value rendered for the control.") _
> _
Property [Text]() As String
Get
' Return the stored value
Return CType (ViewState.Item("Text"), String )
End Get
Set ( ByVal Value As String )
' Store the new value
ViewState.Item("Text") = Value
End Set
End Property
I am going to look at a few of the common metadata attributes used in creating web custom controls. Some are self explanatory, while others have some very funny behaviors.
This is definitely not a complete list of metadata attributes that you will come across when creating web custom controls. It is also not an exhaustive description of the functionality of the attributes. Many of them, such as Designers and ControlBuilders, do a lot more than what I describe. I am only covering the most common attributes that I use, and a description of my normal usage of them.
DefaultValue Attribute
To begin with, lets look at the DefaultValue metadata attribute which is used for property definitions. As the name suggests, this metadata attribute defines the default value of the property.
This attribute doesn’t actually affect anything returned from the property, but it does two things that affect a controls behavior in the IDE. Firstly, this attribute lets the design-time property grid of the IDE know what the default value is. You will notice that property values in the property grid are bold where they are not the default value. The second thing it does is determine how the xml of the aspx page is to be built. If the property value is the specified default value, the data for the property is not written to the xml of the control at design-time.
I have noticed a few interesting things about the DefaultValue metadata attribute. String properties don’t need to specify DefaultValue if the default value is an empty string. This seems to be the default value of DefaultValue (and no I am not trying to be funny). If a non-empty string is the value set for DefaultValue, an empty property value will be persisted in the page xml as a space.
Boolean properties also have interesting behavior. If an empty string is specified in the xml data for a Boolean property (such as <CC1:ImageButton Selected=”“>) then the value stored in ViewState is a Boolean with the value of True. If the property attribute isn’t defined at all in the xml, then the ViewState value is Nothing, in which case my code will return the default value, usually being False. This is different to normal Boolean behavior. For example, if you run code like this:
Dim bTest As Boolean = System.Boolean.Parse(vbNullString)
or this:
Dim bTest As Boolean = System.Boolean.Parse("")
then an exception will be raised. Our good friends at Microsoft are obviously doing a little extra behind the scenes to capture this issue. They must be identifying that the xml attribute is defined and converting an empty string to “True” when building the ViewStates internal value.
Enum properties also have a bit of an interesting behavior. You need to specify the type of object as well as the string representation of the value. If you don’t specify the type, it won’t see the string value defined in DefaultValue as the same as the Enum typed value, therefore the property grid will display it as bold and it will still be persisted in the xml. If the attribute isn’t defined in the xml, like the Boolean case, the ViewState value is Nothing. If an empty string is defined as the attribute value for the property, an error will occur because an empty string can’t be converted to Enum.
Category Attribute
The Category attribute value is assigned to properties definitions. It will determine which Category the property is put into when the controls properties are viewed by groups in the IDE’s property grid. By default, if this attribute isn’t defined, Misc will be used.
Description Attribute
The Description attribute is another useful one. It simply allows you to define a description for an item, such as a class or a property. This is especially useful for properties because the Description attribute value will be displayed in the IDE’s property grid. This is a good opportunity to inform the implementer of your control what the property purpose is.
The Description attribute is also useful for people in the VB.Net world who use VBCommenter. Where the Description attribute has a value, and a new VBCommenter block is created, it will take the Description attribute value and put it in the summary tag for you.
ToolboxData Attribute
The ToolboxData attribute is assigned to a class and is where you define how the IDE will write the aspx xml data for the control when it is added from the toolbox. Where assemblies and their Namespaces are registered with an aspx page, one of the property values of the registration tag is the TagPrefix value that is associated with the assembly. The TagPrefix default value is cc1. This value should be picked up as a variable along with the rest of the toolbox data you specify in this attribute.
My ToolboxData attribute values normally follow this format:
ToolboxData("<{0}:ImageButton runat=server></{0}:ImageButton>")
The {0} part of the value is where the TagPrefix value from the page registration is used. To not include this would be very dangerous. For the other part of the tag name, I have never had a reason to use a different value other than the class name of my control. The TagPrefix value ensures that there are no naming collisions. For example, I am creating this ImageButton control, but there is already the intrinsic ASP.Net ImageButton control. The ASP.Net control is declared as ASP:ImageButton and with a TagPrefix value of cc1 my ImageButton control will be declared as cc1:ImageButton. By default I also need the runat= server declaration put in.
Designer Attribute
The Designer attribute is assigned to a class and allows you to change the HTML data that is rendered for the control at design-time. You may want to render the control in a different way at design-time because of the value of the controls properties or because information isn’t available at design-time as it would be at run-time.
The Repeater control is a good example of this. When a Repeater and its ItemTemplate is declared in a page, the rendered design-time view displays five items databound to it. No data is actually bound to it, so a Designer is used to bind some example data to the control.
Designers are not always required however. My lightweight label control in my first article didn’t need a Designer because the default rendering of the control was appropriate. When my label control doesn’t have a value, nothing is rendered by the control itself as it doesn’t render any tags. In this case, the IDE by default will render the equivalent of “[” & control.id & “]” which is fine as far as my label control is concerned.
I will provide an example of Designer support in a later article.
ControlBuilder Attribute
The ControlBuilder attribute is assigned to a class and is used to help the IDE understand how to interpret xml in an aspx file into control object types. For example, I may have a toolbar control that can hold one or more of my ImageButton controls. When the xml of the aspx file is read, the IDE doesn’t establish a relationship of child control definitions and the actual class type of the child control. If my toolbar is defined as this:
<CC1:Toolbar id="tbrTest" runat="server">
<CC1:ImageButton id="btnTest1" runat="server">
<CC1:ImageButton id="btnTest2" runat="server">
<CC1:ImageButton id="btnTest3" runat="server">
</CC1:Toolbar>
then the toolbar control doesn’t know that the child controls are of the type ImageButton. A ControlBuilder will inform the Toolbar control that a tag that contains the name ImageButton is the ImageButton class. Now that the ControlBuilder is informing the Toolbar control what the correct type of child controls are being created for it, it can take any appropriate action.
I usually use ControlBuilders because I only want certain types of controls to be allowed as child controls. As the ControlBuilder will tell my control what the type of child control is, if it doesn’t like it, it can remove it from its Controls collection. If my control is only told about literal controls, it won’t know whether to allow the child control or not.
I will provide an example of ControlBuilder support in a later article.
PersistenceMode Attribute
The PersistenceMode attribute is assigned to a property and marks how the property value is to be persisted in its parent controls declaration in the xml of the aspx file. Lets take the example of a generic control that exposes a Text property.
A value of Attribute will result in:
<CC1:MyControl id="tbrTest" runat="server" text="my&value">
</CC1:MyControl>
A value of InnerProperty will result in:
<CC1:MyControl id="tbrTest" runat="server">
<Text>my&value</Text>
</CC1:MyControl>
A value of InnerDefaultProperty will result in:
<CC1:MyControl id="tbrTest" runat="server">my&value</CC1:MyControl>
A value of EncodedInnerDefaultProperty will result in:
<CC1:MyControl id="tbrTest" runat= "server">my&value</CC1:MyControl>
In my control development, I have always put my property values as Attribute (which is the default) as I normally support child controls which would be the nested tag definitions.
ParseChildren Attribute
The ParseChildren attribute is assigned to a class and will define whether child xml nodes in the aspx file are parsed as child controls or are interpreted as property values. If I had a property that was defined with a PersistenceMode of InnerProperty, then ParseChildren attribute would have to be False. Following the example above, if ParseChildren wasn’t False, a control of the type Text would attempt to be created and this would fail.
I always declare ParseChildren as True as I want nested tags of the xml data to be parsed as child controls. This means that the properties of my controls must be persisted as attribute values in the xml of the aspx page. The default value of ParseChildren is False.
PersistChildren Attribute
The PersistChildren attribute is a very funny one. I have come across many problems getting child controls to be persisted in the xml of the apx page, especially when the top control is changed through the IDE’s property grid. I can’t give a definitive answer on how this one should be declared as I have needed to do different things for different controls. I will however cover an example of this attribute in a future article.
ToolboxItem Attribute
The ToolboxItem attribute is assigned to a class. It is a True/False value that determines whether the control can be added to the Toolbox of the IDE. I declare this as False for child controls that I don’t want to be used as top level controls. For example, if I was creating a Table control, I would allow the Table class to be hosted in the toolbox, but not the cell control. This attribute value is True by default.