Default Inner ITemplate Property is NEVER NULL in a Web Control

Randomly came across an interesting problem today.  I’m building out a a template web control that allows you define your template in one of two ways – either as an ITemplate property that can be defined directly in your markup or by referring an .ascx file.

The code for class definition looks something like this:

[ParseChildren(true, “ContentTemplate”)]
[DefaultProperty(“ContentTemplate”)]
public abstract class TemplateWebControl : WebControl

And the ITemplate property definition looks something like this:

/// <summary>
///   Inline control template
/// </summary>
[Browsable(false)]
[DefaultValue(null)]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate ContentTemplate
{
     get;
     set;
}

My thought is that if you have a template defined in-line, it should take precedence over the file.  So my code for loading the template looks something like this:

if (ContentTemplate != null)
{
     ContentTemplate.InstantiateIn(this);                
}
else
{
     Control template = Page.LoadControl(TemplateFile);
     template.ID = “Template”;
     Controls.Add(template);
}

So the following code should load the ITemplate defined inline in the control:

<tag:MyControl runat=”server” id=”MyControlID”””” >
     Anything defined here becomes part of the ITemplate
</tag:MyControl>

And the following markup should result in the .ASCX file referencd in the TemplateFile property being loaded:

<tag:MyControl runat=”server” id=”MyControlID”””” TemplateFile=”MyTemplate.ascx” />

Notice that there is no inner content – which I would think would think should result in a NULL ContentTemplate property.  But such is not the case – the default inner property still gets populated.  So the ContentTemplate property has a valid ITemplate, but the template is completely empty.  As such, any time you define an ITemplate as a default inner property it will NEVER be null. 

You can avoid this problem by setting the PersistanceMode attribute of the property to be an InnerProperty, removing the DefaultProperty attribute on the class, and removing the default property name from the ParseChildren attribute on the class.  This forces you to explicitly define the property as an inner child, e.g.:

<tag:MyControl runat=”server” id=”MyControlID”””” >
     <ContentTemplate>
          Anything defined here becomes part of the ITemplate
     </ContentTemplate>
</tag:MyControl>

And results in the expected behavior when no inner content is defined.