Creating controls at runtime (dynamic controls) in Asp.net is both tricky and unintuitive. This article will explain a pattern to make it easier.
The main advantages of this strategy are :
– ability to create deeply nested controls with unlimited depth
– each dynamicaly created controls have normal states (Viewstate is not broken)
– you can create those controls whenever you want (including OnClick events, PreRender and Render phases)
– no hacks with postback arguments are required
[UPDATE (2011/08/01)] : “M” found that the PersistentPanel doesn’t work well when it is instantiated in a markup file (aspx/ascx/master …) so I would advice you to instanciate it via code in the CreateChildControl method. The source code in the bottom of the page have been updated to reflect that.
The online demo
To help you understand what am I talking about here is an online example of deeply nested and dynamically created controls using Asp.Net.
You can create as much nested controls as you want and test that each controls persists its state upon postbacks.
The implementation
The PersistentPanel
The PersistentPanel is just a Panel wich persists its child controls collection using the viewstate automatically. This is a key control because it recreates the dynamically created controls on each post back during the right life-cycle phase : OnLoadViewstate. Thanks to this early recreation, those controls can persist their state in the ViewState like any controls declared in the markup page during the design time.
This kind of component is quite common now a days but the particularity here is that I do not try to persits all the nested controls but only the direct children. Indeed, if you try to persist and recreate the whole hierarchy, you’ll encounter problems and will have to handle a lot of special cases. More over since event handlers are not persisted, the restored components wont work.
The implementation process of the PersitentPanel :
- during the save process of the viewstate we save the control hierarchy (type+Id only) using a serializable entity that store the control type, its Id and and a list of children
- during the restore process of the viewstate we refill the Controls collection using the previously saved control hierarchy
public class PersistentPanel : Panel
{
private static string ControlsViewStateKey = "Controls";
public int MaxDepth
{
get
{
var value = ViewState["MaxDepth"];
if (value != null) return (int)value;
return 1;
}
set
{
ViewState["MaxDepth"] = value;
}
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
var persisted = (ControlEntity)ViewState[ControlsViewStateKey];
if (persisted != null)
foreach (var child in persisted.Children)
Controls.Add(child.ToControl());
}
protected override object SaveViewState()
{
ViewState[ControlsViewStateKey] = new ControlEntity(this, MaxDepth);
var result = base.SaveViewState();
return result;
}
}
The parent of the dynamic controls
The component wich will dynamicaly create the controls will first embed a PersistentPanel. And each time it will want to add a control it will add that control in the PersistentPanel’s controls collection. Here is an example :
public class CustomerView : Page {
private PersistentPanel ctlPanel;
// ------ //
protected void ctlAdd_click(object sender, EventArgs e){
ctlPanel.Controls.Add(new Textbox{Text=Datetime.Now.ToString();});
}
}
Combining both to build a hierarchical data editor
Now we have a persistant panel and know that dynamically created controls are persisted, we’ll create a control that would create other complexes controls wich will have the same type as their creator. This would give us a powerfull control that would be able to display or edit hierarchical data wich, in our case, is a filter expression. We’ll have
– a control to edit scalar filter
– a control to edit composite filter
The scalar filter will just contain 3 simple controls for the field name, the operator and the value. The composite filter editor will be the interesting one. Indeed, it’s gonna contains a variable number of scalar editor and other composite filter editor. So it will use a persistant panel to host those nested controls. And that’s it !
public class compositeView : WebControl, INamingContainer
Conclusion
The important things to remember are that :
– a control can be created at anytime, but it must be recreated on each postback during/before the LoadViewState of its container
– the ID of the dynamic control must be the same
– event handlers are not persisted, you have to rewire them up on each postback, the PersistantPanel has the ControlRestored event wich is the best place to do so.
Download the source code
The online demo application is available here :
- Manitra.DynamicControl.zip (source code)
- http://web.manitra.net/dev/Manitra.DynamicControl/complex.aspx
Have fun !