I am adding some user controls dynamically to a PlaceHolder server control. My user control consists of some labels and some textbox controls.
When I submit the form and try to view the contents of the textboxes (within each user control) on the server, they are empty.
When the postback completes, the textboxes have the data that I entered prior to postback. This tells me that the text in the boxes are being retained through ViewState. I just don't know why I can't find them when I'm debugging.
Can someone please tell me why I would not be seeing the data the user entered on the server?
Thanks for any help.
This is based on .NET v1 event sequence, but it should give you the idea:
Initialize (Init event)
Begin Tracking View State (checks if postback)
Load View State (if postback)
Load Postback Data (if postback)
Load (Load event)
Raise Changed Events (if postback)
Raise Postback Events (if postback)
PreRender (PreRender event)
Save View State
Render
Unload (Unload event)
Dispose
As you can see, the loading of ViewState data back to the controls happen before the Load event. So in order for your dynamically-added controls to "retain" those values, they have to be present for the ASP.NET page to reload the values in the first place. You would have to re-create those controls at the Init stage, before Load View State occurs.
I figured out yesterday that you can actually make your app work like normal by loading the control tree right after the loadviewstateevent is fired. if you override the loadviewstate event, call mybase.loadviewstate and then put your own code to regenerate the controls right after it, the values for those controls will be available on page load. In one of my apps I use a viewstate field to hold the ID or the array info that can be used to recreate those controls.
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
MyBase.LoadViewState(savedState)
If IsPostBack Then
CreateMyControls()
End If
End Sub
I believe you'll need to add the UserControl to the PlaceHolder during the Init phase of the page life cycle, in order to get the ViewState to be filled in by the Load phase to read those values. Is this the order in which you're loading those?
Ensure you are defining your dynamic controls at the class level and adding them to the ASP container:
Private dynControl As ASP.MyNamespace_MyControl_ascx
And when you instantiate the control, ensure you call LoadControl so the object is added properly:
dynControl = CType(LoadControl("~/MyNamespace/MyControl/MyControl.ascx"), ASP.MyNamespace_MyControl_ascx)
You have to create your controls in the Page_PreInit event handler. The ASP.NET server control model is tricky; you have to fully understand the page lifecycle to do it right.
As others have said, any form of control manipulation must be done before viewstate is created.
Here is a good link on the page lifecycle to help you out:
http://msdn.microsoft.com/en-us/library/ms178472.aspx
We have experienced the same thing and have handled it by using ghost controls on page_load that have the exact same .ID and then the post back picks up the events and the data. As others said it's the dynamic adding of the control after the init stages that the state is built already and controls added after aren't stored.
Hope this helps a bit.
I also want to add that I've seen user controls work the way that you'd expect them to just by setting the Control.ID property at run time. If you do not set the ID, items may get built in a different order and work oddly.
Related
I am trying to design a page that handles both Employee and Station CRUD tasks depending on the user's preference. I have sub-classed UserControl to create an EditEntityControl and developed two custom controls that derive from this base class and each handle the CRUD activities for either the Employee or Station object.
When the user toggles the value in a dropdownlist, which triggers a postback, I want to dynamically load the correct control into the page. This works on the first load of each control, but when I try to reload the first control (after loading the second), I get the following error:
Failed to load viewstate. The control tree into which viewstate is
being loaded must match the control tree that was used to save
viewstate during the previous request. For example, when adding
controls dynamically, the controls added during a post-back must match
the type and position of the controls added during the initial
request.
I also see some strange behavior on the initial load of the second control where data bindings don't bind to the correct controls (setting the text of a button rather than a textbox on the control for example).
Is there a way to handle this scenario? Or, is there a way to clear out the ViewState and just re-request the page entirely to get around this error? It appears that if I could clear up this ViewState clutter/confusion between PostBacks, everything else is working as designed.
Great suggestions in the comments above, but what finally pointed me to the correct solution was Joel Coehoorn's answer on this question.
I load up the control OnInit, then in the SelectionChanged event blow away the OnInit changes and recreate the correct control as needed. Thanks for the additional suggestions about making unused controls invisible. I will keep that in mind for future reference.
I have webform where a set of controls are generated in a Panel control during a SelectedIndexChanged event of a dropdown. That all works fine.
However, when I enter values in those controls and I click on my submit button, the controls are wiped out along with the data I entered.
I can only create the controls in that SelectedIndexChanged event because that's where I get the info to generate the dynamic controls.
What I'd like to do is keep those controls displayed with the data I entered and use the data I entered to do something else (like it happens in WinForms.)
Is this doable?
Thanks!
Every time a postback occurs you are working with a new instance of your page class. Dynamic controls added to the page during a previous postback went to the garbage collector as soon as the page for that postback rendered to the browser. You need to re-create your dynamic controls on every postback.
Save the count of "control-sets" in Session or ViewState, so that you can regenerate them with their appropriate ID's(f.e. appendeded with an indexOfControl) during Page_Init.
Here are some additional informations on:
View State and Dynamically Added Controls *
ASP.NET Page Life Cycle Overview
Extract:
Dynamically added controls must be programmatically added to the Web page on each and every page visit. The best time to add these controls is during the initialization stage of the page life cycle, which occurs before the load view state stage. That is, we want to have the control hierarchy complete before the load view state stage arrives. For this reason, it is best to create an event handler for the Page class's Init event in your code-behind class, and add your dynamic controls there.
This is one of those areas where the attempt to make Webforms look like Winforms fails.
With Webforms, you need to do the whole dance of when to create controls. I believe all your controls will need to be recreated by some time around the end of PageLoad. There might be an event or two after page load that you can use, but generally speaking PageLoad is a safe time to create controls.
Essentially the controls need to be created before ASP.NET populates them with data from ViewState/Browser.
From local forum i understood that PreInit can be used to handle the following
PreInit()
>Master pages can be called dynamically
>Themes can be set dynamically
>Programatically add controls to controls collection
and i read Init() is for
Init()
In this event, we can read the controls properties (set at design time). We cannot read control values changed by the user because that changed value will get loaded after LoadPostData() event fires.
Question
I am not getting the point "We cannot read control values changed by the user".Where do
users change the value of control?.Example would help me to understand the point.
PreInit: Raised after the start stage is complete and before the initialization stage begins.
Use this event for the following:
Check the IsPostBack property to determine whether this is the first time the page is being processed. The IsCallback and IsCrossPagePostBack properties have also been set at this time.
Create or re-create dynamic controls.
Set a master page dynamically.
Set the Theme property dynamically.
Read or set profile property values.
Init: Raised after all controls have been initialized and any skin settings have been applied. The Init event of individual controls occurs before the Init event of the page.
Lets say you have a textbox, a dropdownlist, some check boxes... the user enters data into them and you want to read their values by writing
var text = myTextBox.Text;
var selectedItem = ddl.SelectedItem;
this you cannot do before after the LoadPostData method has been called.
This page gives a pretty good summary of the different events and what they should be used for http://msdn.microsoft.com/en-us/library/ms178472.aspx. It says that PreInit should be used for ie. creating dynamic controls and Init for setting properties on them.
On the first post, you show several controls, say a textbox and a submit button.
The user types code into the textbox and click submit.
The user has changed the value of the control when he typed it in the textbox and it then got posted back to the page.
You will not be able to access the value typed in until LoadPostData has processed.
This is the pretty much the same with all other server side controls.
PreInit:
Initialize master page , user controls , dynamic controls
Init :
set the properties of controls
The value is changed through PostBack. The changes in the form data is determined by the current ViewState (which isn't loaded until later) vs the form data. Seeing as that isn't loaded until later, then you can't read any control values at that point.
I have a checkboxlist and textbox controls on my asp.net page and the are dynamically created and added to the page. When I populate the values and submit the form, the values are empty by the time it hits the server. Any help?
They are empty because they are being re-created too late in the page lifecycle.
Without knowing the precise point in the ASP.NET Page Lifecycle you're adding your controls, (though I'd guess it's either Page_Load or an event handler), it goes something like this:
Build control tree
Add dynamic controls
Render
(Postback)
Build control tree
Reconstitute viewstate & bind to Post values
Add dynamic controls
Render
To solve this, you need to make sure your controls are created early enough in the lifecycle. Standard practice is to break the "control creation" into a separate method, and during CreateChildControls, check to see if they need to be created:
override CreateChildControls()
{
if(IsPostBack)
{
EnsureDynamicControlsAreAdded();
}
}
This way, if they do need to be initially added by something as far late in the lifecycle as an event handler (Button_Click, for example), you can call the same EnsureDynamicControlsAreAdded method from there as well, and on the next round-trip they'll be created much earlier.
Further to Rex M's answer, you could try creating the controls in the "Page_Init" event - this is one of the first events in the page lifecycle, and is where I would normally create controls in a viewstateless page (NB: If you do this, do not surround the content of the Page_Init handler with an "if (!IsPostback)" - this will prevent it from working as intended).
I have a rather complex page that dynamically builds user controls inside of a repeater. This repeater must be bound during the Init page event before ViewState is initialized or the dynamically created user controls will not retain their state.
This creates an interesting Catch-22 because the object I bind the repeater to needs to be created on initial page load, and then persisted in memory until the user opts to leave or save.
Because I cannot use ViewState to store this object, yet have it available during Init, I have been forced to store it in Session.
This also has issues, because I have to explicitly null the session value during non postbacks in order to emulate how ViewState works.
There has to be a better way to state management in this scenario. Any ideas?
Edit: Some good suggestions about using LoadViewState, but I'm still having issues with state not being restored when I do that.
Here is somewhat if the page structure
Page --> UserControl --> Repeater --> N amount of UserControls Dynamicly Created.
I put the overridden LoadViewState in the parent UserControl, as it is designed to be completely encapsulated and independent of the page it is on. I am wondering if that is where the problem is.
The LoadViewState method on the page is definitely the answer. Here's the general idea:
protected override void LoadViewState( object savedState ) {
var savedStateArray = (object[])savedState;
// Get repeaterData from view state before the normal view state restoration occurs.
repeaterData = savedStateArray[ 0 ];
// Bind your repeater control to repeaterData here.
// Instruct ASP.NET to perform the normal restoration of view state.
// This will restore state to your dynamically created controls.
base.LoadViewState( savedStateArray[ 1 ] );
}
SaveViewState needs to create the savedState array that we are using above:
protected override object SaveViewState() {
var stateToSave = new List<object> { repeaterData, base.SaveViewState() };
return stateToSave.ToArray();
}
Don't forget to also bind the repeater in Init or Load using code like this:
if( !IsPostBack ) {
// Bind your repeater here.
}
This also has issues, because I have to explicitly null the session value during non postbacks in order to emulate how ViewState works.
Why do you have to explicitly null out the value (aside from memory management, etc)? Is it not an option to check Page.IsPostback, and either do something with the Session variable or not?
I have always recreated my dynamic controls in the LoadViewState event. You can store the number of controls needed to be created in the viewstate and then dynamically create that many of them using the LoadControl method inside the LoadViewState event. In this event you have access to the ViewState but it has not been restored to the controls on the page yet.
1) there's probably a way to get it to work... you just have to make sure to add your controls to the tree at the right moment. Too soon and you don't get ViewState. Too late and you don't get ViewState.
2) If you can't figure it out, maybe you can turn off viewstate for the hole page and then rely only on querystring for state changes? Any link that was previously a postback would be a link to another URL (or a postback-redirect).
This can really reduce the weight of the page and make it easier to avoid issues with ViewState.
protected override void LoadViewState(object savedState)
{
// Put your code here before base is called
base.LoadViewState(savedState);
}
Is that what you meant? Or did you mean in what order are the controls processed? I think the answer to that is it quasi-random.
Also, why can't you load the objects you bind to before Page_Load? It's ok to call your business layer at any time during the page lifecycle if you have to, with the exception of pre-render and anything after.
When creating dynamic controls ... I only populate them on the initial load. Afterwords I recreate the controls on postback in the page load event, and the viewstate seems to handle the repopulating of the values with no problems.
I have to explicitly null the session
value during non postbacks in order to
emulate how ViewState works.
I'm still foggy as to why you can't store whatever object(s) you are binding against in session. If you could store that object in session the following should work:
On first load bind your top user control to the object during OnPreInit. Store the object in session. Viewstate will automatically be stored for those controls. If you have to bind the control the first time on Page_Load that is ok, but you'll end up having two events that call bind if you follow the next step.
On postback, rebind your top user user control in the OnPreInit method against the object you stored in session. All of your controls should be recreated before the viewstate load. Then when viewstate is restored, the values will be set to whatever is in viewstate. The only caveat here is that when you bind again on the postback, you have to make 100% sure that the same number of controls are created again. The key to using Repeaters, Gridviews etc... with dynamic controls inside of them is that they have to be rebound on every postback before the viewstate is loaded. OnPreInit is typically the best place to do this. There is no technical constraint in the framework that dictates that you must do all your work in Page_Load on the first load.
This should work. However, if you can't use session for some reason, then you'll have to take a slightly different approach such as storing whatever you are binding against in the database after you bind your control, then pulling it out of the database and rebinding again on every postback.
Am I missing some obvious detail about your situation? I know it can be very tricky to explain the subtleties of the situation without posting code.
EDIT: I changed all references to OnInit to OnPreInit in this solution. I forgot that MS introduced this new event in ASP.NET 2.0. According to their page lifecycle documentation, OnPreInit is where dynamic controls should be created/recreated.