Custom DropDownList Server Control Losing Items on Postback - c#

I have a custom server control inheriting from DropDownList. On postback, the items are lost. It looks something like this:
public class MyClientSelectList : DropDownList
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// design mode hack to let visual studio display in design mode
if (!DesignMode)
{
if (!Page.IsPostBack)
{
this.Items.Add(new ListItem("Select an item..."));
// add more items from db
}
}
}
}
I checked EnablePostBack = true. I select a selected value in the page load of my page which is hosting this custom server control.
Why are the items lost on postback?

EnableViewState is already True by default, so mshsayem's solution will not work.
There're 2 way to do that, a standard way is to override SaveControlState and LoadControlState Method
Refer to http://msdn.microsoft.com/en-us/library/1whwt1k7(v=VS.100).aspx
Another way is, in the Init, reload list items from database no matter postback or not, then retrieve the selected value from post data and set it back to dropdownlist

Related

Can you fire off a PostBack in ASP.NET using C#?

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();
}
}

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.

.net ViewState in page lifecycle

I have a page containing a control called PhoneInfo.ascx. PhoneInfo is dynamically created using LoadControl() and then the initControl() function is called passing in an initialization object to set some initial textbox values within PhoneInfo.
The user then changes these values and hits a submit button on the page which is wired up to the "submit_click" event. This event invokes the GetPhone() function within PhoneInfo. The returned value has all of the new user entered values except that the phoneId value (stored in ViewState and NOT edited by the user) always comes back as null.
I believe that the viewstate is responsible for keeping track of user entered data across a postback, so I can't understand how the user values are coming back but not the explicitly set ViewState["PhoneId"] value! If I set the ViewState["PhoneId"] value in PhoneInfo's page_load event, it retrieves it correctly after the postback, but this isn't an option because I can only initialize that value when the page is ready to provide it.
I'm sure I am just messing up the page lifecycle somehow, any suggestion or questions would really help! I have included a much simplified version of the actual code below.
Containing page's codebehind
protected void Page_Load(object sender, EventArgs e)
{
Phone phone = controlToBind as Phone;
PhoneInfo phoneInfo = (PhoneInfo)LoadControl("phoneInfo.ascx"); //Create phoneInfo control
phoneInfo.InitControl(phone); //use controlToBind to initialize the new control
Controls.Add(phoneInfo);
}
protected void submit_click(object sender, EventArgs e)
{
Phone phone = phoneInfo.GetPhone();
}
PhoneInfo.ascx codebehind
protected void Page_Load(object sender, EventArgs e)
{
}
public void InitControl(Phone phone)
{
if (phone != null)
{
ViewState["PhoneId"] = phone.Id;
txt_areaCode.Text = SafeConvert.ToString(phone.AreaCode);
txt_number.Text = SafeConvert.ToString(phone.Number);
ddl_type.SelectedValue = SafeConvert.ToString((int)phone.Type);
}
}
public Phone GetPhone()
{
Phone phone = new Phone();
if ((int)ViewState["PhoneId"] >= 0)
phone.Id = (int)ViewState["PhoneId"];
phone.AreaCode = SafeConvert.ToInt(txt_areaCode.Text);
phone.Number = SafeConvert.ToInt(txt_number.Text);
phone.Type = (PhoneType)Enum.ToObject(typeof(PhoneType), SafeConvert.ToInt(ddl_type.SelectedValue));
return phone;
}
}
ViewState is managed between the Init and Load events. You should utilize code in or functions called from the Page_Init handler instead of Page_Load when you are concerned with maintaining values from postbacks.
See this link for more information on the ASP.NET page life cycle.
http://msdn.microsoft.com/en-us/library/ms178472.aspx
I thought, like the answerers, that this problem existed because I was setting the ViewState variable too late to be saved during SaveViewState. However, I added an override for the SaveViewState event, and sure enough I was setting my ViewState variable long before the ViewState was saved. Too long before, it turns out.
Apparently after initialization, .net runs TrackViewState() which actually starts listening for any viewState variables you add. Since I had set this viewState value BEFORE TrackViewState() had run, everything appeared fine but the values were not actually being added during SaveViewState. I explicitly called TrackViewState() right before setting the variable and everything worked as expected.
My final solution was to assign a private property to hold the ID value through initialization and then to set the viewState variable during SaveViewState from the property value. This way I can ensure that TrackViewState has been run by .net without having to explicitly call it and mess up the flow of things. I reload the viewState value on page_load and use it to set the value of the property which I can then use in my GetPhone function.
This article talks about enableViewState.
private int _id = -1;
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["PhoneId"] != null)
_id = SafeConvert.ToInt(ViewState["PhoneId"]);
}
protected override object SaveViewState()
{
ViewState["PhoneId"] = _id;
return base.SaveViewState();
}
public Phone GetPhone()
{
Phone phone = new Phone();
phone.Id = _id;
phone.AreaCode = SafeConvert.ToInt(txt_areaCode.Text);
phone.Number = SafeConvert.ToInt(txt_number.Text);
return phone;
}
To do exactly what you're doing, you have to have the control loaded in the Init portion of the page lifecycle.
However, to do it more easily, why don't you add it to an <asp:ContentPlaceHolder>? I think it will make this much easier.
Based on the comment responses, I'm adding another answer here that explains a part of the Page Lifecycle and what it means for post and viewstate data. Below is a more in-English (to a dev) and less about events version of the beginning of the page lifecycle:
User requests a page (request 1)
ASP.NET builds the page with default values in fields (request 1)
On your Page_Load, you add this special control with your default values (request 1)
You send this page to the user (request 1)
The user posts data to the page (request 2)
ASP.NET builds the page from your ASPX file (request 2)
ASP.NET adds the ViewState data to the page (at this time, there is none) (request 2)
ASP.NET adds the Post data to the page (request 2)
ASP.NET runs reaches the Load step of the Page Lifecycle and adds your special control with your default values (request 2)
You are now in the problematic state that you are seeing.
So the problem here is that your dynamically-loaded control does not exist when post data is added to the page. When you add it, you put your default values in. ASP.NET no longer has a chance to populate it with the proper data.

Problem with dynamic controls in .NET

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

Categories

Resources