I'm trying to build a very specific search page for a project and I'm having lot of trouble dealing with multiple postbacks invoked by dynamically-generated controls on a single page.
The page has to work like this:
There is a single checkbox, "Detailed search", that causes a postback on checking/unchecking.
When detailed search is not active, a simple grid with contents and buttons is displayed. Nothing special.
When detailed search is active, N checkboxes must be generated from some dynamic data, that represent the sections where you want the search to happen. Below the checkboxes, an AJAX-enabled tab control will appear, initially with no tab pages.
When checking one of the section checkboxes, a postback will occur. After the postback, data will be searched in the section selected by the user, then a new tab page containing a grid view of results and the name of the section will be added to the tab control. If the checkbox is unchecked, the tab page will disappear from the control, again, after a postback.
Now, the issue is that pretty much everything has to be generated dynamically, and that pretty much everything is connected to something else.
First issue: dealing with the "Detailed search" checkbox. Sounds easy, doesn't it? My initial idea was to set Page.Viewstate["DetailedSearchEnabled"] to true or false during the check/uncheck event handler, then create controls dynamically checking the value of DetailedSearchEnabled during Page_Load.
Nope. The postback event-handling happens between Page_Load and Page_LoadComplete. It would take an additional refresh for things to work as intended.
<< Then I'll just generate the controls on Page_LoadComplete! >>
Nope. Those controls need event handling as well, and if they're generated after Page_Load they will not be wired up correctly.
A possible solution would be generating everything in advance, on Page_Load, and only hiding/showing controls on Page_LoadComplete. But that is inefficient, and one important point of this search page is that only the minimum amount of controls should be generated.
The difficulty of this task seems to come from the way event wiring and the page life cycle work.
Surely there must be a better way of approaching this problem.
First issue: dealing with the "Detailed search" check box.
The correct approach (if you want to use page post-backs) is as follows:
In the CheckChanged event handler, save the value of the Checked property to ViewState["DetailedSearchEnabled"]. If the value is true, add the dynamic check boxes to the page. If the value is false, find and remove them.
Override LoadViewState. After calling base.LoadViewState, re-create the dynamic check boxes and wire up their events if ViewState["DetailedSearchEnabled"] is true. Note that neither Page_Load nor Page_LoadComplete is the appropriate place to do this.
Yes, you should create the dynamic check boxes at two points in the page life cycle. I recommend a helper method.
In general, your event handlers should add or remove just the dynamic controls (if any) affected by those particular events, but LoadViewState should re-create all dynamic controls that existed from the previous page request. You must store enough information in view state for LoadViewState to do so.
My answer to this other question demonstrates how to add and remove dynamic controls. You may want to use it as a reference.
Sounds to me like you should be using a CheckBoxList control to handle your dynamic checkboxes. You can add an remove items to the CheckBoxList during your post back and not have to worry about dynamically adding/removing actual controls/events to the form.
Here is a link to the msdn:
https://msdn.microsoft.com/en-us/library/14atsyf5(v=vs.85).aspx
Here is some sample code:
Protected void Button1_Click (object sender, System.EventArgs e)
{
CheckBoxList.Items.Add(new ListItem("TextValue1", "Value1"));
CheckBoxList.Items.Add(new ListItem("TextValue2", "Value2"));
}
If all else fails, you could still fall back on the quick-and-dirty old-fashioned ASP way.
Use Response.Write or <%...%> to generate your dynamic controls as plain old HTML (simple form fields, e.g. <input type="checkbox" name="foo" value="1" />).
Make sure you have a form field for every piece of information you may need after the postback(s). If necessary, use hidden form fields to 're-post' values across subsequent postbacks.
After postback, retrieve the values of the controls with the Request object.
Use those values to adjust the generation of controls as you see fit.
You should be able to do all of this in Page_Load. The advantage is total freedom. The disadvantage is total freedom to make a big mess of your aspx. So you may want to migrate all this dirty code out of your aspx, and into a custom-made control, which you can then add to your aspx.
When generating your own HTML, be careful not to introduce XSS vulnerabilities. Use HtmlEncode where necessary.
As you suggested yourself, there is a better way to tackle it.
If I was in the same situation, I would create web methods for interacting with the page, and use client side to do the UI. I'm currently working mostly with angular JS, although it does come with a learning curve. You could use ng-hide/ng-show to bind to the checkbox event to display the detailed search. When the n number of checkboxes needs to be displayed, you can then just fill them in with ng-repeat, for each of the items you need to display, after a check/uncheck you can dynamically populate new controls etc. through web method calls if extra data is needed.
Pure ASP postbacks are quite clunky from my experience, and not really suited for building a maintainable dynamic UI.
Instead of making so many postbacks, it would be better to use jquery and ajax calls to load the controls as needed and then attach events to it or you can even use UpdatePnael for that. Help Links:
https://www.simple-talk.com/dotnet/asp.net/ajax-basics-with-jquery-in-asp.net/
http://encosia.com/using-jquery-to-directly-call-aspnet-ajax-page-methods/
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 cs class which I use as a control on one of my page. It is like a table with some values. This control is loaded when the page loads. After user does some selection, I need to refresh this control with the new values which I do by assigning it in code behind. My problem is that though I am reassigning some properties, the control which was loaded on the page load, does not refresh, which makes sense. i have tried a few ways but could not figure it out. I am posting it here as I am running short of time and would appreciate it if anyone can point me to the right direction.
Thanks.
Make sure you are calling LoadControl on PreInit and doing so on each postback.
PreInit:
Use this event for the following:
Create or re-create dynamic controls.
....
ASP.NET Page Life Cycle Overview
Related question: Get All Web Controls of a Specific Type on a Page
In the question above I asked how I can get all controls, works like a charm but something just doesn't quite fit so I thought it might be me. I have the following code but it's not manipulating the controls on the page but in my theory it should work.
List<DropDownList> allControls = new List<DropDownList>();
ControlEnhancer.GetControlList<DropDownList>(Page.Controls, allControls);
foreach (DropDownList childControl in allControls)
{
foreach (ListItem li in childControl.Items)
{
li.Attributes.Add("title", li.Text);
}
childControl.Attributes.Add("onmouseover", "this.title=this.options[this.selectedIndex].title");
}
Thats the code, GetControlList() code you can get from the related question which shows how it gets all controls, its just my manipulation. I am trying to get all dropdownlist listitems and add a title to them so I can have a tooltip.
It's a quick fix for IE8 and below which cuts of long text in drop down boxes.
Page_Load happens often too soon; Page_PreRender is the last moment before the page's HTML is actually rendered for the browser and in many cases is the best place to set attributes on user controls.
This because during the web form (page) life cycle there are other events in the page (and in the user controls contained in the page...) which sometimes remove/replace/overwrite (really) those attributes so the only way you can get those attributes to the browser is to append them after all other life cycle events have been fired and handled, in the Page_PreRender.
Actually, even PreRender might be too early in some cases (e.g. you could have DropDownList controls added to the control tree during databinding of controls that use DataSourceID).
There are two further events that might be more appropriate:
PreRenderComplete. At this point, all controls are created and the page is ready to render.
SaveStateComplete. Occurs after view state and control state have been saved. Any changes you make here won't be persisted to view state.
In your example (adding client-side attributes), I'd use the SaveStateComplete event to avoid unnecessary view state bloat.
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?