There are plenty of similar questions on SO (1, 2, 3, 4 etc) and on the web (1, 2 etc) but none of the corresponding answers clarifies my situation.
I have a simple custom user control consisting of two drop-down lists. Selection of a value in one drop-down list should lead to the populating of the other one. As long as I declare the user control in the .aspx code everything works as expected.
Now I want to add the user control on the page programmatically (on button click). Although the control is being added, the selection in one drop-down list causes only a postback but no action of the other drop-down list.
While debugging I've found out that not only OnSelectedIndexChanged does not fire, but also OnLoad and all the other events.
Common reasons for such a behaviour regarding all the discussions I've looked through are the following:
AutoPostBack of the DropDownList is not set to true or the databound DropDownList is being rebound on each postback which causes the losing of events. //not a case here, more likely refers to dynamically added drop-down lists
ID is assigned to the dynamically added control automatically on each postback (and every time a different one so that ViewState is not persisted correctly and events do not know that they should fire). // ok I've checked it and now assign the ID manually
The control is added only once (as opposed to the addition on every postback which is necessary because in the ViewState only the state (values) of server controls are stored, but not the controls themselves) and/or
the control is added on each postback but too late in the Page Life Cycle. //ok I'm adding my control in OnInit event handler now
In order to let the page know that the control was added (and how many of them) I use Session. Below a bit of code and then finally the question :)
.aspx:
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
</asp:Content>
and the code behind:
protected void Page_Load(object sender, EventArgs e)
{
if(!this.IsPostBack)
{
Session.Remove("Childcontrols");
}
}
private void AddTransitControl()
{
List<Control> controls = (List<Control>)Session["Childcontrols"];
AddTransitPoint atp = (AddTransitPoint)LoadControl("~/UserControls/AddTransitPoint.ascx");
string id = this.ID + "_eb" + (controls.Count).ToString();
atp.ID = id;
controls.Add(atp);
PlaceHolder1.Controls.Add(atp);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (Page.IsPostBack)
{
if (Session["Childcontrols"] == null)
{
Session["Childcontrols"] = new List<Control>();
}
List<Control> controls = (List<Control>)Session["Childcontrols"];
int count = 0;
foreach (Control c in controls)
{
// AddTransitPoint atp = (AddTransitPoint) c; //mystically not working (fires no events)
AddTransitPoint atp = (AddTransitPoint)LoadControl("~/UserControls/AddTransitPoint.ascx"); //it is working!
string id = this.ID + "_eb" + count;
count++;
atp.ID = id;
PlaceHolder1.Controls.Add(atp);
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
AddTransitControl();
}
(I'm pretty sure that the code of the user control itself is not really relevant for the case but on request I can add it later).
So now the question: Through trial and error I've found out that if I store the newly added control in the collection in Session and OnInit just take the control out of this collection and add again to the control collection of my placeholder, no events of this control fire on the next postback (independent of the way the postback is called). Other way if I create OnInit a new control for each stored in Session and add to the placeholder control collection this newly created control - everything works! So what is wrong with stored in the Session controls and why do they lose their events?
And one more small question here. What is the best practice for creating IDs for such controls? I use a string of particular format along with a counter, but I doubt it is the best way to do it. For example if I added different types of controls I would face trouble with this method.
Thank you everybody for reading such a long question and for your valuable input!
My understanding is this: events must be re-established on every postback. You get this for free if the event is defined in the the ASPX file, because those attributes (OnClick, OnSelectionChanged, etc.) are re-processed when the page object is instantiated.
You do not get this for free with dynamically-built controls, or controls which are defined in the ASPX file but have their events wired-up in the codebehind. For those controls, the events are not known at instantiation time, thus the page can not automatically re-establish them for you. For those controls, you must re-establish your events on every postback.
My understanding of session-stored data is: when you put an object in the session, that object itself is not kept alive in memory. Instead, it is serialized, and the serialized data is stored in the session. When you get the object back out of the session, it is deserialized.
Objects' events do not survive the serialization/deserialization round-trip because they are logically function pointers - so when the object is removed from memory and later re-built, the entire memory "landscape" has changed, so those events/pointers would no longer be valid.
I haven't done a lot of work with dynamically-generated controls, so I can't tell you the most effective pattern to managing them and their events. But I know that storing them in the session isn't really buying you any benefit over re-creating them on each postback; and it is bloating up the session memory unnecessarily. Hopefully some other SO'ers on this thread can point you to the best-practices way of managing them.
The controls events don't work when stored in the session because they are now referencing an instance of the page which no longer exists.
Let me rephrase what I think you are saying (after a brief look at the code):
When I add a user control (ASCX) to a page dynamically, the controls on the user control do not fire.
If so, the code for the user control is relevant, because it should be handling the events fired from its controls.
If not, then you wire up the server controls (Microsoft created or you created) with delegates to handle the event, as you are missing the "I'll do the magic for you" step when dragging and dropping a control on a page.
Am I at least somewhere close to the target?
You will have to recreate the viewstate within the control to solve the problem
Related
I have a set of programmatically added user controls on the page (they are created and added during the Init event of the main page). Each of these control programmatically add buttons during his own Init event.
(So it's "two levels" of dynamically created controls. Don't know if it matters.)
But I need to access the viewstate while creating those buttons (I use the viewstate to remember user data).
protected void Page_Init(object sender, EventArgs e)
{
// I need my ViewState right now !
Button myButton = new Button();
myButton.Text = "Click me";
myButton.Command += new CommandEventHandler(myCommandHandler);
myButton.CommandName = "Delete";
myButton.ID = "myButtonID";
myPlaceholder.Controls.Add(myButton);
}
I understood that the viewstate isn't available during the Init event. But buttons don't fire their events (OnClick, etc.) if they are created after the Init.
How to create dynamic controls and access the viewstate at the same time ?
I don't think you can. Not ideal but I would put the user data in the Session in this instance. Dynamic controls are difficult to work with and I think this is one of the trade offs. ViewState loads after Page_Init - this forum post confirms
Another option might be to store the information in your own hidden fields. These will become available in the Request.Form collection during postback and will be available. Of course if you make them runat="server" (which you will probably have to) the framework will append all the naming container ids to the name so you will have to be a bit clever when digging them out. It's going to get a bit hacky though so I would be tempted to stick to the Session
Someone else might know different of course.
I add a custom user control to my page, with textboxes and radioboxes, but after each postback, the contents are removed and I don't know how to read the values that were entered in it.
From what I've learned you have to add the user controls on each refresh, but that does not restore their state. I want the values to be stored in the ViewState but that does not happen for some reason.
Note: I need to add multiple user controls on one page, so I need to identify each user control, either trough an ID or a linq expression that selects that user control from my page.
Im programming with C# 4.0
How I add my user control:
private void AddInstrumentDetailToPage()
{
RMAItem lItem = (RMAItem)Page.LoadControl("/Controls/RMAItem.ascx");
InstrumentDetail.Controls.Add(lItem);
}
I save the amount of controls I have in the ViewState, and call AddInstrumentDetail() that amount of times, but they appear to be empty. I've read somewhere that I have to add it in Page_Init because view state is not initialised yet, but that doesn't help either.
You need to recreate dynamically added (User-)Controls at latest in Page_Load to maintain ViewState. So you should store the number of already added controls in a ViewState-Property and according to that reload them in Page_Load. You have to sum up this variable with 1 in AddInstrumentDetailToPage.
MSDN: Dynamically Adding User Controls
If you store the number of added controls in a Viewstate variable, you cannot recreate controls in Page's Init-Event because the ViewState-Variable would yet not been reloaded there.
You won't have any ControlState unless you make a PostBack. Refreshing your browser/requesting the same link will just be a fresh start.
By default, any control added to your page are data stored in ViewState and ControlState for your UserControl.
If you are willing to save your contents, I would suggest you save it on Request.Session.Add("yourKey", "yourValue").
You can access your session with var yourValue = Request.Session["yourKey"]; on your protected void Page_Load(object sender, EventArgs e) method.
Specify an ID for your RMAItem instance, otherwise storing data on ControlState might not work!
private void AddInstrumentDetailToPage()
{
RMAItem lItem = (RMAItem)Page.LoadControl("/Controls/RMAItem.ascx");
lItem.ID = "rmaItem1"; //<-- This is important
InstrumentDetail.Controls.Add(lItem);
}
Hope it helps!
I find this as a funny little problem. I think the reason lies behind the life-cycle of page-object/events-generation, but the question is how I come around the problem?
In Default.aspx there exist some funny controls but also a
<asp:PlaceHolder runat="server" id="phUserContent"></asp:PlaceHolder>
This placeholder is empty until runtime. Code behind are, in some circumstances, loading UserControls into it. Like this
Control ctr = LoadControl("~/UserControl/Note.ascx");
phUserContent.Controls.Add(ctr);
This Note.ascx contains some interesting controls and finally a LinkButton that fires an event. The LinkButton-code are very easy and gramatically correct,
<asp:LinkButton runat="server" ID="lbUpdate" OnClick="lbUpdate_Click" Text="Update"></asp:LinkButton>
In the Code behind for the ascx I have the code for the event,
protected void lbUpdate_Click(object sender, EventArgs e)
{ ... }
As I wroted, the postback occurs, the page are regenerated as I would suspect - but without the lbUpdate_Click event to be executed. A break-point is of course tried.
I'm looking for two possible scenarios. One is that I missed something really easy (you know, like wroted in wrong code behind file) or that I missed an important part of the Page Generation Cycle.
I'm mostly into the second, like this (i just think here..)
1. Page (ascx) got it's changes
2. Submit was clicked
3. Ascx was re-generated
4. Events was cleared but was exist and doesnt cast error.
5. After reload, initial content was reloaded
The effect would be that the compiler can't see the breakpoint and the values was never saved due to a "execution of an empty event". But this is just a amateours guess, please advice me.
[UPDATE AS PER ANSWER]
This is how I was solved it, based on the acepted answer below.
List<Control> ctr;
public User()
{
ctr = new List<Control>();
}
protected void Page_PreInit(object sender, EventArgs e)
{
ctr.Add(LoadControl("~/UserControl/Note.ascx"));
}
protected void Page_Load(object sender, EventArgs e)
{
ctr.ForEach(d => phUserContent.Controls.Add(d));
}
Shortly..
1. The class got a list of Controls
2. In Page_PreInit (before creation) add UC (u can have X of them here)
3. In Page_Load (where all ctr are created) add each UC to the PH.
Which also make the events in the UC also working, no magic and no dumb complications :-)
It's a lifecycle issue.
Remember, every page request creates a new Page object, and new instances of all the controls on it. If you are dynamically creating a control, then it has to be done in the exact same manner on every postback. If you want the new control to fire an event, then it has to have the same id as the old one, and have the event hooked up to it before control events are processed in the lifecycle.
If you're creating the control dynamically at a point in the page lifecycle that occurs after ViewState is handled, then you'll have to manage your own state as well. In other words, if you're not dynamically creating the control during the PreInit phase, then you'll have to manually deal with restoring state.
Working on an ASP.NET 4.0 project, which uses user controls to dynamically generate a form based on definitions stored in the database. One form field would look like this:
<usc:RefControl ID="ctrlUser1"
ReferenceFieldId='product.user1'
ValidationFormat="^\d+\.?\d{0,2}$"
runat="server"/>
Behind the scenes the control emits a RegularExpressionValidator based on the RefControl.ValidationFormat property.
This works fine, however the problem is that this architecture only allows us to validate with regexes. Now I need to add date format validation, based on the user's preferences (which aren't dependent on their UICulture).
Reluctant to refactor the entire user control setup, I need to be able to pass a on-the-fly regex pattern to the ValidationFormat property. Currently I'm using a data binding expression to bind the property to a page instance method:
<usc:RefControl ID="ctrlUser2"
ReferenceFieldId='product.user2'
ValidationFormat="<%# GetUserDateValidationFormat()%>"
runat="server"/>
Code behind:
/// <summary>
/// Returns a regular expression that validates the current user's date format
/// </summary>
public string GetUserDateValidationFormat()
{
//...
}
Works okay on first page load, but on subsequent postbacks the validation doesn't work. I think the issue is that the data binding expression doesn't evaluate at all, but I'm not sure I understand why. I'm calling Page.DataBind() in Page_Init whether Page.IsPostBack or not, so shouldn't this work?
If you see I'm barking up the wrong tree, any alternative solutions to the same problem are also welcome.
EDIT
Managed to solve this problem. The issue was with the way ASP.NET page life cycle invokes the user control's events before the page's own events. The control values were being initialized before the data binding on the page could happen.
Because I still need to do the control initialization before Page_Load to subscribe the controls to viewstate, I simply moved the initialization logic to the Page.InitComplete event so the call to Page.DataBind() could get called first.
protected void Page_Init(object sender, EventArgs e)
{
Page.InitComplete += new EventHandler(Page_InitComplete);
}
So the event tree becomes
User Control Page_Init => Hook InitComplete handler
Page (aspx) Page_Init => Bind data
User Control Page_InitComplete => Initialize the control
Couldn't you just set the property in Page_Load()?
public void Page_Load(...)
{
ctrlUser1.ValidationFormat = GetUserDateValidationFormat();
// do whatever stuff you do
}
I am trying to build a server control that, depending on a "QuestionTypeId" display either a text box, date picker or Yes-No radio buttons.
I have my control displaying how I want it to, but when the submit button is pressed on the form, the text box, date picker or radio buttons that were generated in the RenderContents method are all null.
I have attempted to store the generated controls in view state, that stopped them being null, but the user inputs were not being stored.
I will post code if it is needed. Just ask.
I think you need to create (and add) the controls in CreateChildControls. This will mean you'll need to store the value of the QuestionTypeId in either Viewstate or ControlState (I'd argue that ControlState is applicable in this case, as your control can't work without this value).
When you add controls dynamically, you need to make sure they are recreated before the viewstate is restored.
I haven't done this in a while, but from memory I think you should recreate your controls in the OnInit method. This happens before postback data has been loaded and before the controls have their values set from viewstate.
It may be worth doing some reading on the asp.net page lifecycle:
http://msdn.microsoft.com/en-us/library/ms178472.aspx
You can create a user control and use server controls for textbox, datepicker, radiobuttons.
If you create a cusom server control then you have to add the posted data to your control properties. You can do this at your control OnInit event:
MyProperty = Request.Form("myControl");
A simpler method would be to create all the controls at design time and make the controls invisible based on you requirements.
Example code:
protected void Page_Load(object sender, EventArgs e)
{
txtBox.Visible = QuestionTypeID == 1;
chkBox.Visible = QuestionTypeID == 2;
}
If you do use dynamic controls you should do as David pointed out, save the value QuestionTypeID in ViewState or ControlState and then create the control you want based on that value.
(the control needs to be created every time the page loads even on a post back and they cannot be created later in the page life cycle then the Page_Load method if you want their ViewState persisted and recovered)
Example code:
protected void Page_Load(object sender, EventArgs e)
{
var questionId = ViewState["QuestionTypeID"];
if(questionId == /* Value to create TextBox */) {
var txt = new TextBox { ID = "txt" };
placeHolder.Controls.Add(txt);
} else if(questionId == /* Value to create Calender */) {
var cal = new Calender { ID = "cal" };
placeHolder.Controls.Add(cal);
}
/* Once the controls are added they will be populated with the posted values */
}
P.S.
It's always a good idea with dynamic controls to specify the ID.
You can save the added controls to member variables and use them elsewhere (after they are assigned)
You can subscribe to their events and if the user posted a new value your method will be called
I have followed your advice and done the following:
1) Question Type is stored in view state in my server control.
2) on CreateChildControls now creates a new instance of my control and adds it to a place holder on the page.
My problem now is that things seem to fire in bit of an odd order:
1)On initial load of page, create child controls is fired and the RenderContents method of my server control fires.
2)A button is clicked to load a new contact, this triggers create child controls and RenderContents is fired.
3)details are entered and save is pressed, this triggers Create Child Controls but RenderContents is not triggered and a NullReferenceException is generated by trying to access my control to get the value out. (If i skip the code that access my controls, RenderContents is called and renders.
Seconly, another issue is that when I try an set a value (onDataBind) I try to access the Text box that has been generated in my server control and get another NullReferanceExeption
Thoughts?