Issue with asp:ContentPlaceHolder and code blocks
Asked Answered
A

3

7

When a content placeholder contains any code blocks it reports that the control collection is empty.

For instance:

MasterPage.aspx

<asp:ContentPlaceHolder ID="Content1" runat="server" />
<asp:ContentPlaceHolder ID="Content2" runat="server" />

<div>Content1: <%= Content1.Controls.Count %></div>
<div>Content2: <%= Content2.Controls.Count %></div>

APage.aspx

<asp:Content ContentPlaceHolderID="Content1" runat="server">
    Plain text content.
</asp:Content>

<asp:Content ContentPlaceHolderID="Content2" runat="server">
    <%= "Code block content." %>
</asp:Content>

This will render the following:

Plain text content. Code block content.

Content1: 1

Content2: 0

Why is the master page's ContentPlaceHolder.Controls collection empty?

I want to check whether the ContentPlaceHolder has been populated (see also this question) but can't if it contains any <%= blocks.

Does anyone know a way around this?

Asti answered 8/6, 2009 at 12:40 Comment(1)
Thanks for the comment on previous question - will check this out later once home from the office.. This one intrigues me! (+1 from me!) Will update later :)Pentylenetetrazol
P
8

As promised, I said I would take a look. Sorry I never uploaded last night, long day and needed to hit the hay!

So, I was checking out the ContentPlaceHolder.Controls collection differences between how they are populated. I noticed that when the code block is used, it flips to read only. At any other point, it will simply be empty or populated.

I therefore decided to throw in an extension method to check it for us:

ContentPlaceHolderExtensions.cs

public static class ContentPlaceHolderExtensions
{
    public static bool ContainsControlsOrCodeBlock(this ContentPlaceHolder placeHolder)
    {
        if (placeHolder.Controls.Count > 0)
             return true;
        
        return placeHolder.Controls.IsReadOnly;
    }
}

And then check this in the master page:

Site.Master

<asp:ContentPlaceHolder ID="Content1" runat="server" />
<asp:ContentPlaceHolder ID="Content2" runat="server" />
<asp:ContentPlaceHolder ID="Content3" runat="server" />

<div>Content1: <%= Content1.Controls.Count %></div>
<div>Content2: <%= Content2.Controls.Count %></div>
<div>Content3: <%= Content3.Controls.Count %></div>

<div>Content1 (Ex. Meth.): <%= Content1.ContainsControlsOrCodeBlock() %></div>
<div>Content2 (Ex. Meth.): <%= Content2.ContainsControlsOrCodeBlock() %></div>
<div>Content3 (Ex. Meth.): <%= Content3.ContainsControlsOrCodeBlock() %></div>

As proof-of-concept, I then added a content page:

Index.aspx

<asp:Content ContentPlaceHolderID="Content1" runat="server">
Plain Text Content
</asp:Content>

<asp:Content ContentPlaceHolderID="Content2" runat="server">
<%= "Code block content" %>
</asp:Content>

And all rendered as expected (I believe)..

TBH, while it is not perfect.. I don't think we can get much more elegance in this situation. I am not sure how other control collections are set up in these different scenarios, so I only bolted on to the ContentPlaceHolder control.. Other templated controls may or may not work the same.

Thoughts?

You can download the project from here:

http://code.google.com/p/robcthegeek/source/browse/#svn/trunk/stackoverflow/964724

Pentylenetetrazol answered 9/6, 2009 at 5:48 Comment(2)
Cheers (+1 and acc) that works a treat. I made one tweak - an additional check for a single LiteralControl containing only whitespace.Asti
Good call, I was thinking of adding that myself :) Thank you!Pentylenetetrazol
A
3

Too much for a comment, here's the full code that I finally got working (adapted from @Rob Cooper's answer):

public static bool HasContent( this ContentPlaceHolder placeHolder )
{
    if ( placeHolder.Controls.Count > 0 )
    {
        LiteralControl textBlock;
        ContentPlaceHolder subContent;

        foreach ( var ctrl in placeHolder.Controls )
            if ( (textBlock = ctrl as LiteralControl) != null )
            {   //lit ctrls will hold any blocks of text
                if ( textBlock.Text != null && textBlock.Text.Trim() != "" )
                    return true;
            }
            else if ( (subContent = ctrl as ContentPlaceHolder) != null )
            {   //sub content controls should call this recursively
                if ( subContent.HasContent() )
                    return true;
            }
            else return true;   //any other control counts as content

        //controls found, but all are empty
        return false;
    }

    //if any code blocks are used the render mode will be different and no controls will
    //be in the collection, however it will be read only
    return placeHolder.Controls.IsReadOnly;
}

This includes two extra checks - firstly for empty literal controls (which occur if the page includes the <asp:Content tags with any whitespace between them) and then for sub-ContentPlaceHolder which will occur for any nested master pages.

Asti answered 11/8, 2009 at 13:2 Comment(0)
I
2

The controls collection is empty because when <%= %> script tags are present, literal controls are not added to the control tree. However, server controls will still get added. So:

<asp:Content ID="Content2" ContentPlaceHolderID="Content2" Runat="Server">
     <%= "Code block content." %>
     <asp:GridView runat="server" ID="gvTest" />
</asp:Content>

<div>Content2: <%= Content2.Controls.Count %></div>

will return

Content 2: 1

Rick Strahl has a great article that explains this behavior:

To make code like this work, ASP.NET needs to override the rendering of the particular container in which any script code is hosted. It does this by using SetRenderMethodDelegate on the container and creating a custom rendering method ...

Rather than building up the control tree literal controls, ASP.NET only adds server controls to the control tree when <% %> tags are present for a container. To handle the literal content and the script markup, ASP.NET generates a custom rendering method. This method then explicitly writes out any static HTML content and any script expressions using an HTML TextWriter. Any script code (<% %>) is generated as raw code of the method itself.

Unfortunately I can't think of any elegant solution to this conundrum.

Iives answered 8/6, 2009 at 13:31 Comment(1)
Nice explanation. That's a pain for MVC content though - I don't want to clutter <% %> content with <asp:Controls> just so that the Master page can see that the content panel has been used. There has to be some way round this.Asti

© 2022 - 2024 — McMap. All rights reserved.