jump to navigation

Repeaters and Lost Data After Postback (Viewstate) October 8, 2009

Posted by codinglifestyle in ASP.NET, CodeProject.
Tags: , ,
18 comments

Test question: I have a form which binds data to a Repeater on PageLoad.  The Repeater’s ItemTemplate contains a TextBox and Checkbox.  On postback the data is lost.  What’s wrong?

You may have found this page if you have been googling the following:

  • repeater postback lost data
  • dynamic control postback viewstate
  • data lost on postback

The problem is the Repeater is a dynamic control.  If you are binding in the codebehind, which we typically are, you have to realize that the textbox and checkboxes do not existuntil you DataBind().  Keeping that in mind, we should ask ourselves what is the order of execution of ViewState.  I think we can start to formulate our answer to the question above by realizing where I DataBind() and create my controls in relation to the workings of ViewState is why our data is lost on postback.

So, the answer: PageLoad is the wrong place to bind a Repeater or setup a dynamic control.  It is too late.  ViewState has already tried to resync your controls but they weren’t created yet.

What about the answer you want?  Stop toying with me and tell me the answer I hear you say.  Try OnInit().  If you bind there you’re controls will exist in time for ViewState to operate normally.

protected override void OnInit(EventArgs e)

I would have also accepted Repeaters are the evil frogspawn of Satan and should never be used unless you find peeling off your fingernails with rusty pliers appealing.

ref: http://weblogs.asp.net/ngur/archive/2004/05/17/133340.aspx, http://aspnet.4guysfromrolla.com/articles/092904-1.aspx

Composite Controls: Dude, where’s my data? June 25, 2009

Posted by codinglifestyle in ASP.NET, C#, CodeProject.
Tags: , , , , , , , , ,
3 comments

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 2 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 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.

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 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.

 

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 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 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.

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;

}

Lets 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 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 a 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 forum’s 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.  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 can not be a literal string because no 2 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!