Right, I've got something very peculiar going on here...
ASP.NET 4 page with the following property:
protected QuickShopBag QuickShopBagInstance
{
get { return (QuickShopBag)ViewState["QuickShopBag"]; }
set { ViewState["QuickShopBag"] = value; }
}
During the initial Page_Load() in (!Page.IsPostBack) the QuickShopBagInstance is populated and ViewState saved.
However when you perform a postback on the page the ViewState is empty when accessed from the postback Button_OnClick() event!!!
I've checked the Request.Form and sure enough the _Viewstate value is there and is populated. I've also ran this value through a parser and it does contain the expected data, the page has ViewStateEnabled="true" and the new .NET 4 ViewStateMode="Enabled".
I've moved on to override the LoadViewState method to check to see if it is firing, it doesn't appear to be.
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
}
I am really lost as to what could possibly be the problem. Any ideas?
First of all I was mistaken, the code in question was not in Page_Load but in Page_Init, although I haven't read anything that says you can't assign to ViewState at Init.
So I put together a very basic test that duplicates the problems I'm having...
<form id="form1" runat="server">
<div>
<asp:ListView id="QuickshopListView" runat="server">
<LayoutTemplate>
<asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
<asp:TextBox ID="txtItem" runat="server" Text='<%# Container.DataItem %>' />
<asp:Button ID="btnDelete" runat="server" Text="Delete" OnClick="btnDelete_Click" />
<br />
</ItemTemplate>
</asp:ListView>
<asp:Button ID="btnAdd" runat="server" Text="Add" OnClick="btnAdd_Click" />
</div>
</form>
public partial class Quickshop : System.Web.UI.Page
{
protected QuickShopBag QuickShopBagInstance
{
get { return (QuickShopBag)ViewState["QuickShopBag"]; }
set { ViewState["QuickShopBag"] = value; }
}
protected void Page_Init(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
if (QuickShopBagInstance == null)
QuickShopBagInstance = new QuickShopBag();
if (!String.IsNullOrEmpty(Request.QueryString.ToString()))
{
string[] items = Server.UrlDecode(Request.QueryString.ToString()).Split(',');
if (items.Length > 0)
{
foreach (string item in items)
{
QuickShopBagInstance.QuickShopItems.Add(item);
}
}
}
}
}
protected void Page_LoadComplete(object sender, EventArgs e)
{
QuickshopListView.DataSource = QuickShopBagInstance.QuickShopItems;
QuickshopListView.DataBind();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
if (QuickShopBagInstance == null)
QuickShopBagInstance = new QuickShopBag();
QuickShopBagInstance.QuickShopItems.Add("add1");
QuickShopBagInstance.QuickShopItems.Add("add2");
QuickShopBagInstance.QuickShopItems.Add("add3");
}
protected void btnDelete_Click(object sender, EventArgs e)
{
Button DeleteButton = (Button)sender;
ListViewDataItem item = (ListViewDataItem)DeleteButton.NamingContainer;
QuickShopBagInstance.QuickShopItems.RemoveAt(item.DisplayIndex);
}
}
[Serializable]
public class QuickShopBag
{
public List<string> QuickShopItems { get; set; }
public QuickShopBag()
{
this.QuickShopItems = new List<string>();
}
}
If you request say "/quickshop.aspx?add1,add2,add3", the ListView is populated correctly with the data from the qs, however when it comes to clicking the delete button a NullReferenceException is thrown because the ViewState hasn't persisted the QuickShopBag object.
But if you click the "Add" button, which as you can see adds to the same values to the QuickShopBagInstance (and ViewState), the ListView is populated correctly and when you click the Delete button it works perfectly as the ViewState has been persisted.
Now if you change the reading the querystring bit to Page_InitComplete as opposed to Page_Init it works perfectly. So the conclusion is...
YOU CAN'T ADD TO THE VIEWSTATE BEFORE Init_Complete!!!!!!!!
How silly of me, well whoever wrote it at least!
You seem to have ruled out most of the suggestions so far so I've created a basic page with only the information you've provided above:
class
namespace SO_Questions
{
[Serializable()]
public class QuickShopBag
{
public string MyProperty { get; set; }
}
}
code behind
namespace SO_Questions
{
public partial class TestPage : System.Web.UI.Page
{
protected QuickShopBag QuickShopBagInstance
{
get { return (QuickShopBag)ViewState["QuickShopBag"]; }
set { ViewState["QuickShopBag"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
this.QuickShopBagInstance = new QuickShopBag() { MyProperty = "Test String" };
}
Message.Text = "Value is: " + this.QuickShopBagInstance.MyProperty.ToString();
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
btnSubmit.Text += QuickShopBagInstance.MyProperty;
}
}
}
markup:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="TestPage.aspx.cs" Inherits="SO_Questions.TestPage" ViewStateMode="Enabled" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"><title></title></head>
<body>
<form id="form1" runat="server"><div>
<asp:Label ID="Message" runat="server"></asp:Label>
<asp:Button runat="server" ID="btnSubmit" Text="Submit" OnClick="btnSubmit_Click" />
</div></form>
</body></html>
As expected, this runs correctly; the overridden LoadViewState method is hit (and viewstate correctly contains 2 items) and the button's text is updated.
The logical explanation would be that there's something else going on somewhere else, or you've failed to provide an additional salient piece of information.
Something that has tripped me up in the past is
Setting something in the ViewState in the page
Then trying to retrieve it in a user control. Can't find it - where has it gone?
It seems as if you should have one ViewState per page but each usercontrol keeps it's own version.
Could it be something like this?
This SO link gives a better explanation that I have just done
Just a quick note. If you are setting a ViewState value on page_load, make sure you are doing it wrapped in
if (!IsPostBack)
{
ViewState["MyValue"] = MyValue // set dynamically with appropriate code
}
If you don't do this and you do a postback...but your code setting this ViewState value is not in the !IsPostBack brackets, it will reset your ViewState value to null every time you do a postback, no matter where you enable ViewState on the page.
This may seem obvious, but it is easy to miss if you have a lot of code going on.
Or I guess you could not use !IsPostBack if you want your code to run on every postback. But if you are having trouble with a value getting set to null on postback, examine the above carefully.
Related
I have the following event handler in my code-behind file that fires whenever an HTML form is submitted to the server:
public void Validate_Form(object sender, EventArgs e)
{
// Check that the page is loaded due to a postback:
if (IsPostBack)
{
// Check that the page passed validation:
if (IsValid)
{
// perform some logic...
}
}
}
My question is do I need to explicitly call Validate() method right before my if (IsValid) directive? What would be the difference between the following:
if (IsValid) Validate();
{ if (IsValid)
... vs. {
} ...
}
And since I am not seeing any errors / warnings, does that mean that the two above are identical? Thanks!
They are not the same output.So explain this with an example. (You should turn off javascript for testing)
Suppose you have a page as follows:
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox runat="server" ID="txtTest1"></asp:TextBox>
<asp:RequiredFieldValidator ValidationGroup="ValidationGroup1" runat="server" ControlToValidate="txtTest1"></asp:RequiredFieldValidator>
<asp:Button runat="server" ValidationGroup="ValidationGroup1" ID="btnValidate1" Text="Validate1" OnClick="Validate_Form" />
<asp:TextBox runat="server" ID="txtTest2"></asp:TextBox>
<asp:RequiredFieldValidator ValidationGroup="ValidationGroup2" runat="server" ControlToValidate="txtTest2"></asp:RequiredFieldValidator>
<asp:Button runat="server" ValidationGroup="ValidationGroup2" ID="btnValidate2" Text="Validate2" OnClick="Validate_Form" />
</div>
</form>
</body>
If we use first method:
public void Validate_Form(object sender, EventArgs e)
{
// Check that the page is loaded due to a postback:
if (IsPostBack)
{
// Check that the page passed validation:
if (IsValid)
{
// perform some logic...
}
}
}
Validation buttons work properly.
But if we use second method:
public void Validate_Form(object sender, EventArgs e)
{
Validate();
// Check that the page is loaded due to a postback:
if (IsPostBack)
{
// Check that the page passed validation:
if (IsValid)
{
// perform some logic...
}
}
}
Then if fill txtTest1 and click on btnValidate1 IsValid return false! because Validate() checks all of validations.
I have a FormView with data(DataSource,DataBind) that I fill with value='<%# Eval("Name") %>' , but after I'm changing the text in TextBox and press update button I see the same value that before, I cant see new value that I have typed.
What I am missing here?
my html
<asp:FormView ID="MainFormTemplate" runat="server">
<ItemTemplate>
<li class="li_result" runat="server">
<div class="col-3">
<input id="txt_Name" runat="server" value='<%# Eval("Name") %>'>
</div>
</li>
</ItemTemplate>
</asp:FormView>
<asp:Button id="btn_Update" runat="server" OnClick="btn_Update_Click" Text="Update" />
Server side
protected void Page_Load(object sender, EventArgs e)
{
using (DB_MikaDataContext data = new DB_MikaDataContext())
{
MainFormTemplate.DataSource = data.File_Projects.Where(x => x.Num_Tik.Equals("12")).ToList();
MainFormTemplate.DataBind();
}
}
public void btn_Update_Click(object sender, EventArgs e)
{
//using System.Web.UI.HtmlControls
HtmlInputText twt = (HtmlInputText)MainFormTemplate.FindControl("txt_Name");
string text = twt.Value;//i see old value ,not new one that i typed in text box
}
In every postback, you are always getting the old value from your database. The solution is check if the page is being rendered for the first time (!IsPostBack) then set your MainFormTemplate's DataSource else if is being loaded in response to a postback (IsPostBack) get the txt_Name's value like this:
HtmlInputText twt;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
using (DB_MikaDataContext data = new DB_MikaDataContext())
{
MainFormTemplate.DataSource = data.File_Projects.Where(x => x.Num_Tik.Equals("12")).ToList();
MainFormTemplate.DataBind();
}
}
else
{
twt = MainFormTemplate.FindControl("txt_Name") as HtmlInputText;
}
}
protected void btn_Update_OnClick(object sender, EventArgs e)
{
string text = twt.Value; // You will get the new value
}
with Page_Load executing every postback, you are always writing value from database (?), and value sent from browser is lost (although still exist in Page.Request.Form member).
In ASP.NET, When a page is submitted, the Page_Load event runs before the button click event. So, the textbox value gets repopulated with its original value before the click event looks at that value.
If this is the situation, then you can wrap the code that assigns the value to the textbox in an if block like this:
if (!IsPostBack)
{
HtmlInputText twt = (HtmlInputText)MainFormTemplate.FindControl("txt_Name");
string text = twt.Value;
}
Hope this helps you.
I have a FromDate and ToDate textboxes and a submit button in my Master page. I have 4 tabs with links for 4 different URLs displaying various reports.
Now on change of Dates and click of submit button, can I update/reload the reports (tabs) based on date change?
Thanks a lot in advance :)
I would suggest moving your from-to dates and submit button to a user control. You can then put that on each report, expose and wire up changed events on your control and expose properties for your from-to date textboxes to pop into your report.
In Visual Studio create a user control. If you are unsure how to do this try this link.
Populate the user control with your text boxes. Something like this:
<div>
<asp:Label ID="FromDateLabel" Text="From:" AssociatedControlID="FromDateTextBox" runat="server" />
<asp:TextBox ID="FromDateTextBox" runat="server" />
<asp:Label ID="ToDateLabel" Text="To:" AssociatedControlID="ToDateTextBox" runat="server" />
<asp:TextBox ID="ToDateTextBox" runat="server" />
<asp:Button ID="UpdateButton" Text="Update" runat="server"
onclick="UpdateButton_Click" />
</div>
And the code behind for that control. You'll need to expose an event and the two properties, which might look like this:
public partial class ReportDateControl : System.Web.UI.UserControl
{
public event EventHandler UpdateReport;
public string FromDate
{
get { return this.FromDateTextBox.Text; }
set { this.FromDateTextBox.Text = value; }
}
public string ToDate
{
get { return this.ToDateTextBox.Text; }
set { this.ToDateTextBox.Text = value; }
}
protected void UpdateButton_Click(object sender, EventArgs e)
{
if (UpdateReport != null)
{
UpdateReport(this, EventArgs.Empty);
}
}
}
In your .aspx page you'll need to register the control, which might go something like this:
<%# Register Src="~/Controls/ReportDateControl.ascx" TagPrefix="myapp" TagName="ReportDateControl" %>
And then actually put it on the page:
<myapp:ReportDateControl id="ReportDateControl"
runat="server"
OnUpdateReport="ReportDateControl_UpdateReport" />
And then wire up the code behind to handle the update events:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void ReportDateControl_UpdateReport(object sender, EventArgs e)
{
Controls.ReportDateControl control = (Controls.ReportDateControl)sender;
string fromDate = control.FromDate;
string toDate = control.ToDate;
}
}
Change the names and formatting where appropriate, but this should give you a good idea.
Also, you can expose the Date controls from the master page and access them through the Page.Master property. You'll need to cast to the specific type of your master page to get at it's properties.
Basically, in a nutshell, the problem is that dynamically generated triggers for an UpdatePanel can no longer be found (by ASP.NET) as soon as I add them as children of a custom control.
Since the amount of code I'm working on is quite substantial I've recreated the problem on a smaller scale, which will make it easier to debug.
The error thrown in my face is:
A control with ID 'theTrigger' could not be found for the trigger in UpdatePanel 'updatePanel'.
I'm not sure whether this implementation of a "custom control" is the right way to go about it, but I did not write the original implementation: I'm working with code written by a previous developer to which I cannot make large modifications. It looks a little unusual to me, but, alas, this is what I've been given.
Default.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestWeb.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Panel runat="server" ID="panel">
</asp:Panel>
<asp:ScriptManager ID="scriptManager" runat="server"></asp:ScriptManager>
<asp:UpdatePanel runat="server" ID="updatePanel" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="lblSomething" runat="server"></asp:Label>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>
Default.aspx.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace TestWeb
{
public partial class Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
UselessTableWrapper table = new UselessTableWrapper();
TableRow tr = new TableRow();
TableCell td = new TableCell();
LinkButton button1 = new LinkButton { ID = "theTrigger", Text = "Click Me" };
button1.Click += button1_Click;
td.Controls.Add(button1);
tr.Controls.Add(td);
table.AddRow(tr);
panel.Controls.Add(table);
// ### uncomment these lines (and comment the one above) to see it working
// ### without the custom control
/*
Table realTable = new Table();
realTable.Controls.Add(tr);
panel.Controls.Add(realTable);
*/
updatePanel.Triggers.Add(new AsyncPostBackTrigger { ControlID = "theTrigger", EventName = "Click" });
scriptManager.RegisterAsyncPostBackControl(button1);
}
protected void button1_Click(object sender, EventArgs e)
{
lblSomething.Text = "Random number: " + new Random().Next(100);
updatePanel.Update();
}
}
}
MyControl.cs
using System;
using System.Web.UI.WebControls;
namespace TestWeb
{
public class UselessTableWrapper : WebControl
{
private Table table = new Table();
protected override void OnPreRender(EventArgs e)
{
Controls.Add(table);
}
public void AddRow(TableRow row)
{
table.Controls.Add(row);
}
}
}
Any ideas would be greatly appreciated.
Edit
I've tried switching the OnPreRender event for this (found in a tutorial):
protected override void RenderContents(HtmlTextWriter writer)
{
writer.BeginRender();
table.RenderControl(writer);
writer.EndRender();
base.RenderContents(writer);
}
... hoping that it would fix it, but it does not.
this is the approach that I've taken with loading a ascx web control inside an aspx control from the code behind.
In the control:
namespace dk_admin_site.Calculations
{
public partial class AssignedFieldCalculation : System.Web.UI.UserControl
{
public static AssignedFieldCalculation LoadControl(Calculation initialData)
{
var myControl = (AssignedFieldCalculation) ((Page) HttpContext.Current.Handler).LoadControl(#"~\\Calculations\AssignedFieldCalculation.ascx");
myControl._initialData = initialData;
return myControl;
}
private Calculation _initialData;
public Calculation Data { get { return _initialData; } }
protected void Page_Load(object sender, EventArgs e) {}
}
}
in the web form code behind:
protected void Page_Load(object sender, EventArgs e)
{
if (this.IsPostBack)
{
if (ScriptManager1.AsyncPostBackSourceElementID.StartsWith("ctl00$MainContent$calc") && ScriptManager1.AsyncPostBackSourceElementID.EndsWith("$btnRemoveCalculationFromField"))
{
//do something on the postback
}
else if (ScriptManager1.AsyncPostBackSourceElementID.StartsWith("ctl00$MainContent$calc") && (ScriptManager1.AsyncPostBackSourceElementID.EndsWith("$btnMoveCalculationUp") || ScriptManager1.AsyncPostBackSourceElementID.EndsWith("$btnMoveCalculationDown")))
{
//do something on the postback
}
}
foreach (Calculation calc in calculationCollection)
{
AssignedFieldCalculation asCalc = AssignedFieldCalculation.LoadControl(calc);
asCalc.ID = "calc" + calc.UniqueXKey;
pnlFieldCalculations.Controls.Add(asCalc);
foreach (Control ct in asCalc.Controls)
{
if (ct.ID == "btnMoveCalculationDown" || ct.ID == "btnMoveCalculationUp" || ct.ID == "btnRemoveCalculationFromField")
{
ScriptManager1.RegisterAsyncPostBackControl(ct);
}
}
}
}
A few things to note:
You need to make each control ID unique when adding it to the asp:Panel (called pnlFieldCalculations).
The LoadControl method allows you to pass initial arguments
I have Created A Custom Control which is a DropDownList with specified Items. I designed AutoPostback and SelectedCategoryId as Properties and SelectedIndexChanged as Event for My Custom Control.
Here Is My ASCX file Behind Code:
private int _selectedCategoryId;
private bool _autoPostback = false;
public event EventHandler SelectedIndexChanged;
public void BindData()
{
//Some Code...
}
protected void Page_Load(object sender, EventArgs e)
{
BindData();
DropDownList1.AutoPostBack = this._autoPostback;
}
public int SelectedCategoryId
{
get
{
return int.Parse(this.DropDownList1.SelectedItem.Value);
}
set
{
this._selectedCategoryId = value;
}
}
public string AutoPostback
{
get
{
return this.DropDownList1.AutoPostBack.ToString();
}
set
{
this._autoPostback = Convert.ToBoolean(value);
}
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
if (SelectedIndexChanged != null)
SelectedIndexChanged(this, EventArgs.Empty);
}
I Want Used Update Panel to Update Textbox Fields According to dorp down list selected index.
this is my code in ASPX page:
<asp:Panel ID="PanelCategory" runat="server">
<p>
Select Product Category:
<myCtrl:CategoryDDL ID="CategoryDDL1" AutoPostback="true" OnSelectedIndexChanged="CategoryIndexChanged"
SelectedCategoryId="0" runat="server" />
</p>
<hr />
</asp:Panel>
<asp:UpdatePanel ID="UpdatePanelEdit" runat="server">
<ContentTemplate>
<%--Some TextBoxes and Other Controls--%>
</ContentTemplate>
<Triggers>
<asp:PostBackTrigger ControlID="CategoryDDL1" />
</Triggers>
</asp:UpdatePanel>
But Always The Selected Index of CategoryDDL1 is 0(Like default). this means Only Zero Value will pass to the event to update textboxes Data. what is the wrong with my code? why the selected Index not Changing? Help?
If your BindData() method is completely self-contained, move that from Page_Load to:
protected override void OnInit(EventArgs e)
{
BindData();
}
This will keep your dropdown list in your control from being rebound on every page load, which I assume is the problem from the code that you've posted.
If, however, your BindData() method requires information from the parent page, change the page load to:
protected void Page_Load(object sender, EventArgs e)
{
if(!this.Page.IsPostback) {
BindData();
}
DropDownList1.AutoPostBack = this._autoPostback;
}
This will allow your dropdown to be bound only on the first page load, and subsequent loads should be able to access the properties correctly.
Also, be sure to check your ASPX page to make sure you're not binding the ASCX control on every page load. This can be resolved in the same way on the parent page.