Viewstate and controls in ASP.NET - c#

I posted a question a couple of days ago about viewstate and after running some tests I have come to some conclusions/results. Based on these results I have a few questions as to how someone would do certain things.
Here are the results of my tests that I ran:
If usercontrolA is loaded from OnInit of a Page, then his viewstate will be available in OnLoad. All other controls that usercontrolA loads from it's OnInit, will have their viewstate ready in their OnLoad.
If usercontrolA is loaded from OnLoad of a Page, then his viewstate will be available in OnPreRender. All other controls that usercontrolA loads from it's OnLoad, will have their viewstate available in their OnPreRender.
If usercontrolA is loaded from an event (Example: button click. Events fire after OnLoad and before OnPreRender) of a Page, then his viewstate will not be available. All other controls that usercontrolA loades will not have their viewstate available.
So in a perfect world you would always load all controls using situation #1, so that their viewstate is available on their OnLoad. Unfortunately when you need to load a control from a button click or from a OnLoad, is there no way for control to get its viewstate before OnPreRender stage?
I have read a bunch of articles on viewstate and thought I understood it, but working on my current application which loads usercontrols which load other usercontrols, I am having a real hard time with being able to get viewstate on my leaf (last in the chain) usercontrol.
Any suggestions and/or links are appreciated.

I don't think I can add anything that this article doesn't cover.
Look specifically at the Life Cycle Events section.
http://msdn.microsoft.com/en-us/library/ie/ms178472.aspx

It is accepted practice to load dynamic controls in OnInit, so that they get the full control lifecycle. I'm not sure I particularly understand your situation though - if you're loading a control based on a button click, why would it have viewstate at that point? On the next OnInit, you should load the control again (I usually use a page level Viewstate item to track that a particular control needs to be loaded) so that it can restore from Viewstate. Something like:
class Default : Page {
enum LoadedControl { Textbox, Label, GridView }
override OnInit() {
if (IsPostback) {
var c = Viewstate["LoadedControl"] as LoadedControl;
if (c != null) LoadDynamicControl(c);
}
}
void Button_Click() {
var c = (LoadedControl)Enum.Parse(typeof(LoadedControl), ddl.SelectedValue);
LoadDynamicControl(c);
}
void LoadDynamicControl(LoadedControl c) {
switch (c) {
case LoadedControl.Textbox:
this.ph.Controls.Add(new Textbox());
break;
...
}
ViewState["LoadedControl"] = c;
}
}
The slightly more interesting bit, though, is that according to catch-up events - it really shouldn't matter. The callstack for dynamically loading a control looks something like:
Control.Controls.Add(Control)
Control.AddedControl(Control)
Control.LoadViewStateRecursive(object)
Control.LoadViewState(object)
Taking Label as an example, it overrides LoadViewState and pulls it's Text property directly from ViewState. TextBox is similar. So, by my reading, it should be OK to add at any point, and then access ViewState. That doesn't seem to be jive with my experience, though, so further investigation seems warranted.

I'm surprised but interested about your results. When I work with dynamic controls I always add them in Page_Init. Anything else doesn't work. But you are right - how do you do it if you are adding them in response to a button click.
The only way I have found is by examining Request.Form("__EVENTTARGET") collection at PageInit. This contains the control ID of the control that has triggered the postback so for instance a button click. It will of course be qualified by the naming containers it appears in. Once you have identified the 'event' by this method you can add the controls you want.
It is of course all a bit hacky but it's the only way I found of doing these things. It does work.
It's interesting that the ViewState is available on PreRender if you add the controls at Page_Load. But as the above link indicates it too late to help you then. The controls state is rehydrated during the load cycle. If it's not there then your control state or dynamic controls are just going to disappear.

Did you try to use LoadComplete event?
Use this event for tasks that require that all other controls on the page be loaded.
This is fired after PageLoad and all events (ButtonClick, etc.), so your UserControls are are loaded in ButtonClick events, and in LoadComplete their ViewState is already initialized.

Related

onclick event of dynamically added button

I need to add onclick event to some dynamically added button, but when i click the button, the onclick event is not fired. I saw some solutions to this, like link which says I should create the controls and attach the event on page_init or page_load every time there is a request, but, will this make the website very slow if I have a lot of controls to add?
Yes, you must recreate dynamic controls if you want to access them and have their events fire on postback.
If you have so many controls on a page that your site is slow, you have a design issue. Rethink your design so you do not have so many controls on one page (perhaps several pages/tabs?).
Allocate memory like below to button in Init Page Event
Button b = new Button();
b.Click += new EventHandler(b_Click);
void b_Click(object sender, EventArgs e)
{
}
As told by #Oded, this approach can create Design issue.
When you have a Div like control IN YOUR PAGE which is supposed to consume this Button, then you can GIVE CSS styles to give it proper alignment and proper placement.
You have two option.
Add control at Runtime
Add control at Design Time
Definitely, site will go slow in case of many controls in both cases. As both options will take memory BUT, By End of the Page Life Cycle, all the controls will get disposed.

Loading ASP.NET user control at run time

I have an ASP.NET user control with a button, and I want to add it to the page when the user clicks a button from another user control. I have created an event handler to the first user control to handle it from the page and add the second user control to a page. Everything is working normally, but the button on the second user control doesn't respond to the event.
I place the second control on RadAjaxPanel
Note: When I add the second user control at design time its working fine.
All dynamically created controls should be added by the end of Page_Init (though sometimes you can get away with them added by the end of Page_Load).
If you're only adding them based on a button click event then you've done this in the event handers which fire AFTER Page_Init and Page_Load in the lifecycle - This is why your events don't fire and why it works fine when you add at design time.
This is because when a button is clicked on the second user control - the whole page lifecycle starts again. The page goes through Page_Load and Page_Init first and your control doesn't get loaded here. So, when the page lifecycle handles the "handle postback events" part, the control no longer actually exists, so the event doesn't fire.
Conversely, when you add at design time, the control exists in Page_Init and Page_Load so is able to handle the postback events from the user control because it already exists in the control tree - if this makes sense.
You need to think how you can restructure so they're added by the time Page_Load has finished at the very latest or it won't work. Without code samples or more detail it's hard to suggest exactly how you might do this. One possibility would be to set it visible instead of loading it outright - but if the control does some 'heavy lifting' on load like database hits or API calls then this might not be suitable for you.
I did something similar. What I did was to load some controls dynamically based on a selection from a DropDownList. If you have a method which loads the control for you, let's call it LoadControls(), then you can do something like this:
DropDownList_Click {
ViewState("LoadControls") = true;
LoadControls()
}
By setting the ViewState variable, you can then indicate Page_Load to load the controls on future postbacks:
Page_Load {
if (ViewState("LoadControls") == "true")
{
LoadControls();
}
}
This has the effect of then loading the control on-the-fly when the event first happens, and then at future times in the lifecycle.

How to ignore viewstate of a previous request for particular control?

I am dynamically generating controls, and sometimes I want to create a control and have it ignore the viewstate. For example, sometimes the user has clicked a button indicating they want a different form loaded, so the control tree I generate on postback is different from the original control tree. This is fine, except when I call Controls.Add then it tries to load the viewstate form the old controls into the new controls if the control tree structure is similar, and I want them to instead ignore that viewstate(and also ignore the postback values for input controls as well).
Can I do something like set the IDs of the controls or something that would allow me to conditionally prevent them from getting the viewstate/postback data of the previous request?
Edit: If I let the user of the control load the form on demand in postback handler, the postback data is not applied when I call Controls.Add(this really seems like a flaw in ASP.NET, because I would think if you're going to apply viewstate data "after the fact" through Controls.Add, it'd seem you would then apply the postback data automatically as well after the viewstate data is loaded). The real problem I run up against is my control is very dynamic, but the user of my control can't really tell it what to do until their postback handler fires, because one of the things a user can do is select different forms to be loaded via some link buttons. So it's not until the postback handler runs that they know what the uesr requested, and thus can ask my control to load a certain form. So I have to ask them to do convaluted things like saved the formID that identifies the last form to a session variable, and in OnInit they tell my form what the old formID was via a property. My control then loads the form in OnLoad so that it can consume the viewstate and postback data, and later in the programmer's postback handler, they can choose to clear the form and load a different one if they want.
Edit2: FYI Generating IDs for each control unique to the form works great, so I thought I could eliminate the pointless loading of the old form until the programmer requests a form be loaded in his postback handler. But as I mentioned above, what I found was that loading the form after postback data handling has occurred means that data is lost. Whereas viewstate gets loaded via Contorls.Add, playing catch up in the page lifecycle, it seems postback data does not! So it seems I am defeated at every turn.
You're going to avoid problems if you play along with the control lifetime. Basically, whenever you have a control that renders, it's best to ensure that control is recreated on the next postback, even if you aren't going to need it anymore. The first goal of a postback should be to restore the previous state -- only THEN do you make changes to it.
I described it best in this answer:
Wrong state in Server Controls with same ID's when dynamically adding UserControl to UpdatePanel
Giving the controls different ID's would certainly prevent ViewState from being loaded, that would be one way.
You may also be able to manipulate the ViewStateMode property of your controls by setting it to "Disabled". I'm not sure if this prevents it from loading (it definitely prevents them from saving viewstate), but you could try it.
Have you tried just calling controls.clear prior to adding in the new ones?
UPDATE
I'm starting to believe that you are generating the controls at the wrong point in the page lifecycle. What is your flow?
You must be generating dynamic controls on postback in pageload:
protected void Page_Load(object sender, EventArgs e)
{
--Generate Dynamic Controls--
}
You need to do it like:
protected override void OnInitComplete(EventArgs e)
{
base.OnInitComplete(e);
}
protected void OnInit()
{
--Generate Dynamic Controls--
}
protected void Page_Load(object sender, EventArgs e)
{
}

Programmatically adding a user control in ASP.NET

I have a user control that needs to load a child control when a button is clicked.
The trouble is that it has to request the control from another class.
So in the button click event, I call the function to get me my control, and add it to the page, like this:
UserControl ctrl = ExampleDataProvider.GetControl(some params...);
myDetailPane.Controls.Add(ctrl);
The GetControl method looks like:
public static UserControl GetControl(some params...)
{
ExampleDetailPane ctrl = new ExampleDetailPane();
ctrl.Value = "12";
ctrl.Comment = string.Empty;
return ctrl;
}
This isn't working due to the page's lifecycle - the Page_Load of the child control gets fired and its controls are null.
I kind-of know that my approach is wrong and why, but don't know the best way to go about fixing it! Could anyone help?
Dynamic controls must be re-created on every postback, this Article is a good link about how to persist dynamic controls and their state.
If you want to access your control in PostBack or you want to bind Event, you have to create them in CreateChildControls() method.
private UserControl _uc = null;
/// <summary>
/// Creates all controls.
/// </summary>
/// <remarks>All the controls must be created in this method for the event handler</remarks>
protected override void CreateChildControls()
{
_uc = new UserControl ();
this.Controls.Add(_uc);
base.CreateChildControls();
}
Create your control in Page_Init. Then make it visible on your Button_Click event.
(CTRL+C/CTRL+V from some other question I answered last week):
Everything that has to be maintained between page cycles should be declared in Page_Init, not Page_Load.
All the initialization, like adding event handlers, and adding controls should be added during initialization, as the state is saved between page cycles. Handling with the content of controls and the viewstate, should be done in Load.
Check also http://msdn.microsoft.com/en-us/library/ms178472.aspx.
Init
Raised after all controls have been initialized and any skin
settings have been applied. Use this
event to read or initialize control
properties.
.
Load
The Page calls the OnLoad event method
on the Page, then recursively does the
same for each child control, which
does the same for each of its child
controls until the page and all
controls are loaded.
Use the OnLoad event method to set
properties in controls and establish
database connections.
Two possible approaches:
Have the control in the page but not loaded with anything until they click their button. Then you can populate the values in the control. This has the benefit of being within the page life cycle. Note, you can always use the "display: none" style setting for the that your user control is in. Then, as part of the OnClick for the button, you can reveal the div making your control visible.
You could pop up another window, though obviously this has the potential for being blocked by popup blockers.

.net Page events firing order changing

Ok this is a really annoying bug that I have been having issues with all morning!.
I have a custom control that we have used on many project that has properties that are set and stored in Viewstate by the calling pages onload. the control sets up childcontrols with propertes on the CreateChildControls() method of the custom control.
Normally as usual on a postback the Page_Load event is fired then the CreateChildControls method of the control on the page is fired.
The strange thin though is we have a login system (custom membership provider) on the site and when a user is logged in the opposite happens first the CreateChildControls() method fires then the Page_Load so the control properties are wrong (set from the previous postback)
How could the events be firing in a different order? I thought all page events happened in the same order no matter what and I don't see how being logged in would change that order.
UPDATE: It seems the issue is I'm not calling EnsureChildControls() but I'm not sure where it should be called? If several properies are set on the control which are used in setting up the child controls when should I call EnsureChildControls(), I guess I don't fully understand what EnsureChildControls() does?
CreateChildControls is called whenever the ASP.NET page needs them. There is no specific point in the page cycle for that. It can happen in the Init event, it can happen in the Load event. If you want to make sure your child controls are available, then call EnsureChildControls() method of your control. You can do that in the control's Init event to make sure you have child controls through the whole lifecycle or you can do it whenever you need a reference to one of the child controls - e.g. in the getter/setter of a property of your control.
When creating properties of a server/user control that need access to contained child controls I use the following:
public Whatever SomeProperty
{
get
{
EnsureChildControls();
<more code here>
}
set
{
EnsureChildControls();
<more code here>
}
}
This ensures your control consumers are free to work with your control at various stages of the page lifecycle.

Categories