I understand the "why" controls vanish on postback, and up until now I have had great success just creating what I need to do dynamically in page init. However this fell apart for me when I had to add some controls to a asp.net page based on the value of an existing dropdownlist.
So my question is simple, and I don't seem to be able to find a good working code example. I need to add some controls to the page based on the value of a dropdownlist. Then persist these added controls across other postbacks (session is fine).
Here is a snippet to work off of:
protected void Page_Init(System.Object sender, System.EventArgs e)
{
RebuildPlaceholder();
}
protected void ddlGroup_Change(System.Object sender, System.EventArgs e)
{
ExampleDataContext ctxExample = new ExampleDataContext();
var aryExample = (from rslt in ctxExample.mvExample
where rslt.label.ToLower() == ddlGroup.SelectedValue
select rslt);
foreach (var objExample in aryExample)
{
TextBox txtCreated = new TextBox();
txtCreated.ID = "ddl" + objExample.ID;
plcExample.Controls.Add(txtCreated);
}
StorePlaceholder();
}
private void StorePlaceholder()
{
//Need code to store all controls in a placeholder.
}
private void RebuildPlaceholder()
{
//Need code to rebuild all of the controls from Session.
}
I found this related article: Dynamically Adding Controls but I am struggling with the syntax for serializing all the controls, etc.
This can be limited to the child controls of a single placeholder that already exists on a page, just storing/restoring that placeholder's controls is what I am after.
Any version of ASP.NET is fine, if there is something that made this easy in 4.0 great.
Instead try caching the dropdown list selection. Then during the next page load use the cache to set the value selected. Then load the new controls based on that selection.
Session["CacheKey"] = DropDownList1.SelectedValue;
Then to access the Session Cache:
var value = Session["CacheKey"];
Take a look at this Microsoft article
on ASP.NET Caching
I've found that DropDownList.SelectedValue is unavailable during Page.Init. But you can still get access to the value with Request[ddl.UniqueID] and then create and add all your dynamic controls.
It feels kind of like a hack, but the ASP.NET page lifecycle doesn't allow many alternatives, particularly if your controls are not serializable.
Related
From research, the answer to my question seems to be a resounding no.
From Rob de la Cruz's answer here and Jonathan Wood's answer here, it seems that the only way to do it is to use JavaScript. Sadly, I don't have the necessary skill level to implement their solutions and I'm not completely sure it will work for my situation anyway. See what you think:
What I have (using asp.net and C# in VS2019) is a treeview control which:
1.1 At the first level, displays the names of customers.
1.2 Expand a customer node and the next level displays a list of sales order numbers for that customer.
1.3 Expand a sales order node and the third and final level displays a list of the sale items belonging to that particular sales order.
Pretty standard stuff I should imagine. Now, also pretty standard is that, when a node is clicked a procedure will make visible a formview which displays information about that object. When a customer node is clicked it will display the customer formview. When a sales order node is clicked it will display a sales order formview. When a sale item node is clicked... you can probably guess what it displays then.
And this is where things start to go off the rails. When a node is clicked, it's Value property is stored in a variable called _id. This variable is then stored in ViewState. When the PostBack happens, the idea is that the Page_Load event will read the value of _id from ViewState, run the showFormViews() procedure and display the relevant formview. I have it laid out like this:
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["_id"] == null)
{
_id = "";
}
else
{
_id = Convert.ToString(ViewState["_id"]);
}
if (!IsPostBack)
{
fillTreeView;
tv.CollapseAll();
}
showFormViews();
}
and when a node is clicked:
protected void tv_SelectedNodeChanged(Object sender, EventArgs e)
{
// (a) PostBack occurs and Page_Load is run *before* _id is set and stored in ViewState
_id = tv.SelectedNode.Value;
ViewState.Add("_id", _id);
showFormViews();
// (b) would be great to be able to fire off a PostBack programmatically right here!
}
(the .aspx markup is just a TreeView and three FormViews linked to ObjectDataSources)
As I now know from this helpful page:
"The initialisation part of the page lifecycle will execute before the
event handler of the control that caused the post back. Therefore the
code in the page’s Init and Load event handler will execute before the
code in the event handler for the button that the user clicked."
As written at line (a) above, when the treeview node is clicked, the page is posted back and Page_Load is run whilst _id is still null. Because _id is null the showFormViews() procedure hides all of the formviews.
Then the SelectedNodeChanged event fires, _id is set and the showFormViews() procedure sets the relevant formview to visible. But of course, by now all of the controls have been rendered and so... nothing happens.
Being able to somehow fire a PostBack at line (b) would work out wonderfully. Page_Load would fire and run showFormViews() but this time with _id being what it should be.
Various variations at line (b) of:
Server.Transfer("samePage.aspx");
// or
Response.Redirect(Request.RawUrl, false);
don't work because they destroy ViewState (unless someone knows different?) so _id is back to being null again.
So if you can't fire off a PostBack in C# and if I can't work out how to implement the solutions by the two posters above (or even if they will be appropriate in this situation), is there any way I can restructure the page to make this - a fairly common pattern I would imagine - work (and please don't suggest MVC - I tried learning that and still have the nightmares!).
Many thanks in advance,
You can call in your PageLoad the method used to fill the ViewState... Like:
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["_id"] == null)
{
_id = "";
}
else
{
_id = Convert.ToString(ViewState["_id"]);
tv_SelectedNodeChanged(null, null);
}
if (!IsPostBack)
{
fillTreeView;
tv.CollapseAll();
}
}
This is my code:
public class MyCollection {
internal static Dictionary<string, CheckBox> MyCheckBox = new Dictionary<string, CheckBox>();
}
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
CheckBox chk1 = new CheckBox();
chk1.ID = "chk1";
chk1.Checked = true;
if (!MyCollection.MyCheckBox.ContainsKey(chk1.ID))
MyCollection.MyCheckBox.Add(chk1.ID, chk1);
CheckBox chk2 = new CheckBox();
chk2.ID = "chk2";
chk2.Checked = true;
if (!MyCollection.MyCheckBox.ContainsKey(chk2.ID))
MyCollection.MyCheckBox.Add(chk2.ID, chk2);
pl1.Controls.Add(chk1);
pl2.Controls.Add(chk2);
}
}
protected void btn1Click(object sender, EventArgs e) {
lit1.Text = "Chk1.Checked: " + MyCollection.MyCheckBox["chk1"].Checked.ToString();
lit1.Text += "<br />Chk2.Checked: " + MyCollection.MyCheckBox["chk2"].Checked.ToString();
}
i have 2 checkboxes, but always when press the button, Checkboxes.Checked are true, also when i unchecked them,
how can i hold checkboxes in some dictionary like above and check their Checked property in right way?
actualy i do't want any event for change Checkboxes, I Think about a jQuery script, that when i press the button retrive me the name of checkboxes that unchecked
i find a jQuery that can find the checkboxe names that unchecked but i don't know how can pass the names to the c# code?
my above code is just a sample and real project is a user control that dynamically define and render controls from a type, hmm the scenario is like this: the clients add the assembly of user control and declare a property of that named target type, user control read target and for each property decide which control must be add so by overrided RenderContents and other methods can add controls to page, so every control automatically have a Checkbox that enable/disable control and i need the checked property
i find this code in Stackoverflow...
var sList = "";
$('input[type=checkbox]').each(function () {
var sThisVal = (this.checked ? "1" : "0");
sList += (sList=="" ? sThisVal : "," + sThisVal);
});
console.log (sList);
but this code just write sList on console how can retrive the sList values in C# Code?
Firstly, you should never store an instance of a control such as a CheckBox in a static member since this is a recipe for a memory leak in your application. ASP.NET Control classes hold a reference to their containing Page and so by adding the Control to a static collection you're going to keep the entire Page instance and all its child Controls from being garbage-collected.
Secondly, there's no point in doing this in your case since it isn't going to achieve what you're trying to do. The instance of the CheckBox class that you add to the Dictionary isn't going to be the same instance that you want to interact with on subsequent requests; the instance you store in the Dictionary is for that request only which is why the CheckBox instances are always returning true from their Checked property.
I don't think that JQuery is relevant here since that will not help you reconstitute the CheckBox controls on the server. What you need to do is add the CheckBox controls to the Page on every request. In order for the CheckBox controls to have their state loaded from the Form collection you need to add the Controls early enough in the Page lifecycle: I would recommend trying to add them during the Page.Init event.
public void Page_Init(object sender, EventArgs e)
{
CheckBox chk1 = new CheckBox();
chk1.ID = "Chk1";
CheckBox chk2 = new CheckBox();
ck2.ID = "Chk2";
if(!IsPostBack)
{
ck1.Checked = true;
ck2.Checked = true;
}
p1.Controls.Add(chk1);
p2.Controls.Add(chk2);
}
Without the actual code it is difficult to understand what you're trying to do and how you're going about it. The fact remains, though, that if you don't add the CheckBox controls to the Page on every request then they cannot participate in the Page lifecycle and won't have their state loaded from the Form collection when the Page is submitted. It sounds to me like you want to manipulate the control tree of the Page on the server but that needs to be reconstituted on every request and only exists during the handling of the request. After the Page is rendered to the client the control tree no longer exists.
So, you could POST the values of the HTML input checkboxes to the server using JavaScript but where would you send them and what do you expect to happen? It seems like you're fighting against the ASP.NET Webforms way of doing things which revolves around post-backs and the Page object on the server that handles the request.
Use this one maybe useful this is all of your needed test it and let me know about the result:
Loop through checkboxes and count each one checked or unchecked
Remember please some simple thing: YOU MUST ADD DYNAMIC CONTROLS ON EACH AND EVERY POSTBACK. This will save a lot of your time.
I have this DropDownList bound to a XmlDataSource, but the XPath must depend on another input (another DropDownList). I modify the XPath and rebind inside the parent DDL's SelectedIndexChanged, but the page does not refresh. What should I do to display the update?
protected void ProductDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
CompMapping.XPath = "//couple[#product='" + ProductDropDownList.SelectedValue + "']";
CompMapping.DataBind();
ComponentDropDown.DataBind();
}
ComponentDropDown is set to AutoPostBack=True, but that's not enough. Any idea? Could it be related to the fact both DropDownLists are in a UserControl?
Remove the line:
CompMapping.DataBind();
The issue must be linked to the fact that it's a user control, since linking the dropdowns outside it has no problem. Realizing I don't absolutely need these to be in a user control, I just moved them outside. That don't really solves the issue, but at least it's not my concern anymore.
I'm working on an ASP.NET project in which the vast majority of the forms are generated dynamically at run time (form definitions are stored in a DB for customizability). Therefore, I have to dynamically create and add my controls to the Page every time OnLoad fires, regardless of IsPostBack. This has been working just fine and .NET takes care of managing ViewState for these controls.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
RenderDynamicControls()
}
private void RenderDynamicControls()
{
//1. call service layer to retrieve form definition
//2. create and add controls to page container
}
I have a new requirement in which if a user clicks on a given button (this button is created at design time) the page should be re-rendered in a slightly different way. So in addition to the code that executes in OnLoad (i.e. RenderDynamicControls()), I have this code:
protected void MyButton_Click(object sender, EventArgs e)
{
RenderDynamicControlsALittleDifferently()
}
private void RenderDynamicControlsALittleDifferently()
{
//1. clear all controls from the page container added in RenderDynamicControls()
//2. call service layer to retrieve form definition
//3. create and add controls to page container
}
My question is, is this really the only way to accomplish what I'm after? It seems beyond hacky to effectively render the form twice simply to respond to a button click. I gather from my research that this is simply how the page-lifecycle works in ASP.NET: Namely, that OnLoad must fire on every Postback before child events are invoked. Still, it's worthwhile to check with the SO community before having to drink the kool-aid.
On a related note, once I get this feature completed, I'm planning on throwing an UpdatePanel on the page to perform the page updates via Ajax. Any code/advice that make that transition easier would be much appreciated.
From Dirk to Dirk :-)
What do you mean with RenderDynamicControls? Create and set controls? If this is your intention not ASP.NET is managing your ViewState, but you do. If you fill the controls on every load, you always overwrite the existing ViewState!
If you want to use the ViewState, create your controls in the pages init event and fill them in the load event, but only if the request isn’t a postback. This is necessary, because ASP.NET recreates the ViewState between init and load. And this is also the reason for the two “rendering cycles” you describe. You need the first control creation cycle because ASP.NET can’t restore the ViewState without a proper control set and ASP.NET can’t react proper on your response without it.
Back to your code: In general your RenderDynamicControlsALittleDifferently wouldn’t work - because you create your controls too late in the pages life cycle and you would damage the ViewState by inserting new objects to the control collection. In a similar situation I solved this problem by a redirecting the page to itself (Response.Redirect). In this case RenderDynamicControls would do the job, based on a “little differently situation” after you change your internal state.
Problem with dynamic controls
Hello all,
I'm wanting to create some dynamic controls, and have them persist their viewstate across page loads. Easy enough, right? All I have to do is re-create the controls upon each page load, using the same IDs. HOWEVER, here's the catch - in my PreRender event, I'm wanting to clear the controls collection, and then recreate the dynamic controls with new values. The reasons for this are complicated, and it would probably take me about a page or so to explain why I want to do it. So, in the interests of brevity, let's just assume that I absolutely must do this, and that there's no other way.
The problem comes in after I re-create the controls in my PreRender event. The re-created controls never bind to the viewstate, and their values do not persist across page loads. I don't understand why this happens. I'm already re-creating the controls in my OnLoad event. When I do this, the newly created controls bind to the ViewState just fine, provided that I use the same IDs every time. However, when I try to do the same thing in the PreRender event, it fails.
In any case, here is my example code :
namespace TestFramework.WebControls
{
public class ValueLinkButton : LinkButton
{
public string Value
{
get
{
return (string)ViewState[ID + "vlbValue"];
}
set
{
ViewState[ID + "vlbValue"] = value;
}
}
}
public class TestControl : WebControl
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Controls.Clear();
ValueLinkButton tempLink = null;
tempLink = new ValueLinkButton();
tempLink.ID = "valueLinkButton";
tempLink.Click += new EventHandler(Value_Click);
if (!Page.IsPostBack)
{
tempLink.Value = "old value";
}
Controls.Add(tempLink);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ValueLinkButton tempLink = ((ValueLinkButton)FindControl("valueLinkButton")); //[CASE 1]
//ValueLinkButton tempLink = new ValueLinkButton(); [CASE 2]
tempLink.ID = "valueLinkButton";
tempLink.Value = "new value";
tempLink.Text = "Click";
Controls.Clear();
Controls.Add(tempLink);
}
void Value_Click(object sender, EventArgs e)
{
Page.Response.Write("[" + ((ValueLinkButton)sender).Value + "]");
}
}
}
So, let's examine case 1, where the line next to [CASE 1] is not commented out, but the line next to [CASE 2] is commented out. Here, everything works just fine. When I put this control on a page and load the page, I see a link that says "Click". When I click the link, the page outputs the text "[new value]", and on the next line, we see the familiar "Click" link. Every subesquent time I click on the "Click" link, we see the same thing. So far, so good.
But now let's examine case 2, where the line next to [CASE 1] is commented out, but the line next to [CASE 2] is not commented out. Here we run into problems. When we load the page, we see the "Click" link. However, when I click on the link, the page outputs the text "[]" instead of "[new value]". The click event is firing normally. However, the "new value" text that I assigned to the Value attribute of the control does not get persisted. Once again, this is a bit of a mystery to me. How come, when I recreate the control in OnLoad, everything's fine and dandy, but when I recreate the control in PreRender, the value doesn't get persisted?
I feel like there simply has to be a way to do this. When I re-create the control in PreRender, is there some way to bind the newly created control to the ViewState?
I've struggled with this for days. Any help that you can give me will be appreciated.
Thanks.
ViewState-backed properties are only persisted to ViewState if the control is currently tracking ViewState. This is by design to keep ViewState as small as possible: it should only contain data that is truly dynamic. The upshot of this is that:
ViewState propeties set during the Init event are not backed to ViewState (because the Page has not yet started tracking ViewState). Thus Init is a good place to add controls and set (a) properties that won't change between postbacks (ID, CssClass...) as well as initial values for dynamic properties (which can then be modified by code in the rest of the page lifecycle - Load, event handlers, PreRender).
When dynamically adding controls in Load or PreRender, ViewState is being tracked. The developer can then control which propeties are persisted for dynamically added controls as follows:
Properties set before the control is added to the page's control tree are not persisted to ViewState. You typically set properties that are not dynamic (ID etc) before adding a control to the control tree.
Properties set after the control is added to the page's control tree are persisted to ViewState (ViewState tracking is enabled from before the Load Event to after the PreRender event).
In your case, your PreRender handler is setting properties before adding the control to the page's control tree. To get the result you want, set dynamic properties after adding the control to the control tree:
.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ValueLinkButton tempLink = new ValueLinkButton(); // [CASE 2]
tempLink.ID = "valueLinkButton"; // Not persisted to ViewState
Controls.Clear();
Controls.Add(tempLink);
tempLink.Value = "new value"; // Persisted to ViewState
tempLink.Text = "Click"; // Persisted to ViewState
}
As others have statement you'll need to ensure that you are creating via the Init method. To learn more about the ASP.NET page life cycle check out this article: http://msdn.microsoft.com/en-us/library/ms178472.aspx
I'm already re-creating the controls in my OnLoad event.
That's your problem. OnLoad is too late. Use Init instead.
Thank you for your help, but I tried that and it didn't make a difference. Besides, OnLoad works just as well for dynamic controls as OnInit, as long as you give your controls the same IDs every time.
I believe that once you have added the dynamic controls to the page in PageLoad, the ViewState is bound to the controls and the "ViewState still needs to be bound" flag (in concept, not an actual flag) is cleared. Then, when you recreate the controls, the existing ViewState is no longer bound.
I faced something similar last year, only in my case I did not want the ViewState to rebind. My issue is that I was not recreating the previous controls, which is why I think that the pseudo-flag notion above applies.
Try calling Page.RegisterRequiresControlState(). You can also use RequiresControlState() to check if it's already been registered.
ViewState works on the Page and its child objects. The new control in [Case 2] has not been added to the Page (or any of its children). In fact, in case of the code above, the object will be out of scope as soon as the OnPreRender method ends and will be garbage collected.
If you absolutely have to swap out the control, you will need to remove the old control from its parent using Remove() method and add the new control at the right place using AddAt().
If the control was the only child of the parent, the code would be something like the following.
ValueLinkButton tempLink = new ValueLinkButton();
Control parent = FindControl("valueLinkButton").Parent;
parent.Remove(FindControl("valueLinkButton"));
parent.AddAt(0, tempLink);
Control added before SaveViewState method called in control life cycle should persist their values. I would concur with Joe's answer. Check this image
http://emanish.googlepages.com/Asp.Net2.0Lifecycle.PNG
I figured out yesterday that you can actually make your app work like normal by loading the control tree right after the loadviewstateevent is fired. if you override the loadviewstate event, call mybase.loadviewstate and then put your own code to regenerate the controls right after it, the values for those controls will be available on page load. In one of my apps I use a viewstate field to hold the ID or the array info that can be used to recreate those controls.
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
MyBase.LoadViewState(savedState)
If IsPostBack Then
CreateMyControls()
End If
End Sub