After wasting hours for trying to solve dynamic user control's ViewState being lost I decided to disable ViewState for good.
The question is what should I do now? How should I keep my dynamic control's state so that they don't get lost after postbacks. I am thinking about using Session instead but that means I have to generate unique keys for each page/tab opened by the user so that values are not overwritten (right?). What is the best way of doing it?
For ensuring view-state of dynamic user controls, you need to ensure that
Dynamic controls are created in every post-back scenario
They are created as early as possible in page life cycle - init and load state are best bets (loading them in control events is unlikely to work)
The dynamic control hierarchy should be exactly the same and all controls in hierarchy should have ids same as in previous request
Always assign ids (otherwise they may get auto-generated and can have different values) and assignment should happen before adding the control in control tree.
Changing the view-state store to say session instead of hidden field will not solve the problems where ASP.NET run-time has already loaded the view-state or is unable to associate control with its view-state data (inferred from id and hierarchy)
Related
About 5 months ago I was tasked with creating a new intranet site for my current employer as the old one is a nightmare to work with. The site uses multiple .NET languages (classic asp, VB, and C#) with multiple .NET frameworks (1.0, 2.0, 3.5, few places with 4.0). Simple changes that should only take an hour to implement and test would take days just to implement.
The new intranet sites content is controlled through user controls that are loaded dynamically at load time based on the page you are on and the access level you have. Each user control has a specific task and does not affect any other user control on the page.
About 3 weeks ago my database guy (was an application developer at his last job) pitched this idea to the middle management that the user controls could talk to each other and affect the selections available in each user control (all this without my knowledge).
At first, I didn't think it was possible when I heard. Then, everything I read about having user controls communicate with each other indicated that the user controls had to know about each other and that wasn't possible since all of the user controls are load at runtime based off of the access level you have. I found a solution last week were I could have a user control fire a custom event handler and have my other user controls listen for that specific custom event handler.
Now, today, I was asked if I could add filtering to the contact management part of the site that lists all of our clients similar to how Ebay has there filters on the left that allows you to drill down farther into the results returned. For example, you search "flat screen tvs". Ebay will list all results that match you search and on the left you can select the size range or the brands to narrow down the results.
On the page I setup I load 3 user controls to handle the criteria and the results. Control1 has all of the basic search criteria (ex. industry, region state, ect), control2 has the filters for drilling down the results from control1. Control3 displays the clients based off the criteria in control1 (so control1 fires an event that control2 and control3 hear and they both display the results based on control1). Now I select the criteria from control2 and fire the event that control3 can hear and displays the results.
All of this works, the problem I am having is that the controls in control2 are built dynamically and when the event in control1 is fired -> then control2 posts back to fire the event for control3 to hear I lose all the dynamic controls in control2 as the controls can't be recreated in the Page_Init because the values passed in from the custom event in control1 no longer exists because control2 did the postback and the event from control1 is only fired when control1 postsback. What is the best way to store the values passed in to control2 from control1's custom event or get control1 to repass the values when control2 posts-back so I can recreate the dynamic controls in control2?
Note: I tried using sessions but had trouble reassigning values from control1 after the first search. I believe the reason they don't work is due to the way I have control1 setup and the creation of dynamic controls in control2 is skipping over getting the session values.
I thank all of you in advance for your (hopefully) helpful responses.
Update
Turns out that the way I was loading my usercontrols at runtime on my default page is the reason why the dynamic control in 'control2' were not being recreated when 'control2' posted back. My default page loaded the usercontrols in the 'page_load' instead of the 'page_init' (must have forgot to move the loading of usercontrol to the 'page_init' like all my other pages). Made the switch and the dynamic controls are recreated on postback.
The only issue that I had after moving my code from the 'page_load' to the 'page_init' was that the 'checkbox' controls would be unchecked on postback even though I checked them. I was able to over come this with a few session variables.
This is a common problem.
Only controls dynamically created in your page_init event can survive a postback.
During page_init, dynamically created controls become part of the DOM, and thereby have sessionstates. If you can re-factor your code to fire the dynamic control creation during page_init, your controls should survive.
Update:
I realize from your comments and post that you're reluctant to use Sessions. Problem is that Sessions are the ONLY way to save your controls.
One way I dealt with this case was to create a Class Object with Lists of Controls. When I came back to the page, if the Object existed I used it as a default.
Second way I approached this was to save the search criteria in Session and feed the criteria to my dynamic control creation method.
I am creating a custom control extending WebControl. This web control allows the consumer to define a collection of columns in markup, something like this:
<Custom:CustomGrid>
<Columns>
<Custom:DataColumn HeaderText="FirstName" />
<Custom:DataColumn HeaderText="LastName" />
</Columns>
and put an IEnumerable in a DataSource property and this is rendered out to a table.
This control also allows paging. The IEnumerable in DataSource is the full list, and I display a page of the list at a time. I am already saving the current page, number of rows per page, etc. to viewstate. Should I also put the full list in viewstate? Maybe session?
This list can become a bit hefty. Maybe save in session with a random key, which is saved in viewstate?
What is the best practice here?
Edit: I don't think it's right to impose that all types in the IEnumerable be serializable. Is that fair? So do I need to copy the data source to some other data structure for serialization?
Edit 2: Even if I do use a base control instead of implementing RenderChildControls I will need to implement CreateChildControls, but I will still need to persist the data somewhere, or did I miss the point of the base class?
Indeed, not all IEnumerable instances will be serializable.
If the query is cheap to run I wouldn't persist the whole data set but just run the query again for a different page or a change in the sort order.
If you put the data in viewstate you'll end up with huge pages. Session state might be acceptable if you don't have many users, but large data sets with lots of users won't scale well. What if I bind a million rows to your control? Or what if your control was used in a repeater and shown 100 times on a page?
Are you sure you need to persist the data? This isn't premature optimisation is it?
Remember that your control is a UI component. The viewstate should hold enough information to maintain the UI state as it is. A change in state (e.g.: switching to a different page of results) is something for which your control should pass responsibility to the data source.
Take a look at good old GridView. It displays what you give it and remembers that. If you're using paging then it raises an event to say "the user has changed page; give me page x of data". For me, that's the best practice for a UI control.
For implementing databound control it is better to use base class which was designed to perform such task. For example in ASP.NET exist CompositeDataboundControl which can be used as a base class to implement custom data bound controls. I can advice to review the following Dino Esposito article:
http://msdn.microsoft.com/en-us/library/aa479016.aspx.
Basically if you create control like ASP.NET gridview then it is store values in viewstate. To be more clear it is create number of the DataRow controls which saved assigned values in viewstate. During postback it recreates the same number of rows and values are restored from viewstate. If you will save only datasource for example in session without using viewstate then you will need to redatabind data to your grid during every postback. So, if you create Server control similar to gridview then approach described in the Dino Esposito post will be very helpful because it shows how to create control similar to ASP.NET Server GridView control.
I am dynamically loading user controls to switch between views in my project. I am aware that I need to reload my user control on every postback. The odd thing is my viewstate for the control is maintained even though the control is gone? I confirm that the panel I loaded it into is empty and then I check the view state and my value is there.
What's stranger is that if I load a different control, it can see the viewstate from the previous control? I checked and my page cannot see viewstate from my dynamically loaded control and visa versa. This makes me think the user control is treated as its own page. The confusing part is why the second view I load can see values from the first and why the values are there even though I the control has disappeared?
I also find this section of the code to be useless. Why is it removing the control? The panel is always empty (the code is from Telerik):
string controlId = LatestLoadedControlName.Split('.')[0];
Control previousControl = pnlControlPlaceholder.FindControl(controlId);
if (!Object.Equals(previousControl, null))
{
this.pnlControlPlaceholder.Controls.Remove(previousControl);
}
I looked at several posts and most say that viewstate is lost on every postback, although this is not the case for me. Perhaps because I'm using update panels. Although if an intial request handles an event and then reloads the same control again, the viewstate is lost. It only seems to preserve the viewstate on the very next postback.
Can anyone explain this odd behavior of sharing viewstate between user controls or why it is there even though the control is lost?
Apparently you can read viewstate between pages in two scenarios... Cross page postback and when using Server.Transfer. I believe the cross page postback scenario would explain what I am seeing.
i have a form that is generated dynamically.
the plan is to generate it, the user to enter data, and then to save all that lot away.
although a slight variation to this is if the form has previous data associated with it, and then it loads in all pre-populated. - the user may then change any previous selections.
and that is the rub really, i know if i call generateform regardless of postback the viewstate should take over and remember what the settings weer.. but as the generateform method as mentioned above populates the form if the form has previously been saved. which will win, the viewstate or the generateform method for the field populations.. ?
thanks
nat
If you dynamically generate any form controls that post data or cause a postback, you need to recreate them again on postback in order for them to be bound to their data, or their events, after the postback. Conceptually, this makes sense. If you don't have a control in your form after the postback, how could you look at its contents?
There are several ways you could approach this problem.
1) Call GenerateForm() no matter what. Since you said it pre-populates some of the data, you would need to change it so it can be called without doing that. ASP.NET will populate the controls with the data posted automatically on postback, which is what you want.
2) Keep a list of all your dynamically generated controls in a ViewState variable, so you can re-generate them upon postback. For most situations involving dynamically-created controls that aren't very simple (e.g., you may not know in advance exactly what controls are generated), this is the best solution. And often you will want to be able to access the data after a postback, but maybe you really don't want to recreate the whole form because you aren't using it any more.
As long as you recreate a control of the same type and ID on or before Page_Load(), it will be bound to the posted data. It does not need to be in exactly the same place on your form. And it does not need to be used or displayed, either - you can destroy it before the form is rendered, e.g., in Page_PreRender()
3) If you have no interest in any of this, you can always use Request.Form to look directly at the posted data, though this can also be tricky because the names will likely not match your form control IDs exactly. ASP.NET generates unique client-side IDs that depend on the container, and this is what you'll find in Request.Form. If you don't regenerate a control, you may not be able to easily determine the ID that you are looking for. Generally, you should not do this, but it's a way to look at the posted data and sometimes you need it.
I am writing a survey generating system in asp.net. i asked an ealier question about the best way to create controls that can be passed about as variables. This was problematic with user controls so i was advised to use custom controls and a quick way to do this was to inherit from the panel control and just add a bunch of standard controls the the controls collection by overriding the CreateChildControls method. This way i could create my "survey" controls,which are basically Questions in the survey. THe question controls are then dynamically added to the page. This all works well but know i have come to the point that i want to try and retrieve the values from these controls and i seem to be lost in a nether world of of viewstates and page lifecycles. I can ensure that the dynamically added text boxes have a known ID, however even if i add the parent control in the page init handler the CreateChildControls method does not run until after until after the viewstate is loaded. I cannot work out how to retreive the values from these text boxes.
You can call the EnsureChildControls method on the init handler of your control to ensure CreateChildControls is called before the ViewState is loaded.
You certainly seem to be doing this the hard way. TextBox values are not saved in the Viewstate, they are posted in the request.
Why aren't you using a UserControl here? So you can "pass" it somewhere? what exactly are you trying to do?