Viewstate of ascx lost between postbacks - asp.net

In my ASP.NET application, I am loading an .ascx dynamically using LoadControl, using the following pattern:
var ctrl = LoadControl("/path/to/control.ascx");
((ControlType)ctrl).SomeProperty = someData;
placeholder.Controls.Add(ctrl);
The control that I add saves the SomeProperty property value directly to ViewState, as follows:
public int? SomeProperty
{
get { return (int?)ViewState["SomeProperty"]; }
set { ViewState["SomeProperty"] = value; }
}
After that, the ascx control lives a life on its own and all is well until postback occurs. When the page posts back, suddenly the view state is empty! I suspect this happens because I manipulate the ViewState before I add the instantiated ascx to my page. Also, I can prevent the ViewState from getting lost by adding the following line in the Page_Load() method of my ascx control:
SomeProperty = SomeProperty;
I have to do the above for each and every property to ensure that the ViewState is preserved. Now, is there a prettier way of doing this? Manipulating the ViewState after the instantiated .ascx has been added to the page is not an option - I need the contents of the ViewState in the Page_Init() and Page_Load() methods, which are triggered the instant I add the .ascx to my page.
Thanks.

Take a look at the ASP.NET Page Life Cycle and Understanding View State. View State gets loaded after Initialization, so you won't be able to access it in Page_Init. You'd be better off using a hidden field.
If you are dead set on using View State, the earliest you can get to it would be by overriding the LoadViewState method (Remember to call base.LoadViewState before trying to access it though).

You also need to add the control to the controls collection BEFORE you set the property. ViewState does not get recorded until after it is added to the controls collection.
placeholder.Controls.Add(ctrl);
((ControlType)ctrl).SomeProperty = someData;

Keep track of the ID of the UserControl before postback then on postback re-create the control and assign the ID back and it should automatically load the ViewState back in.

Related

How do you dump viewstate for a control while leaving it enabled for future postbacks?

We have a Telerik Grid (i.e. enhanced version of ASP.Net vanilla grid). On some postbacks we perform major changes to the grid and we don't want load the viewstate for the grid control for that one page load, but we want viewstate to be load to future loads. And in all cases we want to save the viewstate in case the user uses the same data source each time.
It basically goes like this:
Page initially loads.
Viewstate saved.
User manipulates control on page that isn't the change datasource control.
Page posts back.
Controls created.
Viewstate loaded.
Events processed.
Viewstate saved.
User gets back page.
User manipulated datasource change control.
Page posts back.
Controls created.
Viewstate loaded (we don't want the grid's viewstate loaded, but it is anyways).
Events processed.
Viewstate saved (we want all viewstate saved including the new grid's viewstate).
User gets back page.
You can derive from the grid control in question and then override the LoadViewState method to not load viewstate when you don't want to.
public class MyGrid : BaseGrid
{
public bool IsNew { get; set; }
public override void LoadViewState(object viewState)
{
if (!this.IsNew)
{
base.LoadViewState(viewState);
}
}
}
Set EnableViewState = False for the control you don't want to save ViewState information on. Here is a great article on View State:
http://msdn.microsoft.com/en-us/library/ms972976.aspx

How to set user control properties before it gets rendered?

I have a User Control (uc) on a page. uc exposes some properties that get set on Page_Load() event of the parent page, and should be read while user control loads up.
Looks like Page_Load() of the uc fires before any properties get set from the parent page.
On what event should I set the uc properties so that it can use those properties as it gets rendered?
I use ASP.NET 3.5 and C#
p.s. i just dug out a solution from my old code:
in Page_Load of the control do:
Page.LoadComplete += delegate { LoadTheControl(); };
i'd still like to hear your ideas though.
You can get access to the property OnPreRender without any tricks.
I JUST ran into this problem this afternoon myself.
I created a public method on the UserControl called LoadData() and called it from the Page_Load of my hosting page after setting the property that the data depended on.
Added - code example
In the user control:
public property SomeProp {get, set};
public void LoadData()
{
// do work
}
and in the Page_Load on the hosting page
myControl.SomeProp = 1;
myControl.LoadData();
If you're putting the usercontrol on the page declaratively, just set the property there.
<cc1:myUserControl MyProperty="1" />
If you're adding the controls to the page dynamically, just set the property before you add the control to the hosting page's control collection. None of the lifecycle events for the control will fire until you call Page.Controls.Add(myUserControl).
If it is impossible to avoid the setup you've got right now, and the parent must perform some logic in Page_Load and the UC has to be there already, then I would suggest what David Stratton posted, but in my experience this is usually standardized to be called Initialize() or Init().
Well,
None of the answer help me.
But when a set manually the properties on the event InitComplete() everything works fine.
My user control was, now, enabled to read the property AFTER i set the property not before.
I have encountered a similar problem:-
I first add a UserControl dynamically, and then attempt to assign values to the control's internal controls (a textbox within the user control). Using any event such as INIT or LOAD, the control gives me a null reference exception, because as evidenced by stepping through the code, the controls have not as yet been created.
Later I found that this problem only occurs if I declare the control through a dim statement;
Dim MyControl as new MyUserControl
Me.Panel1.Controls.add(MyControl)
MyControl.SetLabelText("My Name") 'Uses a method to set the label text on load.
On the other hand, if I use the control as follows it works just fine:
Dim MyControl as MyUserControl = LoadControl("MyUserControl.ascx")
Me.Panel1.Controls.add(MyControl)
MyControl.SetLabelText("My Label Text")

Catching events from dynamically added ASP.NET User Controls

I have an ASP.NET web form which I am adding a variable number User Controls to. I have two problems:
The User Controls are added to a PlaceHolder on the form in the first PageLoad event (I only add them when "(!this.IsPostback)", but then when the form is posted back, the controls are gone. Is this normal? Since other controls on the form keep their state, I would expect these dynamically added ones to stay on the form as well. Do I have to add them for every postback?
I also have a button and an event handler for the button click event, but this event handler is never called when I click on the button. Is there something special I have to do to catch events on dynamically added controls?
Yes, you need to add them in every postback.
Yes... the control needs to be in the control hierarchy before asp.net dispatches the event (i.e. create the dynamic controls as early in the page lifecycle as possible).
1) You should add the controls on the Pre-init (Page life cycle)
2) You have to attach the event handler to the event of the created button.(events might occur much later in the page life cycle than the same events for controls created declaratively)
To achieve this, add your controls at page init instead of page load. (re-add at postback)
You'll need to know the id of the buttons added to bind them to the event.
I ran into a similar problem. I had a page that displayed a collection of custom web controls. My solution was to add an additional invisible web control so that when I clicked a button to add another control that I would just use the invisible one. Then on post back my load function would add another invisible control to the collection.
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 ran into the exact same problem and struggled through like 5-6 hours.
I'm posting this maybe someone like me could get help.
1) You should initialize your controls at Page.PreInit event. (In my case I had to add my controls to a place holder so I extended PreInit to load those controls before but you don't need to do that. It depends on your scenario.)
2) You should bind those exact methods to your controls after you initialize them in your Page.PreInit event.
Here is my sample code:
protected override void OnPreInit(EventArgs e)
{
// Loading controls...
this.PrepareChildControlsDuringPreInit();
// Getting ddl container from session and creating them...
if (GetDDLSession().Count != 0)
{
foreach (DropDownList ddl in GetDDLSession())
{
ddl.SelectedIndexChanged += SelectedIndexChanged;
phDropDowns.Controls.Add(ddl);
}
}
base.OnPreInit(e);
}
public static void PrepareChildControlsDuringPreInit(this Page page)
{
// Walk up the master page chain and tickle the getter on each one
MasterPage master = page.Master;
while (master != null) master = master.Master;
}

ASP.Net: User controls added to placeholder dynamically cannot retrieve values

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.

What do you do when you can't use ViewState?

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.

Resources