views:

944

answers:

2

I'm writing a small ASP.Net custom control inheriting from CompositeControl. The control is just a panel containing two subpanels with a label in each subpanel. When rendered, I am seeing in the HTML source that the first child control of my custom control gets two id attributes - the first being the id of the custom control itself and the second being the ID property I assign to the first child control. Why is this happening?

Code:

[ToolboxData("<{0}:MessageBox runat=server></{0}:MessageBox>")]
public class MessageBox : CompositeControl {

    private Panel _MessageHeaderContainer = null;
    private Label _MessageHeaderLabel = null;
    private Panel _MessageDetailsContainer = null;
    private Label _MessageDetailsLabel = null;

    protected override HtmlTextWriterTag TagKey {
        get {
            return HtmlTextWriterTag.Div;
        }
    }

    protected override void CreateChildControls() {

        // Message header area.
        _MessageHeaderContainer = new Panel();
        _MessageHeaderContainer.ID = "HeaderContainer";
        _MessageHeaderContainer.CssClass = "__MessageBox_Container";
        this.Controls.Add(_MessageHeaderContainer);

        // Message header text.
        _MessageHeaderLabel = new Label();
        _MessageHeaderLabel.ID = "HeaderLabel";
        _MessageHeaderLabel.Text = "[ Header ]";
        _MessageHeaderContainer.Controls.Add(_MessageHeaderLabel);

        // Message details area.
        _MessageDetailsContainer = new Panel();
        _MessageDetailsContainer.ID = "DetailsContainer";
        this.Controls.Add(_MessageDetailsContainer);

        // Message details text.
        _MessageDetailsLabel = new Label();
        _MessageDetailsLabel.ID = "DetailsLabel";
        _MessageDetailsLabel.Text = "[ Details ]";
        _MessageDetailsContainer.Controls.Add(_MessageDetailsLabel);

    }

    protected override void RenderContents(HtmlTextWriter output) {
        AddAttributesToRender(output);

        // Render the box.
        _MessageHeaderContainer.RenderControl(output);
        _MessageDetailsContainer.RenderControl(output);
    }
}

Usage in ASPX page:

<cc:MessageBox ID="ctlMessageBox" runat="server" />

HTML output:

<div id="ctl00_ctl00_ctlMessageBox">
 <div id="ctl00_ctl00_ctlMessageBox" id="ctl00_ctl00_ctlMessageBox_HeaderContainer" class="__MessageBox_Container">
  <span id="ctl00_ctl00_ctlMessageBox_HeaderLabel">[ Header ]</span>
 </div><div id="ctl00_ctl00_ctlMessageBox_DetailsContainer">
  <span id="ctl00_ctl00_ctlMessageBox_DetailsLabel">[ Details ]</span>
 </div>
</div>
A: 

This is the ID ASP.NET assigns the Custom Control as a whole. If you wrap your Custom Control in a Div or Span (within the Custom Control code itself, not in the markup) the ID will be assigned to that instead of your first inner "control".

Something like this:

protected override void Render(HtmlTextWriter writer)
{
  AddAttributesToRender(writer);

  //must render tag or first inner control will get two IDs
  writer.RenderBeginTag(HtmlTextWriterTag.Span);

  //render child controls here...

  writer.RenderEndTag();
}
Aaron Hoffman
I did this by adding output.RenderBeginTag(HtmlTextWriterTag.Div) as the first line under the comment "Render the box" (and I added the appropriate output.RenderEndTag() after rendering the child controls. Now the HTML output includes an enclosing div tag with a duplicate id as the custom control's own "root" div tag. Basically I have <div id="ctl00_ctl00_ctlMessageBox"><div id="ctl00_ctl00_ctlMessageBox">< child control stuff /></div></div>. I still have duplicate control id's in the rendered output - not good.
Matt Hamsmith
Try overriding "Render", and not "RenderContents" Also, try removing the HtmlTextWriterTag TagKey.
Aaron Hoffman
+2  A: 

You can fix this by removing the RenderContents method entirely - don't replace it with anything. By default, composite controls render all their child controls. If you do this, your control produces clean markup:

<div id="test">
    <div id="test_HeaderContainer" class="__MessageBox_Container">
     <span id="test_HeaderLabel">[ Header ]</span>
    </div>
    <div id="test_DetailsContainer">
     <span id="test_DetailsLabel">[ Details ]</span>
    </div>
</div>

As you've noted, the AddAttributesToRender method is the culprit - it's a convenience method renders all the top-level control's attributes (whether the current context is the top-level control or not). The only reason you would call this is if you were taking full control of the rendering process yourself and wanted to render all the attributes for the top-level control in a single statement.

Jeff Sternal
+1 - Thanks! Very helpful.
Matt Hamsmith