I have some values that are set on a master page and that I want to save across a postback. I then want these variable to be available to pages using that master page during their load events.
Easy enough to create properties on the master page. So my first try to was to say that during the master page's load event, if not ispostback then generate the values and save them to the viewstate, else read them from the viewstate.
Except ... apparently the regular page load event happens BEFORE the master page load event, so the data wasn't there yet when I tried to read it.
Second try: have the master page set or retrieve these values during the Init event. No luck. Appears that the view state is not populated by Init time.
As far as I can tell, there's no event on a master page that happens after view state is populated but before the main page's Load event.
I suppose each page could have an InitComplete or PreLoad that calls a function to populate these fields, but that seems really clumsy. The call would have to be in every page. And it would have to be in every page even if that page never used this data, because the master page uses the data for its own purposes.
Is there a way to do this? Maybe view state is not the right place to save the data? I could store the data in Session variables, but then on not-postback the data in them would be left over from the last call. I guess I could make sure to clear the obsolete data, but that seems really clumsy.
I'm writing in VB but I wouldn't think that would make a difference here.
You could override LoadViewState method of the master page in the following manner:
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
// use the loaded values, if any, here
}
LoadViewState is called before Load and in fact even before PreLoad of the page, so that seems to fit what you are looking for.
Related
I have a session variable that changes some things about how a page looks. I have a button that changes the value of this session variable. But ... the onClick event happens after page load, so by the time I update the session variable based on the button click, it's too late, the page has already been loaded.
Theoretically I could put all the logic about changing the display into a function and call it from page load, and then call it again from the onclick after the variable as been updated. But this is impractical: there are many user controls that check the value, used on many different pages in different combinations. I would have to hard-code the list of user controls on each page, and if someone added a new user control to a particular page, they'd have to remember to update this function, which is lame.
Is there a way to force a page reload? (I can use response.redirect back to myself and it works. If all else fails I guess this is what I'll do. But it means an extra round trip to the server, which is clumsy.)
Is there a way to process the onclick before the page load?
Some other magic solution?
If you have to change the look and feel of a page based on a specific value which can change, then you should have dedicated functions that set up the look and feel in a single unified place, and then you call those functions in every case where a value that affects the look and feel is called.
Examples:
private void SetDivVisibility()
{
// display logic here based on variables
}
private void MyControl_Click(...)
{
myvalue = blah;
SetDivVisibility();
}
It helps to bear in mind that the actual rendering of the page is last thing that happens, after both page load AND event processing.
Theoretically I could put all the logic about changing the display into a function and call it from page load
That's how you should do it. Cleanup your logic and markup - refactor and keep it DRY. That should help.
I can use response.redirect back to myself
That's the other option. Yes, a round trip is nasty.
you may put your code of styling your page in a void called by the page_load normally and called again from buttonclick
or call response.redirect to same url
or even onClick is client side use window.location.href
A design with a layout predicated on the existence of a session variable which won't exist until after it's been render is a huge design error. I like to call it the "Chicken or the Egg" syndrome. (yes, you can quote me.. ;)
I'd argue that your controls shouldn't get their layout completed in the on render. Instead, use a method (similar to databinding) where you can "rebind" the controls with the new session value on demand. This method would show/hide things based on the updated values.
I'm currently working on a dynamic core for several webprojects. It has a core that uses a treeview and a menu. And then for each specific projekt it loads several different wuc into a maincontent. Some business projects use business related wucs while others uses different ones. So the span of wuc's is really big.
Now to my problem, whenever a user press a menuitem or a treeitem it loads a wuc to the maincontent linked to that object.
But I'm having some viewstate errors and i've been looking around for 2 days now and none of the solutions explained are working for my projekt.
All my wuc has to have viewstate enabled.
Cycle is ->
Page(Control A) does postback with variable to change control to ControlB in wucPanel(UpdatePanel).
OnLoad LoadRequested Wuc.
Current code is
protected void Load_Page(object sender, EventArgs e)
{
//Code to decide which wuc to load.
UserControl wucc = (UserControl)Page.LoadControl(sFilePath);
ParentControl.ContentTemplateContainer.Controls.Add(wucc);
}
I've tried several fixes like adding diffrent ids to the wuc, but this either disabels the internal functions of control like handlers etc or generates the same viewstate error.
One solution i found was to load ControlA and then just removing it and then load ControlB. But this disabled the scripts for my 3rd party controller (Telerik).
I've also read about having diffrent PlaceHolders for each typof but since i expect havign up to 50 diffrent Controls I don't feel this is gonna help me.
And moving from Page_Load -> Page_Init generated the same error.
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.
In your case Anders, you still need to add the old control to your page in the init method along with the new control that you now want to add. Keep a reference to this old control that you have just added in a class level variable. So something like
Control _oldControl = null;
protected void Init_Page(object sender, EventArgs e)
{
//Code to decide which wuc to load.
UserControl wucc = (UserControl)Page.LoadControl(sFilePath);
ParentControl.ContentTemplateContainer.Controls.Add(wucc);
_oldControl = wucc as Control;
//Now add the new control here.
}
//override the LoadViewState method and remove the control from the control's collection once you page's viewstate has been loaded
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
ParentControl.ContentTemplateContainer.Controls.Remove(_oldControl);
}
Hope this helps. If it did, please check the checkbox next to this answer to accept it and vote it up if you like :)
In order to avoid ViewState related errors please make absolutely sure that in Page_Init you create the same control tree that was created the previous time ViewState was saved i.e. the previous postback. Simple page life cycle:
Page Init - create the control tree
- View State is loaded and applied here
Page Load - already loaded view state, you can do modifications to the control tree here
- Save View State
Page PreRender
For what it’s worth I recently had the same problem.
My scenario was as follows.
A fixed panel of filters (dropdown lists and textboxes) which built a search SQL string. On submission of the search consequent results were displayed in an editable gridview beneath.
On editing the gridview I cold effectively change the state of a database record thus removing it from the gridview under the filters previously chosen. In some cases this resulted in no results being returned thus causing me to hide the gridview.
I then found that if I used the new state of the record in the filter and resubmitted the search that error sometimes occurred.
The problem I eventually found had nothing to do with enabled viewstates etc but simply that the empty gridview, though no longer visible (changed programmatically), had not been rebound to a null datasource.
This appeared to cause the conflict and the error.
So it appears as though in my case the viewstate issue arose from a non-visible gridview that contained non-refreshed data.
I have a UserControl A that has to be loaded first and after that completes loading, I need to load a UserControl B.
I prefer to add both these user controls on the page at compile time (would like to avoid dynamic loading if possible).
If I add user controls on the page at compile time and set visible to false for User Control B, does it still execute the B's code behind? I can then set the visibility to true after loading User Control A
Should I be using events/delegates for notifying the completion of loading User Control A?
Don't load everything in the page event in control b, just put a method on control b to be called. Then add an event to control a which the page consumes, when the event is raised, call the load method on control b.
Edit: SampleCode
Ok so for example, create
a ASPX page
2x user controls
Put both user controls into the aspx page.
<cc:control1 runat="server" id="control_one" />
<cc:control2 runat="server" id="control_two" />
Then in control 1, create a delegate and event.
public delegate void MyCustomEvent (EventArgs args);
public event MyCustomEvent MyEvent;
protected void Page_Load(object sender, EventArgs e)
{
MyEvent(e);
}
So I have the event raised on page load. So you would have your logic in there thats required, when your done, calls MyEvent event.
In the page you want to add a handler for that event so when it's called you can do something with it.
protected override void OnInit(EventArgs e)
{
control_one.MyEvent += new WebUserControl1.MyCustomEvent(control_one_MyEvent);
base.OnInit(e);
}
void control_one_MyEvent(EventArgs args)
{
control_two.MyCustomLoad();
}
So when the page is initialized I add the event handler. In the event handler I call a custom method on the second control to load stuff.
Then in the second control I have:
public void MyCustomLoad()
{
//Stuff only loaded when event is raised and calls this method.
}
So this allows control 1 to load something, say it's done, when the page knows it's done, it can tell control 2 to do something.
Edit: After discussing this with a friend I'll explain what I mean by controlling the order.
You cannot control the order of page-life-cycle events. i.e: You can't have Control A, run through all it's page-life-cycle events, then once it's done, have Control B run through all it's page-life-cycle events.
If you do-away with the page life cycle, you can do a degree, as my example above shows, create a way of controlling the order in which the controls are rendered. By raising an event(s) at certain points when Control A is finished, you can tell Control B to do something.
The intermediate between the two controls is the page which handles the events raised by Control A which calls a method on Control B. You (well you can hack around to do it) can't specifically make Control A tell Control B to do something because that creates a direct dependency between the two controls which is bad.
Yes, the code behind will still run
Events could be useful
But if your controls have a specific dependency on each other, maybe they should just be a single control?
This is a fatally-flawed design. You should design your UI so that it doesn't matter in what order the controls load. The order in which controls load is outside of your control.
To address "Phill's" issue with an Order/Orderlines control pair:
I assume that the Order control was developed because it's useful by itself. I assume that OrderLines was developed to be able to show the line items for a given order displayed by the Order control.
I contend that there should be a single, composite control which combines Order and OrderLines. this control will pass to the OrderLines control, a DataSource consisting of the line items it is to display. This makes OrderLines independent of any other control - it simply displays the data it is told to display. It has no idea where that data came from.
Note that this can extend to a typical grid / detail / detail lines scenario, where you pass the grid a set of orders; when selected, a particular grid row will pass the Order control the selected order; when its' time to display the line items, pass the line items collection of the current order to the OrderLines control, etc.
This leaves each control with nothing to do but the Single job it is Responsible for.
"I have a UserControl A that has to be loaded first and after that completes loading, I need to load a UserControl B.
I prefer to add both these user controls on the page at compile time (would like to avoid dynamic loading if possible). "
I would suggest using WebFormsMVP: -
http://webformsmvp.com/
http://wiki.webformsmvp.com/index.php?title=Main_Page
As well as being a good implementation of Model-View-Presenter, one of the most useful features of this framework is its support for Messaging.
In a nutshell, you create Message Types, and your usercontrols (views) can post messages of whichever type you need to a message bus. Other controls can subscribe to messages of a particular type and the WebFormsMVP framework will ensure they are delivered.
This allows you to handle interaction between usercontrols by messaging publish & subscribe, without worrying about which order they load in.
Your only other option is to use dynamic control loading because, as others have pointed out, you can't rely on the order in which ASP.NET loads controls into a container.
I am working on a project which creates controls dynamically for a form in the page_load event, loads in their current values from the database and saves their values (using FindControl) when the user clicks the continue button.
When I added a control statically in the .aspx page and followed their same procedure of loading the value in the page load and saving it on the button press I found that the value would not save correctly. It seems that it wouldn't save because the click event fires after the page_load, so the page_load of the post back reverted the value and the user entered value was not saved.
The strange thing is that by changing the control to be dynamically created just as all the other controls on the page and keeping the loading and saving the same it now works. Even though the page load still creates the control with the old database value.
It seems like a very fundamental asp .net feature here but i'm just unclear as to what is going on. I suspect it is to do with the timing of creation and maybe when the view state kicks in.
Static page controls are created just like dynamic page controls. The difference might be coming in your Page_Load. Whenever you postback all the controls are created afresh which means they are created with their initial values. This happens because after creating the controls asp.net throws away the controls/objects.
So, when the request comes, the first thing that asp.net does it to recreate the controls by looking at their definitions (in the designer files). On each postback they are created and initialized again losing their state in the process.
But after creating the controls Asp.Net loads any viewstate that is sent along with the request which makes people think that the state is always saved at the server.
What might be happening is that either the viewstate is not enabled for your control (in case they are created in designer), in which case you may try using EnableViewState property to true of the control.
Or, when you're doing a Page_Load, you're forcefully re-initializing everything. And in process losing all the control data. If you could post the logic of Page_Load, it might get clarified.
Make sure that:
you are not setting the value again for the static control in Page_Load. The dynamic control are probably getting around it by grabbing the ViewState and form values at a different stage in the lifecycle.
The dynamic controls are added After the static control. Or at least they are added in a different container. Placement in the control's collection can affect the ViewState, although it doesn't look like your scenario / since what you mention seems to be more about the values in the current post.
The save is happening After the Page_Load in response to the corresponding event.
I've run into similar problems in the past (quite a few times actually), but what helped me the most is understanding the ASP.NET Page Lifecycle.
Microsoft has an article on it which describes it pretty well, but this post by Solomon Shaffer really cleared up everything.
I suggest reading them both and coming back with additional questions regarding to a particular state, when to load/save data etc..
Hope this helps.
Marko
Note that you may want to use Page.IsPostBack property to avoid reinitializing values on button clicks and other events.
private void Page_Load()
{
if (!this.IsPostBack)
{
// Assign values to the controls.
}
}
Previously Called: How to deal with dynamically created controls under load in aspx
in response to a question below: the information required to determine which controls to restore is contained in a dedicated viewstate object.
I am dynamically creating controls in the codebehind page - these controls are all hooked up to click handlers so when a postback occurs I must re-create the previous set of controls, then clear the controls down and generate the new set of controls based on the previous click.
This is coded and working correctly under normal circumstances esentially as follows:
in Page_Load
if not postback generate default buttons
else if postback re-generate buttons that were shown on last page
in click_handler
Clear the dynamically generated buttons created in the Page_Load
generate new buttons based on the specific click being handled
however when the server comes under load we start getting problems:
With 5 users per second we start getting the exception:
Multiple controls with the same ID 'add0' were found. FindControl requires that controls have unique IDs.
With 100 users per second we start getting the exception:
The control collection cannot be modified during DataBind, Init, Load, PreRender or Unload phases.
Once this occurs all subsequent requests get the same error and IIS has to be re-started.
What could be causeing this and how can I avoid it? Do html requests possibly overwrite and interfere with each other when under load? do objects somehow hand around after a page unload in a manner that would allow the next page load to trip over them?
How are you storing information about the controls you need to restore? If you are using ViewState or ControlState, then I don't see how load could affect things. That's how any of the composite controls do things.
I will say that I saw your second error while using the Infragistics UltraWebGrid, and never was able to track it down. From the call stack, it appeared that EnsureChildControls was being called during the Load phase (or maybe LoadViewState).
A private static variable was being used to store a dictionary of names and table cells so that table cells would not get re-created during the page lifecycle.
The key point is that it was marked static - it should have been an instance variable - the end result being that under load when requests started backing up then multpile requests were sharing this static dictionary.
exactly what happened i'm not 100% sure - but under medium loads FindControl would find multiple controls of the same name, under very high loads it seems one request would try to modify a control (probably add to it) while it was in an invalid state from the other request.
End result - if you dont really know what your doing - prefer instance variable sto static variables.
Everything you have written seems to be correct and doable. Most likely this is an issue with your control generation code. Perhaps if you post some of that we can better find a solution.