I'm a component developer. Always have been. I have a passion for creating reusable units of code - especially in the form of controls. Back in the day, I was an avid ActiveX control developer and if I say so, some of my controls have been very successful and well received...particularly a dropdown ActiveX calendar control (more on that another day).
ASP.NET developers are (or should be) familiar with two types of controls: UserControl and WebControl.
UserControls are in essence HTML with code-behind that are referenced via a URL on the containing page. They can be loaded dynamicaly (via Page.LoadControl(”...ascx”)). A UserControl provides visual HTML reuse. Perhaps some good examples of when to use a UserControl would be a custom link, a data entry form, or a link/menu bar. I am probably not alone when I say that I think they're really cool.
WebControls, on the other hand, are quite a bit more complicated - but more powerful as well. Unlike the UserControl, the WebControl doesn't have a nice, pretty HTML front-end. All of the rendering of the control happens server-side in the WebControl's Render() method. To create a WebControl basically all you need to do is derive from WebControl and override the Render() method and output your custom HTML as in the following example:
namespace Devstone.Web.TestControls {
public sealed class MyWebControl : WebControl {
protected override void Render(HtmlTextWriter output) {
output.Write(“Custom control text“);
}
}
}
Then in order to use this control on a page you do the following on the .aspx page:
<%@ Register tagPrefix=“devstone“ Namespace=“Devstone.Web.TestControls“ Assembly=“Devstone.Web.TestControls“ %>
<body>
<devstone:MyWebControl runat=server />
</body>
That's pretty slick...but you can do oh so much more than this.
If your control has properties (as most in fact do), you can reference the controls as child Xml elements to the tag on the aspx page or as attributes. Personally, I prefer the attributes approach. However, suppose you want to have a control that has child elements that you want to be child controls - not properties. The trick is to add the ParseChildrenAttribute to your WebControl with a value of false. When you do this, each element will be interpreted by a built-in parser and handed off to a control builder class that you designate with the ControlBuilderAttribute. Upon parsing the element, ASP.NET will call into your WebControl's AddParsedSubObject() method, passing in the new control instance.
A word of caution, however. The ASP.NET parser is kinda stupid (or maybe it's brilliant and I can't fathom it's logic). It will call AddParsedSubObject(), passing in LiteralControl instances for each line between the child elements and you probably want to ignore those, only looking for your own custom tags. Here's the updated control now:
[ParseChildren(false)]
[ControlBuilder(typeof(MyWebControlBuilder))]
public sealed class MyWebControl : WebControl {
protected override void AddParsedSubObject(object obj) {
// ignore all child controls except the ones we care about
MyCompanyLink link = obj as MyCompanyLink;
if ( null != link )
this.Controls.Add(link);
}
protected override void Render(HtmlTextWriter output) {
// render all child controls
// this is kinda brain-dead, but it gets the point across
foreach ( Control ctl in this.Controls )
ctl.RenderControl(output);
output.Write(“Custom control text“);
}
}
internal sealed class MyWebControlBuilder : ControlBuilder {
public override Type GetChildControlType(string tagName, IDictionary attributes) {
switch ( tagName.ToUpper() ) {
case “COMPANYLINK“:
return typeof(MyCompanyLink);
default:
return null;
}
}
}
At this point, all I have to do is define the MyCompanyLink control, derive from a WebControl and override it's Render() method. The .aspx page might look like this:
<devstone:MyWebControl runat=server>
<companyLink />
</devstone:MyWebControl>
Granted, this is pretty simplistic. What if we're creating a control that has more than one level of nested children controls? An example of this might be a menu or a toolbar control. Well, what we've talked about so far applies, but it's not enough. By default the ASP.NET parser will only parse down one level for child controls. If you go to the trouble of creating a child control such as a toolbar and add buttons, the buttons will be interpreted as properties of the toolbar. Ok, so you can add the ParseChildrenAttribute to the child control, but it's ControlBuilder will never be invoked.
After a bit of digging I found the solution and it's pretty simple: Add the PersistChildrenAttribute to the root object.
[ParseChildren(false)]
[PersistChildren(true)]
[ControlBuilder(typeof(MyWebControlBuilder))]
public sealed class MyWebControl : WebControl { ... }
Now, if you had, say a toolbar child control you could do the following to create a custom button arrangement:
<devstone:MyWebControl runat=server>
<toolBar>
<cut />
<copy />
<paste />
</toolBar>
</devstone:MyWebControl>
I am, in fact, in the process of creating a control that does just this and when it's ready I'll post it up here for public consumption. Enjoy!