Pourquoi ne pas créer un contrôle Web sans partir sur des CompositeControl mais plutôt sur un simple WebControl. Nous utilisons donc un div pour l'exemple .... 
public class CustomWebControl : WebControl
{
    protected override HtmlTextWriterTag TagKey
    {
        get { return HtmlTextWriterTag.Div; }
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        base.RenderContents(writer);
    }
}
La seconde étape est de générer notre code HTML.
public class CustomWebControl : WebControl
{
    protected override HtmlTextWriterTag TagKey
    {
        get { return HtmlTextWriterTag.Div; }
    }
    public MySource DataSource
    {
        get { return (MySource)this.ViewState["DataSource"]; }
        set { this.ViewState["DataSource"] = value; }
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
        writer.AddAttribute(HtmlTextWriterAttribute.Value, this.DataSource.Name);
        writer.RenderBeginTag("input");
        writer.RenderEndTag();
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "button");
        writer.AddAttribute(HtmlTextWriterAttribute.Value, "Hello!");
        writer.RenderBeginTag("input");
        writer.RenderEndTag();
        base.RenderContents(writer);
    }
}
La dernière étape revient a utiliser l'interface IPostbackEventHandler. Celle-ci nous offre la gestion du PostBack.
public class CustomWebControl : WebControlIPostBackEventHandler
{
    protected override HtmlTextWriterTag TagKey
    {
        get { return HtmlTextWriterTag.Div; }
    }
    public MySource DataSource
    {
        get { return (MySource)this.ViewState["DataSource"]; }
        set { this.ViewState["DataSource"] = value; }
    }
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        this.Page.RegisterRequiresRaiseEvent(this);
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
        writer.AddAttribute(HtmlTextWriterAttribute.Value, this.DataSource.Name);
        writer.RenderBeginTag("input");
        writer.RenderEndTag();
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "button");
        writer.AddAttribute(HtmlTextWriterAttribute.Value, "Edit!");
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick,
            this.Page.ClientScript.GetPostBackEventReference(this"Edit;" + this.DataSource.Id));
        writer.RenderBeginTag("input");
        writer.RenderEndTag();
        base.RenderContents(writer);
    }
    public void RaisePostBackEvent(string eventArgument)
    {
    }
}
Il est dès lors possible d'ajouter sur notre contrôle un event et de le lancer dans la méthode RaisePostBackEvent. N'ayant pas besoin de ViewState, on peut très bien au Load de la page remplir le Datasource d'un Repeater ... Le rendu sera parfait et on supportera la fonctionnalité ...
<asp:Repeater runat="server" ID="repeater">
    <ItemTemplate>
        <cc1:CustomWebControl ID="control" runat="server" EnableViewState="False" />
    </ItemTemplate>
</asp:Repeater>
Ceci termine ce petit article... Il offre la possibilité si le contrôle est un affichage de données sans modification de s'abstraire facilement du ViewState,de réduire le code HTML généré et d'éviter une profusion de contrôle ASP.NET sur une page ... En effet, il ne faut pas oublier qu'il y a par défaut une limite (modifiable dans la config mais si ce n'est pas indispensable ...) ...