Problem with dynamic controls in .NET - c#

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

Related

Click event not firing on dynamically added LinkButton

I have a LinkButton that is created dynamically in Load and then added to a control that resides on the Master page. Here is the original code used to create the LinkButton:
LockUnlock = new LinkButton() { ID = "LockUnlock", Visible = false };
LockUnlock.Click += LockUnlock_Click;
Now, when I first dug into this problem I thought it might be related to the ASP.NET life cycle so I moved the code to an override OnInit, but that didn't fix it.
I then moved on to setting the ID because that's not being done. So I added this line:
LockUnlock.ID = "LockUnlock";
and I tried that in both OnInit and Load - no luck.
Then I thought, because I'm adding this to a custom control that is actually part of a ContentPlaceHolder I may need to make the ID static to get this to work, so I added this:
LockUnlock.ClientIDMode = ClientIDMode.Static;
I have only tried that in the Load, but no luck, and honestly if it's not working in Load it's almost certainly not going to change anything in OnInit - that was just my first move and really a hail Mary in a lot of ways.
Alright, so now I'm at the point where I feel like it's related to the fact that the ResourceCenter is a custom control that is added to a ContentPlaceHolder and thus the event, though it's technically hooked up, can't be fired because of the context of the class. But I'm not sure where to go from here.
Do I need to add a shared class for the click? Do I need to hookup the click in the custom control and then delegate it from there somehow?
I would prefer not to use either of those solutions, but hey, we do what we have to do! I look forward to everybody's input on this.
EDIT
The code that adds the control to the ResourceCenter looks like this:
this.ResourceCenter.AddAdminLink(LockUnlock.Visible ? LockUnlock : null);
and the code inside the ResourceCenter control that adds to its list looks like this:
if (link == null) { return; }
var wrapper = new HtmlGenericControl("li");
wrapper.Controls.Add(link);
this.AdminLinkList.Controls.Add(wrapper);
where link is what was passed into the method by the aforementioned line.
It would have to be in the init. Also, try adding the control to the control's collection, before adding the event, as in:
LockUnlock = new LinkButton() { ID = "LockUnlock", Visible = false };
Panel.Controls.Add(LockUnlock);
LockUnlock.Click += LockUnlock_Click;

In which event should one set dynamic control properties?

This article states that Page_PreInit should be used to
create or re-create dynamic controls.
For example:
Button button = new Button();
somePanel.Controls.Add(button);
Good. I understand.
However, it also says:
If the request is a postback, the values of the controls have not yet
been restored from view state. If you set a control property at this
stage, its value might be overwritten in the next event.
Huh?
Does this mean that all I should do is create the button, but not set any members of the button?
For example:
Button button = new Button() { CommandArgument="arg" };
somePanel.Controls.Add(button);
Does this mean that setting CommandArgument in this event is incorrect/not recommended/might cause an error/unexpected behavior?
Assuming it is incorrect, this would lead me to think that one would have to do something like this:
protected void Page_PreInit(object sender.....)
{
somePanel.Controls.Add((new Button());
}
protected void Page_Init(object sender.....)
{
foreach(Button button in somePanel.Controls)
button.CommandArgument = "arg";
}
is this the right way?
Finally, in which event should one set dynamic control properties?
There is no single answer for that last question as depending on the nature of the property it may or may not make sense to set a value in a specific method.
If the request is a postback, the values of the controls have not yet
been restored from view state. If you set a control property at this
stage, its value might be overwritten in the next event.
Might is the keyword here. If you consider some properties that may change as a form goes through various states then this is where you have to be careful of what may get overwritten as well as the question of whether or not this is a bad thing as it may be that the updated value should persistent and in other cases the original value may be better such as if someone wants to reset the form to its initial state.
My suggestion would be to do some trial and error to see what works as I can remember working with dynamic controls that could be tricky in some me cases to manage properly.

Find Unchecked Checkboxes with jQuery and pass the Names to C# Code

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.

Serialize and Reload Dynamic Controls

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.

Dynamically created controls and the ASP.NET page lifecycle

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.

Categories

Resources