Mea Culpa
The first time I wrote this article, I stated, among other things, that Attributes are parsed alphabetically.
In fact, the gist of what I said was that it parses in the following order:
- properties of type ITemplate
- Attributes in alphabetical order
- Nested properties in alphabetical order.
I was wrong. Mea Culpa.
How ControlBuilder parses the Tags
The above is half correct. If you study the live investigation I put together to check this, you will notice is that it does it the following way:
- properties of type ITemplate
- Attributes in the order they were declared in the *.aspx
- Nested properties in the order they were declared in the *.aspx
In other words:
<MyControl ListControlType="DropDown" ButtonText="Submit" Angle="30">
<Items>
<ListItem>One</ListItem>
<ListItem>Two</ListItem>
<ListItem>Three</ListItem>
</Items>
<TemplateMain>
...template that contains a DropDownList where the above
...ListItems will be passed into
...and a Button, which will end up being given Text 'submit'
</TemplateMain>
</MyControl>
Will be parsed in the following order:
- TemplateMain
- Attributes:
- ListControlType
- ButttonText
- Angle
- Nested Properties:
- Items
Why is this Important?
The reason knowing how ControlBuilder goes about parsing declarative code and setting the properties of newly instantiated controls is its arch-enemy EnsureChildControls();
Consider the following tag:
<XACT:MyControl runat="server" NameLabel="Name:" Layout="Left" PasswordLabel="Password:" DomainControlType="DropDown">
<Items>
<ListItem>One</ListItem>
<ListItem>Two</ListItem>
<ListItem>Three</ListItem>
</Items>
</XACT:MyControl>
wired up as follows:
class MyControl : WebControl {
public NameLabel {
get {
EnsureChildControls();
return _NameLabel.Text;
}
set {
EnsureChildControls();
_NameLabel.Text = value;
}
}
public Layout {
get {
object o = ViewState["Layout"];
return (o!=null)(Layout)o:Layout.Left;
}
set {
ViewState["Layout"]=value;
ChildControlsCreated=false;
}
}
public NameLabel {
get {
EnsureChildControls();
return _PasswordLabel.Text;
}
set {
EnsureChildControls();
_PasswordLabel.Text = value;
}
}
public ListControlType DomainControlType {
get {
object o = ViewState["DCType"];
return (o!=null)?(ListControlType)o:ListControlType.ListBox;
}
set {
ViewState["DCType"]=value;
}
}
...etc....
}
With such a simplistic wiring behind the scenes, how the controls are parsed will greatly affect the amount of CPU needed to render the control.
Notice what happens:
- the first attribute set will be NameLabel, which because it wraps an EnsureChildControls(), causes the child controls to be created (1).
- Then the next attribute is parsed -- Layout -- which resets ChildControlsCreated.
- The the third control is parsed, PasswordLabel, which causes the child controls to be recreated a second time (2).
- Then DomainControlType resets ChildControlsCreated...
- Finally PreRender will invoke EnsureChildControls() (3).
In other words, the control rendered its child controls 3 times - and we didn't even address DataBind(), which would have maybe done it a 4th time!
Bright Sparks...and Red Herrings...
Some bright spark out there is thinking that the way to work around this is to somehow enforce that properties that reset ChildControlsCreated, such as Layout and ControlType attributes, are parsed first will think of maybe doing the following:
public ListControlType DomainControlType {
get {
object o = ViewState["DCType"];
return (o!=null)?(ListControlType)o:ListControlType.ListBox;
}
set {
if (ChildControlsCreated){
throw new System.Exception(
"Cannot set DomainControlType property after ChildControlsCreated.<br/>"+
"Ensure it is one of your first attributes used:<br/>" +
@"<MyControl DomainControlType=\"DropDownList\" Layout=\"Left\" NameLabel=\"...\" .../<"
);
}
ViewState["DCType"]=value;
}
}
In order that the user is forced to always put the attribute first:
<MyControl runat="server" DomainControlType="DropDownList" NameLabel="Nom:" .... />
I applaud your creativity....and then let the penny drop...see it? No? One more effort...Yes! I can see you see the problem: although you can force the user to put the attribute up front, you have no control on how the IDE -- ie Visual Studio -- is going to serialize attributes of your control when you drag/drop a copy of this control from the Toolbox onto the workspace. It may serialize it (alphabetically???) and put the attribute later. Which would cause an immediate exception due to the throw; inserted in the above code.
The Solution
The solution to these types of problems is studied in great detail in my other article (which it too is going to need a rewrite), on CompositeControl property wiring.