ASP.NET / UserControl, ViewState and losing Property values - asp.net

this is about ASP.NET, ViewState, UserControls and losing the values of my properties. It is an classic question by know, I know, and even though I have searched here and on Google for a resolution to this problem, I havent succeeded. On the contrary, the more I test different things, the less I understand it seems.
On top of this I am using ext.net and their so called DirectMethod's but I dont think that has much to do with this problem. There are numerous questions I have and I hope that this text will be fairly readable and understandable =)
The UserControl
I have a UserControl, Customers.ascx, that contains some Properties. The "most" important is *_CustomerId*. The _CustomerId is set in code-behind, in ext.net's "DirectMethod" like this (code below from the Page Customers.aspx):
[DirectMethod]
public void SetCustomer()
{
RowSelectionModel sm = GridPanel2.SelectionModel.Primary as RowSelectionModel;
if (sm.SelectedRow != null)
{
uint customerId = uint.Parse(sm.SelectedRow.RecordID);
customer_modify._CustomerId = customerId;
}
}
The "customer_modify" is the instance of the UserControl as defined in Customers.aspx (a Page, not a UserControl):
<asp:Content ID="Content2" ContentPlaceHolderID="CPH_center" runat="server">
<CP:Customer ID="customer_modify" runat="server" _IsCreateMode="false" ViewStateMode="Enabled" />
</asp:Content>
In that Page, Customers.aspx, I have ext.net GridPanel that lists the Customers and when I click the rows in the GridPanel the (Direct)method SetCustomer is executed.
As you can see in the SetCustomer-method, the property "_CustomerId" is updated with an uint, so lets take a look at the Property in the UserControl:
public uint _CustomerId
{
get
{
object o = ViewState["_CustomerId"];
if (o == null)
return 0;
return (uint)o;
}
set
{
object o = ViewState["_CustomerId"];
ViewState["_CustomerId"] = value;
SetCustomer();
}
}
As you can see, I am using the ViewState-thingie and it was my hope that the state, ie the value of the properties would be remembered. They are not.
When I click a Row in the GridPanel the first time I can see (by breaking) that the ViewState["_CustomerId"] == null which is alright. I see thereafter that the setter for _CustomerId is executed and that the ViewState["_CustomerId"] is assigned the RecordID (uint).
I now click another Row in the GridPanel and the SetCustomer-method is executed again. I break in the setter for _CustomerId and take a look at what the ViewState["_CustomerId"] is BEFORE assigning it. It is 0, but I expect it to be the value from the previous Row clicked.
Why is it 0? What am I missing?
Also:
I actually have two of there UserControls on the Page, the other one named "customer_create":
<ext:Window
ID="Window_CreateNewCustomer"
runat="server"
Icon="New"
Title="Skapa ny kund"
Hidden="true"
Width="480"
Height="370"
Modal="true">
<Content>
<CP:Customer ID="customer_create" runat="server" Title="Skapa ny kund" _IsCreateMode="true" />
</Content>
</ext:Window>
What disturbs me a lot is that each and every time I click on a Row in the GridPanel the Page_Load in the UserControl is executed twice, even though I am only updating one of them in the SetCustomer()-method.
Why is that?
Any tips, ideas and help would be greatly appreciated.

ViewState is serialized, enciphered and sent to the client as a hidden form field so that it may be reconstituted when something triggers a form submission. A [DirectMethod] does not go through form submission (it's processed as an AJAX call), so it can neither read nor write ViewState data, unless round-tripping of this data is specifically requested with ViewStateMode.Enabled.
On any given request directed to a form, the Load event will be fired from the Page, and thus received by every user control on the page that subscribes to the event at the expected phase of the page processing lifecycle -- whether it's two instances of one UserControlor two different UserControls, it's two subscribers and therefore two calls (one to each instance).

Related

UpdatePanel resetting object to inital state

I have an application that I am currently writing that works by iterating through nodes, and then updating the page with the information of the current node. I have an UpdatePanel in the page which contains a label, textbox, and a button. The label lists the currently available children of the current node, the user enters in which child they want to go to into the textbox, and then hits the submit button. I set the new value of the node in the submit button's event handler.
Here's my problem: Every time I enter in which node I want to navigate to, the object resets its value to the value it was initially initialized to. I have even put this same code into a Windows Form to validate that it's working correctly to iterate through my tree, and it works as it should, so I know my problem is AJAX-related.
This is the first app that I have written using AJAX, so I am still in the process of learning how it works. Any help would be greatly appreciated. I have Googled and searched SO through and through.
Here is the HTML:
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager" runat="server"></asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="question" runat="server" Text=""></asp:Label>
<br />
<asp:TextBox ID="answer" runat="server"></asp:TextBox>
<br />
<asp:Button ID="Submit" runat="server" Text="Submit" onclick="Submit_Click" />
</ContentTemplate>
</asp:UpdatePanel>
</form>
And the C#:
protected void Submit_Click(object sender, EventArgs e)
{
int ans = int.Parse(answer.Text);
if (!current.ChildIDs.Contains(ans))
{
return;
}
current = tree.Node(ans);
question.Text = current.Question;
}
current is the current node, which has a public ArrayList of all of its children's IDs. tree is the NodeTree I have; calling Node just returns the new node. Both current and Tree get initialized in the Page_Load event, and that only fires once (when the page is first loaded).
It's really pretty simply code; I'm just having difficulty understanding why the AJAX isn't working correctly.
I have even put this same code into a
Windows Form to validate that it's
working correctly to iterate through
my tree, and it works as it should, so
I know my problem is AJAX-related.
It sounds like you're expecting ASP.NET to remember what the object current is between requests, since that's how Windows forms applications work.
Web applications are stateless - after each request, ASP.NET discards all your variables. To access the variable during a subsequent request, you have to either:
1) Send enough data with the request to reconstruct the variable. You can do this using a querystring parameter or an HTML form value (the hidden fields another response mentioned).
2) Save the variables in a Session store (which can be in-memory or backed by a database).
3) Store the value in a coookie.
Of these three, it's easiest to show you how to use Session, given what you've shared in your question. However, beware: session has its risks - by default, ASP.NET session objects are stored in-memory, and it's a potential security hazard. But here's how you should be able to get your application to work.
// In your Page_Load code that initializes your 'current' variable
// When the user first requests the page, create a new Node
if (! this.IsPostBack)
{
Node current = new Node(); //
Session("currentNode") = current;
}
// When the user clicks a button on the page (posts), use the
// node in session instead
else
{
current = Session("currentNode");
}
When you update non-form elements in the browser (labels, literals, etc.), .NET is unable to see any of the changes you've made.
Try adding a hidden input for each label that records the new value. Then within the method you have wired up to the button's OnClick event, do something like this:
myLabel.Text = myHiddenInput.value;
I think you just need to tell the updatepanel to update itself. Try this:
protected void Submit_Click(object sender, EventArgs e)
{
int ans = int.Parse(answer.Text);
if (!current.ChildIDs.Contains(ans))
{
return;
}
current = tree.Node(ans);
question.Text = current.Question;
UpdatePanel.Update();
}

Refresh updatepanel through triggers when other updatepanel does because the first must have UpdateMode=Conditional

I have some updatepanels in asp.net page. I want updatepanel 2 get refreshed when updatepanel 1 does, but UpdatePanel 2 has his UpdateMode attributed set to Conditional and ChildrenAsTrigger=False, because it has another updatePanels inside and i need to control the way it is refreshed. I was trying to make a trigger for the other updatepanel but this seems not work, maybe i am missing something
Is there a way to make this happen?
If you can't figure a valid example, imagine product category list and product list, when selected category change (linkbutton inside updatepanel) product list is refreshed which is inside another updatepanel, product list change for example when you order by price (order is inside other updatepanel) and i don't want category list gets refreshed.
thanks in advance.
You can call updatePanelProdList.Update() inside the code-behind file of your page. The place to call this method in your scenario would be the event handler for the category change LinkButton.
Thank you, that's what i am looking for! I have 2 "Update Panel" on my page, one keeping my form and other one keeping flexiGrid (non .net version,pure jQuery).
I do manual AsyncPostBack from my grid to get in edit or delete mode.
function doPostBackAsync(eventName, eventArgs) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
if (!Array.contains(prm._asyncPostBackControlIDs, eventName)) {
prm._asyncPostBackControlIDs.push(eventName);
}
if (!Array.contains(prm._asyncPostBackControlClientIDs, eventName)) {
prm._asyncPostBackControlClientIDs.push(eventName);
}
__doPostBack(eventName, eventArgs);
}
<a title="Edit" href="javascript:doPostBackAsync('Edit','2');">Edit</a>
and then i handle it like
string EventName = Request.Form["__EVENTTARGET"].ToString();
int EventValue = Request.Form["__EVENTARGUMENT"].ToString();
after determinate EventName and EventValue calling the method below
protected void Edit(int id)
{
//load the form here
UpdatePanel1.Update(); //do the trick right, without this line unable to show form fields and other stuff with newly loaded data
}
regards.
(sorry for my English)

ASP.Net - repeating input boxes on the client side using Repeater

I have the following requirement for creating a user profile in my application:
User should be able to enter multiple phone numbers/email addresses in his profile.
The screen looks somewhat like this:
- By default, on page load a single textbox for phone and email are shown.
- User can click a "+" button to add additional numbers/addresses.
- On clicking the "+" button we need to add another textbox just below the first one. User can add as many numbers/addresses as he wants. On submit, the server should collect all numbers/emails and save it in DB.
I tried using the Repeater control to do this. On page_load I bind the repeater to a "new arraylist" object of size 1. So, this renders fine - user sees a single textbox with no value in it.
When he clicks the "+" button, I ideally want to use javascript to create more textboxes with similar mark-up as the first.
My questions are these:
Can I render the new textboxes anyway using js? I notice that the HTML rendered by the repeater control is somewhat complex (names/ids) etc. and it might not be possible to correctly create those controls on client-side.
If there is a way to do #1, will the server understand that these additional inputs are items in the repeater control? Say, I want to get all the phone numbers that the user entered by iterating over Repeater.DataItems.
Conceptually, is my approach correct or is it wrong to use the Repeater for this? Would you suggest any other approach that might handle this requirement?
Coming from a Struts/JSP background, I am still struggling to get a grip on the .NET way of doing things - so any help would be appreciated.
The repeater control may be a bit of overkill for what you're trying to accomplish. It is mainly meant as a databound control for presenting rows of data.
What you can do is to dynamically create the boxes as part of the Page_Load event (C#):
TestInput.aspx :
<form id="form1" runat="server">
<asp:HiddenField ID="hdnAddInput" runat="server" />
<asp:Button ID="btnPlus" OnClientClick="setAdd()" Text="Plus" runat="server" />
<asp:PlaceHolder ID="phInputs" runat="server" />
</form>
<script type="text/javascript">
function setAdd() {
var add = document.getElementById('<%=hdnAddInput.ClientID%>');
add.value = '1';
return true;
}
</script>
TestInput.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["inputs"] == null)
ViewState["inputs"] = 1;
if (hdnAddInput.Value == "1")
{
ViewState["inputs"] = int.Parse(ViewState["inputs"].ToString()) + 1;
hdnAddInput.Value = "";
}
for (int loop = 0; loop < int.Parse(ViewState["inputs"].ToString()); loop++)
phInputs.Controls.Add(new TextBox() { ID = "phone" + loop });
}
I ended up using a PlaceHolder to dynamically add the text boxes and a HiddenField to flag when another TextBox needed to be added. Since the IDs were matching, it maintains the ViewState of the controls during each postback.
Welcome to the hairball that is dynamically-added controls in ASP.NET. It's not pretty but it can be done.
You cannot add new fields dynamically using javascript because the new field would have no representation in the server-side controls collection of the page.
Given that the requirements are that there is no limit to the number of addresses a user can add to the page, your only option is to do "traditional" dynamic ASP.NET controls. This means that you must handle the adding of the control server-side by new-ing a new object to represent the control:
private ArrayList _dynamicControls = new ArrayList();
public void Page_Init()
{
foreach (string c in _dynamicControls)
{
TextBox txtDynamicBox = new TextBox();
txtDynamicBox.ID = c;
Controls.Add(txtDynamicBox);
}
}
public void AddNewTextBox()
{
TextBox txtNewBox = new TextBox();
txtNewBox.ID = [uniqueID] // Give the textbox a unique name
Controls.Add(txtNewBox);
_dynamicControls.Add([uniqueID]);
}
You can see here that the object that backs each dynamically-added field has to be added back to the Controls collection of the Page on each postback. If you don't do this, data POSTed back from the field has nowhere to go.
If you want to user the repeater, I think the easiest way is to put the repeater in a ASP.Net AJAX update panel, add the extra textbox on the sever side.
There are definitely other way to implement this without using repeater, and it maybe much easier to add the textbox using js.
No, but you can create input elements similar to what TextBox controls would render.
No. ASP.NET protects itself from phony data posted to the server. You can't make the server code think that it created a TextBox earlier by just adding data that it would return.
The approach is wrong. You are trying to go a middle way that doesn't work. You have to go all the way in either direction. Either you make a postback and add the TextBox on the server side, or you do it completely on the client side and use the Request.Form collection to receive the data on the server side.

ASP.NET: adding controls client-side

If I have a page with a form (imagine a simple one with just TextBoxes and a submit button) and I want to allow the user to dynamiccally add more TextBoxes to the form via javascript, what is the best way to handle the request server side?
Example: I have a page rendered like the following :
<input type = "text" id = "control1" name = "control1" />
<input type = "text" id = "control2" name = "control2" />
<input type = "text" id = "control3" name = "control3" />
<input type = "submit" />
The user trigger some Javascript and the page turns out like:
<input type = "text" id = "control1" name = "control1" />
<input type = "text" id = "control2" name = "control2" />
<input type = "text" id = "control3" name = "control3" />
<input type = "text" id = "control4" name = "control4" />
<input type = "text" id = "control5" name = "control5" />
<input type = "submit" />
What is the best way to handle this kind of situation, or, more generally, working with dynamically generated input both client and server side (eg, how to generate them server side starting from, say, some data taken from a database)?
If you want to be able to access them in the code behind using the FindControl method, the AJAX UpdatePanel is probably your best bet. Just remember that every time you update the UpdatePanel, your going through the entire page life cycle but only getting the pieces that render in the update panel back from the server, so be weary of the overhead.
If you create them dynamically with Javascript you will not be able to use FindControl to get access to them in the code behind because they won't be re-created during the page event life cycle. Also, be careful because if you're creating a lot of them at the same time with some kind of loop it can slow things down, especially in Internet Explorer.
You may also consider using AJAX and WebServices with WebMethods for submitting the data instead of a post-back if you're creating the controls dynamically with Javascript.
I have done similiar things to this numerous times. My preferred approach is usually to use a Repeater and a Button (labelled Add) inside an UpdatePanel.
For the Button_OnClick event in your code behind do something similiar to this;
Loop through the Items collection of the Repeater
Use item.FindControl("txtUserInput") to get the TextBox for that item
Save the text of each input to List<string>
Add an empty string to the the list
Databind the repeater to this new list
Here's some example code;
<asp:Repeater runat="server" ID="rptAttendees">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtAttendeeEmail" Text='<%# Container.DataItem %>'></asp:TextBox>
</ItemTemplate>
</asp:Repeater>
protected void btnAddAttendee_Click(object sender, EventArgs e)
{
var attendees = new List<string>();
foreach (RepeaterItem item in rptAttendees.Items)
{
TextBox txtAttendeeEmail = (TextBox)item.FindControl("txtAttendeeEmail");
attendees.Add(txtAttendeeEmail.Text);
}
SaveAttendees();
attendees.Add("");
rptAttendees.DataSource = attendees;
rptAttendees.DataBind();
}
On client side, make a JS function for creating the inputs, for example:
var numCtrl = 1; // depends on how many you have rendered on server side
var addCtrl = function() {
var in = document.createElement('input');
in.name = "control" + ++i;
in.type = "text";
in.id = "control" + i;
document.getElementById("yourcontainer").appendChild(in);
}
..on the server side just lookup your HTTP params collection, iterate through it, and select only those matching /control\d*/
On the server side look into Request.Form["controlN"] where you will find your old and new fields.
On the server side you can add controls dynamically like:
foreach (Type object in MyDBCollection)
{
Controls.Add (new TextBox ());
}
In webforms you won't have a lot of choices if you expect the values to postback and be able to access them in typical fashion. What I would suggest is that you use an Ajax UpdatePanel to allow the controls to be registered at the appropriate point of the page lifecycle (before onLoad), this will also make a difference on whether or not you need the boxes to persist across postbacks. The JS methods involved in the other 2 answers will not persist the textboxes through a postback.
MVC gives you more leeway, since there isn't that crazy page lifecycle, control registration, viewstate, etc to get in your way.
So the big question is, which model/framework for ASP.Net are you using?
Since you are indeed using webforms, I really strongly suggest you go with updatepanels if they'll work for you. it will permit you to add controls in such a way that wont get in the way of eventvalidation, etc.
I've begun using jquery in a lot of my webforms applications and have found the combination of dynamic controls and using JSON to communicate with the page's WebMethods give me much more control over how the page is presented and eliminate needless postbacks.
When I want to trigger a postback, I use javascript to populate server controls on the page that are hidden by jquery and then click a server control button. This allows the page to degrade gracefully if the user cannot use javascript.

What's the best way to send a lot of checkboxes to the client in ASP.Net?

I have the following situation:
A user will define a certain filter on a page, and on postback I will query the database using that filter and return a bunch of matching records to the user, each with a checkbox next to it, so he can choose whether to act on each of those records.
In Classic ASP / PHP I can generate a lot of controls named "chk__*" and then on postback go through all the $POST entries looking for the ones prefixed "chk".
What is the best way to do this in ASP.Net 2.0?
I can do it easily by implementing a Repeater with a Template containing the checkbox, bind the Repeater to a Dataset, and then on the second Postback, I just do:
For Each it As RepeaterItem In repContacts.Items
Dim chkTemp As CheckBox = DirectCast(it.FindControl("cbSelect"), CheckBox)
If chkTemp.Checked Then
End If
Next
However this has the slight disadvantage of giving me a HUGE Viewstate, which is really bad because the client will need to re-upload the whole viewstate to the server, and these people will probably be using my site over a crappy connection.
Any other ideas?
(I can also create the controls dynamically and iterate through Request.Form as in the old days, however, I was looking for a cleaner
Have you looked at the CheckBoxList control? You can bind it to your data set, provide text member and value member items, and it will also allow you to easily see which items are checked. There is also the ability to dynamically add more checkbox items if needed.
Do it the same way you did it in classic ASP. Use <input type="checkbox"> instead of <asp:checkbox>. You can access the raw post paramaters using Request.Form
I recommend the classic ASP solution when faced with absurd Viewstate conditions. It is sad to lose the nice features it provides, but combining some Viewstate enabled controls (asp:*) with some classic techniques (input type="...") has saved me a lot of headaches in the past.
Sometimes you just want to do something simple, and the simple solution beats "WYSIWYG" form editing.
One of the things that I have done is to record the state of a check via AJAX in the session, then on Postback (full or partial via AJAX), look in the session for the items to perform the selected action on.
The basic idea is to add an onclick handler to the checkbox that knows the id of the associated item. In the on click handler communicate this id back to the server via AJAX and record it in the session -- you'll need to communicate checkbox status as well so you can uncheck items. Have the handler for the submit control use the data about which items were selected from the session.
This way allows you to handle paged data as well, since you can set the initial value of the checkbox from the session when rendering (either full or partial) a page with checked items on it.
It might look something like this. Assuming ASP.NET AJAX with PageMethods (and ScriptManager, of course).
<script type='text/javascript'>
function record(checkbox,item)
{
var context = { ctl : checkbox };
PageMethods.Record(item,checkbox.checked,onSuccess,onFailure,context);
}
function onSuccess(result,context)
{
// do something, maybe highlight the row, maybe nothing
}
function onFailure(error,context)
{
context.ctl.checked = false;
alert(error.get_Message());
}
</script>
...
<tr><td><input type='checkbox' onclick='record(this,"item_1");'></td><td>Item 1</td></tr>
...
Codebehind
[WebMethod(EnableSessionState=true)]
public static void Record( string itemName, bool value )
{
List<string> itemList = (List<string>)Session["Items"];
if (itemList == null)
{
itemList = new List<string>();
Session["Items"] = itemList;
}
if (itemList.Contains(itemName) && !value)
{
itemList.Remove(itemName);
}
else if (!itemList.Contains(itemName) && value)
{
itemList.Add(itemName);
}
}
protected void button_OnClick( object sender, EventArgs e )
{
List<string> itemList = (List<string>)Session["Items"];
if (itemList != null)
{
foreach (string item in itemList)
{
// do something with the selected item
}
}
}
Disable the ViewState. In case it cannot be done try using Session to store the view state

Resources