Try this:
Section.cs:
[ToolboxData("<{0}:Section runat=\"server\" />")]
public class Section : WebControl, INamingContainer
{
    private SectionPartCollection _parts;
    [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty)]
    public SectionPartCollection Parts
    {
        get
        {
            if (this._parts == null)
            {
                this._parts = new SectionPartCollection();
                if (this.IsTrackingViewState)
                    ((IStateManager)this._parts).TrackViewState();
            }
            return this._parts;
        }
    }
    [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty)]
    public ITemplate LayoutTemplate { get; set; }
    protected override void CreateChildControls()
    {
        base.CreateChildControls();
        if (this.LayoutTemplate != null)
        {
            this.LayoutTemplate.InstantiateIn(this);
            foreach (SectionPart part in this.Parts)
            {
                Control placeHolder = this.FindControl(part.PlaceHolderID);
                if (placeHolder != null)
                    if (part.ContentTemplate != null)
                        part.ContentTemplate.InstantiateIn(placeHolder);
            }
        }
    }
    protected override void LoadViewState(object savedState)
    {
        object[] states = (object[])savedState;
        base.LoadViewState(states[0]);
        if (states[1] != null)
            ((IStateManager)this.Parts).LoadViewState(states[1]);
    }
    protected override object SaveViewState()
    {
        object[] states = new object[2];
        states[0] = base.SaveViewState();
        if (this._parts != null)
            states[1] = ((IStateManager)this.Parts).SaveViewState();
        return states;
    }
    protected override void TrackViewState()
    {
        base.TrackViewState();
        if (this._parts != null)
            ((IStateManager)this._parts).TrackViewState();
    }
}
SectionPart.cs:
[DefaultProperty("PartName")]
public class SectionPart : IStateManager
{
    private StateBag _viewState;
    private bool _isTrackingViewState;
    [DefaultValue("")]
    public string PlaceHolderID
    {
        get { return (string)this.ViewState["PlaceHolderID"] ?? string.Empty; }
        set { this.ViewState["PlaceHolderID"] = value; }
    }
    [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty)]
    public ITemplate ContentTemplate { get; set; }
    public void SetDirty()
    {
        if (this._viewState != null)
            this.ViewState.SetDirty(true);
    }
    [Browsable(false)]
    protected StateBag ViewState
    {
        get
        {
            if (this._viewState == null)
            {
                this._viewState = new StateBag(false);
                if (this._isTrackingViewState)
                    ((IStateManager)this._viewState).TrackViewState();
            }
            return this._viewState;
        }
    }
    protected virtual bool IsTrackingViewState
    {
        get { return this._isTrackingViewState; }
    }
    protected virtual void LoadViewState(object state)
    {
        if (state != null)
            ((IStateManager)this.ViewState).LoadViewState(state);
    }
    protected virtual object SaveViewState()
    {
        if (this._viewState != null)
            return ((IStateManager)this._viewState).SaveViewState();
        return null;
    }
    protected virtual void TrackViewState()
    {
        this._isTrackingViewState = true;
        if (this._viewState != null)
            ((IStateManager)this._viewState).TrackViewState();
    }
    bool IStateManager.IsTrackingViewState
    {
        get { return this.IsTrackingViewState; }
    }
    void IStateManager.LoadViewState(object state)
    {
        this.LoadViewState(state);
    }
    object IStateManager.SaveViewState()
    {
        return this.SaveViewState();
    }
    void IStateManager.TrackViewState()
    {
        this.TrackViewState();
    }
}
SectionPartCollection.cs:
public class SectionPartCollection : StateManagedCollection
{
    public SectionPart this[int index]
    {
        get { return (SectionPart)((IList)this)[index]; }
    }
    public void Add(SectionPart part)
    {
        if (part == null)
            throw new ArgumentNullException("part");
        ((IList)this).Add(part);
    }
    public void Insert(int index, SectionPart part)
    {
        if (part == null)
            throw new ArgumentNullException("part");
        ((IList)this).Insert(index, part);
    }
    public void Remove(SectionPart part)
    {
        if (part == null)
            throw new ArgumentNullException("part");
        ((IList)this).Remove(part);
    }
    public void RemoveAt(int index)
    {
        ((IList)this).RemoveAt(index);
    }
    protected override void SetDirtyObject(object o)
    {
        ((SectionPart)o).SetDirty();
    }
}
Example:
<uc:Section ID="Section1" runat="server">
    <LayoutTemplate>
        <table>
            <tr>
                <td id="TitlePlaceHolder" runat="server">
                </td>
            </tr>
            <tr>
                <td id="BodyPlaceHolder" runat="server">
                </td>
            </tr>
        </table>
    </LayoutTemplate>
    <Parts>
        <uc:SectionPart PlaceHolderID="TitlePlaceHolder">
            <ContentTemplate>
                <span>Title</span>
            </ContentTemplate>
        </uc:SectionPart>
        <uc:SectionPart PlaceHolderID="BodyPlaceHolder">
            <ContentTemplate>
                <p>
                    Some content...</p>
            </ContentTemplate>
        </uc:SectionPart>
    </Parts>
</uc:Section>