Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Composite Controls: Dude, Where’s My Data?

0.00/5 (No votes)
31 Mar 2011CPOL5 min read 14.3K  
Composite controls can be tricky. If they aren’t set up just right, even the most basic example won’t work properly.

I started my .NET career writing WebParts for SharePoint 2003. You would think this would make me a bit of a server control expert as WebParts are essentially gussied up server controls. Yet, I’ve officially wasted two days on an age old composite control problem which typically involves these kinds of Google searches:

  • Server control postback missing data
  • Postback server control child control missing
  • Composite control event not firing
  • Webcontrol createchildcontrol postback control
  • Viewstate webcontrol data missing empty blank

Sound familiar? I understand your pain and hope I can help. Composite controls can be tricky. If they aren’t set up just right, even the most basic example won’t work properly.

First, keep in mind that there are two types of custom controls developers typically write. There are user controls (.ascx) and server controls (.cs). We will focus on a server control. One gotcha for a composite control is using the right base class*. We want to use the CompositeControl base class, not WebControl or Control. This will tell ASP.NET to ensure the IDs of our child controls are unique (via INamingContainer). This is simple but very important; in order for ASP.NET to wire up events and serialize ViewState, the ID of a control needs to be identical at all times. So, only assign a literal string to your child control and let .NET worry about making it unique.

Now there are two cases to consider, the first is where our composite server control creates a static set of controls. This is a straightforward case, because we can create the controls at any time. The most important function in a server control is CreateChildControls(). This is where you can create and assign your controls to member variables. We can have properties and get and set values straight to our member controls. In every property, just call EnsureChildControls() in each get and set.

C#
private TextBox _TextBox;
[Bindable(true), Category("TextBoxEx"), DefaultValue(""), Localizable(true)]
public string Text
{
    get
    {
        EnsureChildControls();
        return _TextBox.Text;
    }
    set
    {
        EnsureChildControls();
        _TextBox.Text = value;
    }
}

protected override void CreateChildControls()
{
    _TextBox = new TextBox();
    _TextBox.ID = "_Text";

    Controls.Add(_TextBox);
}

In reality, we may have properties or logic which determine which controls are created or how our control behaves. This is a complicated scenario, because we cannot create the controls before the logic has been initialized (otherwise our logic will not know which controls to dynamically create)! In this case, we want our control’s own properties independent of the child controls, and we’ll store this information in the ViewState, not a control. We want to avoid calling EnsureChildControls() and delay calling CreateChildControls() prematurely. This allows the control to be initialized first so that when CreateChildControls() is called, our logic will know which controls to create. First, let’s see how to store a property in ViewState.

C#
private const string STR_CreateTextBox = "CreateTextBox";

public bool CreateTextBox
{
    get
    {
        if (ViewState[STR_CreateTextBox] == null)
            return false;
        else return (bool)ViewState[STR_CreateTextBox];
    }
    set
    {
        ViewState[STR_CreateTextBox] = value;
    }
}

If you were wondering if ViewState was the right place to store properties, the answer is maybe. For a bound property, it is overkill as it will be set back to its original value in every postback. In that case, we simply need a property with a backing store (or in C# 3.0+, a simple get; set; will do). But when our logic needs to be stored, ViewState is the place to persist it. Just remember that the dynamically created controls don’t need a ViewState of their own so we’ll be sure to turn that off when we create them. The right place to create and add our dynamic controls is in CreateChildControls(). Let’s create a TextBox based on some logic stored in the composite control’s ViewState.

C#
protected override void CreateChildControls()
{
    if (CreateTextBox)
    {
        _TextBox = new TextBox();
        _TextBox.ID = "_Text";
        _TextBox.EnableViewState = false;
        _TextBox.TextChanged += new EventHandler(Text_TextChanged);

        Controls.Add(_TextBox);
    }
}

void Text_TextChanged(object sender, EventArgs e)
{
    string sText = ((TextBox)sender).Text;
}

Let's take a look at what we’re doing here. We are dynamically creating our TextBox control in CreateChildControls(). We are setting the ID to a literal string; ASP.NET will make sure our name is unique because we inherit from CompositeControl. We are setting EnableViewState to false because, as discussed, our composite control is already taking care of the ViewState. We are adding an event as an example as this is the right place to setup any events you might need.

Now, here is the interesting bit: How do we get the user’s value back from a dynamic TextBox? Take another look at the property Text above and note that the getter calls CreateChildControls(). This will ensure our textbox is recreated and the form will syncronize the user’s form data back in to the textbox. We could also capture the value using the TextBox’s text changed event to do some processing or whatever. With this event, when we postback, our Text_TextChanged event will fire before an OK button’s click event on the page due its place in the control hierarchy. This allows our event to manipulate the TextBox’s text value before our OK button’s click event occurs.

I’ll just note that you may be reading some advice on forums to override OnInit. Contrary to that advice, CreateChildControls() is the right place to dynamically create your controls and events. OnPreRender() is a great place for initialization as it is called after all properties are set. And, of course, Render() gives you complete control on how your control will be drawn.

* Now I have to mention Repeater. A Repeater may cause a lot of pain with your server controls. You may see your control working properly outside a Repeater and suddenly all go pearshaped when used with one. After a lot of trial and error, I discovered this had to do with the ID assigned to the dynamic controls. We know ASP.NET depends on the ID being identical at all points for events and serialization with the form and viewstate to take place. Sad to say, when our composite control is inside a Repeater, we can not trust our IDs to .NET. So we do not want to inherit from CompositeControl or INamingContainer. Instead, assign a unique ID yourself. Being that it is a Repeater, this cannot be a literal string because no two controls can have the same ID. Instead, try _TextBox.ID = this.ID + “_Text”;.

So although there are several points and gotchas to consider, think about using a custom control to lend some OO design to your UI. Be sure to unit test on a simple website to make development of the control easier. Good luck!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)