Reputation: 3442
I have a page that lists current projects, if you select one of the projects it loads the same page with a parameter and shows the users that belong to that project. For some reason it sometimes (depends on the project) shows the name of the project in the textbox where the username should be.
protected void Page_Load(object sender, EventArgs e)
{
var dbProxy = new DatabaseProxy();
// No parameter, show project list
if (this.Request.QueryString["ProjectID"] == null)
{
var user = new UserContainer();
user.LoadUser(HttpContext.Current.User.Identity.Name);
List<ProjectContainer> projects = dbProxy.GetProjects(user.UserID, true);
foreach (ProjectContainer project in projects)
{
var linkButton = new Button {Text = project.ProjectName};
this.ProjectsPanel.Controls.Add(linkButton);
}
}
// We have a parameter, show users for project
else
{
int projectId = int.Parse(this.Request.QueryString["ProjectID"]);
this.LoadUsers(projectId);
}
}
The LoadUsers function:
private void LoadUsers(int projectId)
{
var databaseProxy = new DatabaseProxy();
List<UserContainer> users = databaseProxy.GetUsersByProject(projectId);
foreach (UserContainer user in users)
{
var txtUsername = new TextBox();
txtUsername.Text = user.Username; // <- Shows projectname!
var txtUsername2 = new TextBox();
txtUsername2.Text = user.Username; // <- Shows username!
this.UserPanel.Controls.Add(txtUsername);
this.UserPanel.Controls.Add(txtUsername2);
}
}
The HTML code:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditUsers.aspx.cs" Inherits="xxx.EditUsers1" MasterPageFile="PageStructure.Master" %>
<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="ContentPlace">
<asp:Panel ID="Panel1" runat="server" style="padding-left: 25px">
<div style="margin-bottom: 50px">
<asp:Label ID="lblHeader" runat="server" Text="Benutzer verwalten" CssClass="PageHeader"></asp:Label>
</div>
<asp:Panel ID="UserPanel" runat="server"></asp:Panel>
<asp:Panel ID="ProjectsPanel" runat="server"></asp:Panel>
</asp:Panel>
</asp:Content>
The master page only contains a header and a footer and some JavaScript for a different page.
The output:
https://i.sstatic.net/p49jh.png
The user variable contains the right values and nothing more happens after LoadUsers. I can even add an empty label as first element, then it will show the correct username in the first textbox! I don't know why, but it just looks like it really wants the project name in the first element I add.
Example how it works, too:
private void LoadUsers(int projectId)
{
var databaseProxy = new DatabaseProxy();
List<UserContainer> users = databaseProxy.GetUsersByProject(projectId);
foreach (UserContainer user in users)
{
var lbl = new Label();
lbl.Text = ""; <- Stays invisible!
var txtUsername = new TextBox();
txtUsername.Text = user.Username; // <- Shows username!
var txtUsername2 = new TextBox();
txtUsername2.Text = user.Username; // <- Shows username!
this.UserPanel.Controls.Add(lbl);
this.UserPanel.Controls.Add(txtUsername);
this.UserPanel.Controls.Add(txtUsername2);
}
}
I guess I could find a workaround, like splitting it in two different web forms, but I still would like to know why I get the wrong value.
Upvotes: 2
Views: 1846
Reputation: 1777
The problem is caused by an out-of-sync view-state of the controls.
In ASP.NET, each form control has a view-state that keeps track of entered values so that when a page gets reloaded after a post-back, it can automatically fill in the previous values into each form control. Normally this will work fine, and is usually the desired behaviour. However, on some situations (like this one) where the controls are dynamically generated and their values got changed after returning from a post-back, then their view-states will end up overwriting the values and hence causing a problem.
To resolve this issue, either disable the post-back for the dynamically generated controls, like so:
foreach (UserContainer user in users)
{
var txtUsername = new TextBox();
txtUsername.EnableViewState = false;
txtUsername.Text = user.Username; // <- Shows projectname!
var txtUsername2 = new TextBox();
txtUsername2.EnableViewState = false;
txtUsername2.Text = user.Username; // <- Shows username!
this.UserPanel.Controls.Add(txtUsername);
this.UserPanel.Controls.Add(txtUsername2);
}
Or assign a different ID to each control each time it is generated so that the control will never match to the ID of the saved data in the viewstate, like so:
foreach (UserContainer user in users)
{
var txtUsername = new TextBox();
txtUsername.ID = Guid.NewGuid().ToString("N");
txtUsername.Text = user.Username; // <- Shows projectname!
var txtUsername2 = new TextBox();
txtUsername2.ID = Guid.NewGuid().ToString("N");
txtUsername2.EnableViewState = false;
txtUsername2.Text = user.Username; // <- Shows username!
this.UserPanel.Controls.Add(txtUsername);
this.UserPanel.Controls.Add(txtUsername2);
}
Warning: If you use either of the methods above, the values of the controls will not be sent back during a post-back or form submit. So any user-entered values in any of those controls will be lost. To avoid this problem, use Javascript to copy those values to a hidden control (that is not dynamically generated) on form submit. Or just simply don't use dynamically generated controls (if that is possible).
For more information on view-state vs dynamic controls, please check out: http://msdn.microsoft.com/en-us/library/hbdfdyh7%28v=vs.100%29.aspx
Upvotes: 2