UPDATE: Thanks to Jon Hynes of InfoMason, added functionality to support nested master pages. Code updates highlighted.

I've seen several articles on the web that lay out a method for checking to see if a ContentPlaceHolder has any content or is empty. These work some of the time but unfortunately fall down in certain situations – like a placeholder that contains an embedded literal code block for example.

In this case, there is no publicly exposed method that will provide the answer and no way to assemble any information that would tell us whether the ContentPlaceHolder is empty or has content. Fortunately, there is an internal .NET framework property that gives exactly what we need. A little reflection magic and we have a method that works in all circumstances.

The MasterPage has a property called ContentTemplates that is a dictionary of all content templates that have been generated by the content page. If we check this, we can determine whether in fact the ContentPlaceHolder has been overridden by the content page. This, combined with the control check gives us a method that tells us if the ContentPlaceHolder has anything in it.

This means we have three methods that work together to provide the resulting boolean. First, a method to check if there are any non empty controls in the ContentPlaceHolder:

public static bool HasNonEmptyControls(ContentPlaceHolder cph)
{
if (cph.Controls.Count == 0)
{
return false;
}
else if (cph.Controls.Count == 1)
{
LiteralControl c = cph.Controls[0] as LiteralControl;

if (string.IsNullOrEmpty(c.Text) || IsWhiteSpace(c.Text))
return false;
}

return true;
}

static bool IsWhiteSpace(string s)
{
for (int i = 0; i < s.Length; i++)
if (!char.IsWhiteSpace(s[i]))
return false;

return true;
}

Next, a method to check if the ContentPlaceHolder has a content template defined on the content page:

static readonly Type _masterType = typeof(MasterPage);
static readonly PropertyInfo _contentTemplatesProp = _masterType.GetProperty("ContentTemplates", BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

public static bool HasContentPageContent(ContentPlaceHolder cph)
{
IDictionary templates = null;
MasterPage master = cph.Page.Master;

while (templates == null && master != null)
{
templates = (IDictionary)_contentTemplatesProp.GetValue(master, null);
master = master.Master;
}


if (templates == null)
return false;

bool isSpecified = false;

foreach (string key in templates.Keys)
{
if (key == cph.ID)
{
isSpecified = true;

break;
}
}

return isSpecified;
}

This is where the reflection comes in. We grab the ContentTemplates dictionary off the MasterPage and check to see if the specified ContentPlaceHolder is defined.

Finally, we call both methods to come to a final determination of whether the ContentPlaceHolder is empty or has content defined:

public static bool HasContentOrControls(ContentPlaceHolder cph)
{
return HasNonEmptyControls(cph) || HasContentPageContent(cph);
}

This code will all run on .NET 2.0 and above. If you are using .NET 3.5, you can use the following extension method to add this to the ContentPlaceHolder itself:

public static bool HasContentOrControls(this ContentPlaceHolder cph)
{
return MasterHelper.HasContentOrControls(cph);
}

MasterHelper source

Comments

Comment by Luke

Thank you, exactly what I needed.

Luke
Comment by wilf

KISS:
keep it small and simple!
great solution for simple problem!
I need only the master reflection code.
thanks wilf

wilf
Comment by Jeff L

fantastic, works exactly as I expected. Thanks.

Jeff L