Keep a value accessible in Page.Init between postbacks in asp.net - asp.net

Ok, I've already learned that in order to keep the dynamic (created) controls and their viewstate in asp.net we must (re)create them in the Page init event, so they already exist within the page's control hierarchy before the view state is loaded.
As it says in this article.
There is no problem to achieve this behaviour if the criteria to create these controls is outside the web environment, for example a database. But what should I do if what I use to decide how many dynamic controls I have to create is actually a value that is in the controls?
I try to explain it with an example, maybe it's clearer:
Let's say that we have a textbox and two buttons. In the textbox I write the number of how many dynamic controls I want to create, for example 4 checkbox. When I hit the button1 the controls should be created. No problem. Then I check some checkboxes and hit the button2 just to fire a postback. Now I should recreate the controls in the page init event, like we said before, in order to maintain the controls and their state.
And here comes the problem. Because of I'm in the init stage I have no viewstate so I'm no able to access the value in the textbox that tells me how many dynamic checkbox should I create.
I thought that storing the value in the session object would do the trick, but it doesn't. The session object is no accessible as well.
Where can I save the value that it'll be accessible from the init event too?
Thanks and sorry for the long post!

First thing - textbox value is not stored/retrieved from view state, you cannot get textbox value from viewstate.
Coming to actual problem, here is the sequence of (imp) events init -> load view state -> bind postback data -> page load. You can retrieve textbox value only after bind postback data event (which actually takes posted data and binds to the textbox control).
In init only option is to use Request.Form{"textboxid"] to get the textbox value.

You are on the right track.
If you use TextBox, you do not need another ViewState to keep track of how many controls has been created, because TextBox control has its own ViewState already.
You can use either Page_Init or Page_Load to load control back.
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs"
Inherits="WebApplication2010.WebForm1" %>
<!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:TextBox runat="server" ID="NumberTextBox" />
<asp:Button runat="server" ID="CreateControlButton"
OnClick="CreateControlButton_Click"
Text="Create Control" />
<br />
<asp:PlaceHolder runat="server" ID="PlaceHolder1"></asp:PlaceHolder>
</div>
</form>
</body>
</html>
using System;
using System.Web.UI;
namespace WebApplication2010
{
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
int ids;
if (Int32.TryParse(NumberTextBox.Text, out ids))
{
for (int i = 0; i < ids; i++)
{
Control ctrl = Page.LoadControl("WebUserControl.ascx");
ctrl.ID = i.ToString();
PlaceHolder1.Controls.Add(ctrl);
}
}
}
}
protected void CreateControlButton_Click(object sender, EventArgs e)
{
}
}
}
<%# Control Language="C#" AutoEventWireup="true"
CodeBehind="WebUserControl.ascx.cs"
Inherits="WebApplication2010.WebUserControl" %>
<asp:CheckBox runat="server" ID="CheckBox1" />
<asp:Button runat="server" ID="Button1" OnClick="Button_Click"
Text="Post Back" />
<asp:Label runat="server" ID="Label1" />
<br />
using System;
namespace WebApplication2010
{
public partial class WebUserControl : System.Web.UI.UserControl
{
protected void Button_Click(object sender, EventArgs e)
{
Label1.Text = " This control was posted back.";
}
}
}

This is the common paradox with page lifecycle when you work with code behind. For example, you save the editing customerid in viewstate but controls need to bind on init to be able to read their posted values.
Best real life solution is to do what bound controls do on postback, call explicilately the LoadPostBackData on IPostBackDataHandler interface that all controls implement, after you have created them in Page Load event.
You should create an extension method for Controls and call it when needed:
control.DataBind();
control.LoadPostedData(); // extension method

Related

Dynamically creating ASP.NET Web User Control contain AJAX Extender is throwing error "Extender controls may not be registered after PreRender"

I am trying to dynamically add a web user control that contains an AJAX collapsible panel with a Gridview inside the panel when a user clicks on a button. I am able to add a single instance of the user control but when I add additional user controls it throws the following error:
Description: An unhandled exception occurred during the execution of
the current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Extender controls
may not be registered after PreRender.
Source Error:
An unhandled exception was generated during the execution of the
current web request. Information regarding the origin and location of
the exception can be identified using the exception stack trace below.
I am new to ASP.NET Development and the method I am using to handle postbacks is to store the user controls in a list and add them again on prerender method call. I am not sure what to do or if I am handling the creation of the user controls correctly. Any advice is appreciated.
Here is the back end code:
public partial class Test : System.Web.UI.Page
{
private IList<Control> _persistedControls;
private const string PersistedControlsSessionKey = "thispage_persistedcontrols";
static int count = 1;
private IList<Control> PersistedControls()
{
if (_persistedControls == null)
{
if (Session[PersistedControlsSessionKey] == null)
Session[PersistedControlsSessionKey] = new List<Control>();
_persistedControls = Session[PersistedControlsSessionKey] as IList<Control>;
}
return _persistedControls;
}
protected void Page_Load(object sender, EventArgs e)
{
PersistedControls();
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
IList<Control> controlsToGenerate = PersistedControls();
// regenerate dynamically created controls
foreach (var control in controlsToGenerate)
{
MasterPanel.Controls.Add(control);
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Control control = LoadControl("~/WebUserControl/UseCaseSetupUserControl.ascx");
control.ID = "uc" + count++.ToString();
MasterPanel.Controls.Add(control);
_persistedControls.Add(control);
MasterPanel.Controls.Add(new LiteralControl("<br />"));
_persistedControls.Add(new LiteralControl("<br />"));
}
}
Here is the ASPX Code:
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Test.aspx.cs" Inherits="Test" %>
<!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">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
<br />
<asp:Panel ID="MasterPanel" runat="server">
</asp:Panel>
</div>
</form>
</body>
</html>
Create your controls as early as possible in the Page life cycle.
Since you have the PersistedControls on load, that is where you should add them to the controls collection. The best place to add controls in Init.
If you really can't do that then use Load.
If this must be done on an event, then you can add the control there for the first time, but persist it and remember to add it back on Init or Load from the next postback onwards.
If you add controls after PreRender they cannot add any of their data to the ViewState. SaveViewState occurs just before PreRender. A lot of controls rely heavily on the ViewState to function properly. The exception is simply telling you that you should add your control earlier on in the Page lifecycle.

Disable ASP.NET viewstate for some controls but not all

How can I disable viewstate in my ASP.NET page for most controls, but allow some controls to still use ViewState?
I have tried:
In the properties I change EnableViewState=false
Use <%# Page Language="C#" EnableViewState="false" ... > at the top of the page
But how do I enable some controls to still allow viewstate?
I am using .NET 4.
I found a way, but only use this in the .NET framework 4. Put this in page directive:
<% ---- ViewStateMode="Disabled" %>
and use this for each controls that want to save the Viewstate (For example):
<asp:DropDownList ID="comGrade" runat="server" ViewStateMode="Enabled">
</asp:DropDownList>
ASP.NET 4 allows for more control over the viewstate. See MSDN's documention on the ViewStateMode property. Also the question on SO Minimizing viewstate- confused by EnableViewState and ViewStateMode in asp.net 4.0.
In ASP.NET prior to v4, disabling ViewState disables it for all children as well, regardless of setting a child to EnableViewState="true". In ASP.NET 4 you may re-enable the child by following the MSDN docs suggestion:
To disable view state for a page and to enable it for a specific
control on the page, set the EnableViewState property of the page and
the control to true, set the ViewStateMode property of the page to
Disabled, and set the ViewStateMode property of the control to
Enabled.
Sounds like a job for ControlState:
http://msdn.microsoft.com/en-us/library/1whwt1k7.aspx
ViewState can be disabled at the application, page, or control level.
To disable at the application level, put the following into your web.config file:
<Pages EnableViewState="false" ... />
To disable a particular page, you do it declaratively in the page directive:
<%# Page EnableViewState=”false”
or programmatically in a page event:
private void Page_Init(object sender, System.EventArgs e)
{
this.EnableViewState = false;
}
Finally, to disable viewstate on a particular control, you can use the following:
<asp:datagrid EnableViewState="false" ... />
Keep in mind that certain controls will not work properly with viewstate turned off. Sometimes you can keep viewstate enabled for a particular control but minimize the size of the viewstate payload by carefully determining where in the ASP.Net eventing pipeline to populate the control. You can read this excellent reference for more information.
Set the ViewStateMode to Disabled on the page, and omit EnableViewState. You can set it to enabled for some controls (default is Inherit).
Edit (sidenote, see comments too):
As we discussed in the comment, text boxes keep their value even though ViewState is disabled. This is true, as they are elements of a HTTP POST request. Labels aren't, for instance, as they render to span tags.
In the following sample code, there is a label filled with a random number. If you set ViewStateMode to Enabled, the random number from the last request is written to the Debug Output Window. If you set ViewStateMode to Disabled, an empty string is written. The Label's state is not kept:
<%# Page Language="C#"
AutoEventWireup="true"
CodeBehind="Default.aspx.cs"
Inherits="WebApplication2.Default"
ViewStateMode="Disabled" %>
<!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">
<asp:TextBox runat="server" ID="textbox" Text="Default" />
<asp:Label runat="server" ID="randomNumberLabel" />
<asp:Button runat="server" Text="Click Me" />
</form>
</body>
</html>
This is the code behind. Be sure to attach a debugger:
namespace WebApplication2
{
public partial class Default : System.Web.UI.Page
{
private readonly static Random rnd = new Random();
protected void Page_Load(object sender, EventArgs e)
{
if(this.IsPostBack)
Debug.WriteLine(this.randomNumberLabel.Text);
this.randomNumberLabel.Text = rnd.Next(Int32.MaxValue).ToString();
}
}
}
I hope I managed to clarify the difference.

ASP.NET Web Forms multiple views and the lifecycle of controls that are not Visible

Given a scenario where a page/control should display different views (like tabs) in different circumstances (query string argument, postback from a control, user setting retrieved from the database) I would normally put a control like MultiView or Placeholder and switch the active view index or the Visible property like this:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="TestWebApplication.WebForm2" %>
<%# Register Src="SomeControl.ascx" TagName="SomeControl" TagPrefix="uc1" %>
<!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:PlaceHolder runat="server" ID="phControl" Visible="false">
<uc1:SomeControl ID="SomeControl1" runat="server" />
</asp:PlaceHolder>
<asp:PlaceHolder runat="server" ID="phNotControl" Visible="false">
Some other content
</asp:PlaceHolder>
</div>
</form>
</body>
</html>
And I would switch the views in a code behind depending on the logic like this:
using System;
namespace TestWebApplication
{
public partial class WebForm2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
phControl.Visible = false;
phNotControl.Visible = true;
}
}
}
This is the control:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="SomeControl.ascx.cs" Inherits="TestWebApplication.SomeControl" EnableViewState="false" %>
I am the control
And the code behind of the control:
using System;
namespace TestWebApplication
{
public partial class SomeControl : System.Web.UI.UserControl
{
protected void Page_Init(object sender, EventArgs e)
{
Response.Write("I am the control's Init and I am executing heavy operations <br />");
}
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("I am the control's Load <br />");
}
protected void Page_PreRender(object sender, EventArgs e)
{
Response.Write("I am the control's PreRender and I am only called if the control is Visible <br />");
}
}
}
However what if the control in question has some relatively heavy work to do (calling the database) in its init and load events? They get executed even if the control is not Visible and will slow down the page. One possible solution I see is moving all the work in the PreRender method of the child control because it is not executed for controls which are not visible. However this will not work if the control accepts user input from some dynamically populated control (think of dropdownlist) because the logic that populates the dropdown with options will not be executed.
I've come up with the following solutions:
Move the heavy work in the PreRender method and wrap it in an if(!IsPostBack). Then let the ViewState hold the possible values for the dropdown and ASP.NET will restore it. The downside is that it is using ViewState and bumping up the size of the page.
Create the control dynamically with LoadControl and add it in the placeholder from code behind. This will trigger catch up events and the code that populates the dropdown can be put in the Init method where it belongs. The downside is that the layout is defined in the code behind. Looking at the markup one cannot see what controls are on the page and looking at the code behind it is not clear where the control will appear. It also makes styling and other "design" operations more difficult.
Leave it like it is. The downside is that there will be additional database queries and if there are a lot of views these would add up.
My actual question is how I solve this problem without putting stuff in the ViewState, without multiple queries and without dragging my layout into the code behind.
Could it be a solution to make an AJAX call for your heavy database work?
Then you could display the View and in the background load the data you need and display it. The page would load really fast and you could display some indication that data is currently being loaded.
How about creating a public method on in your user controls, such as InitControl(). Do all the heavy work within InitControl() instead on page load. Then, on parent page, when page is being shown, call InitControl.
using System;
namespace TestWebApplication
{
public partial class WebForm2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
phControl.Visible = false;
phNotControl.Visible = true;
SomeControl1.InitControl();
}
}
}
Don't do any work within OnLoad event of your user controls.

Using same form on multiple pages?

I am writing an application, and I have a user form, which will be the same for the users and the administrators on different pages.
I want to only create the form once, and then be able to put it on two different aspx files.
I tried it with the "control", but it then gets really complicated trying to access fields on the control from the aspx page to do the calculation, etc.
is there another way to do this? creating a form in one place, be able to add it to any aspx page, and have easy access to it's controls?
It's not very difficult at all. You can provide an accessor method or make the control inside public.
Example: A page which displays the contents of a TextBox inside a control, when a button is pressed.
Control
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="WebApplication1.WebUserControl1" %>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
Page
<form runat="server">
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<Test:Control ID="ctlTest" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
</form>
Code (If TextBox1 is public)
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = ctlTest.TextBox1.Text;
}
Or you could have, in the code of the control
public string GetText()
{
return TextBox1.Text;
}
And in the aspx code page
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = ctlTest.GetText();
}
What is so difficult about that?!
you have to create MasterPage:
http://msdn.microsoft.com/en-us/library/wtxbf3hh.aspx
so that you can share common controls between pages. Master pages can be nested, too. You can access controls on master page from child page via Master.FindControl("abc")
EDIT: it seems that you want to reuse common piece of functionality, then you might want to use user control, like in this example:
http://msdn.microsoft.com/en-us/library/3457w616.aspx
you created the control, and do not use findControl() method to access the control that inside you UserControl, that is not feasible or proper solution.
Better to use Properies. Define Properties, so your control can be generalised and you can use that on multiple pages.

ASP.NET: How to destroy dynamically added User Control

I added user control to a PlaceHolder with an ID phCustomFields. When I want to remove the user control in phCustomFields, i call phCustomFields.Controls.Clear(). However, after doing this, my usercontrol's Page_Load method still gets hit (i verified this by putting a breakpoint in the user control.) Why is my user control's page_load method still being called? i thought the instance of my user control was removed and destroyed once i called the Clear() method.
Update
Here is some sample code that demonstrates my problem. By setting the breakpoint at the Page_Load method of HelloWorld.ascx and debugging Page.aspx, the debug process would stop at the Page_Load method in HelloWorld.ascx which is expected and fine by me. But when I click on "Remove HelloWorld.ascx" button to remove the user control and then click on "Do Nothing" button to cause a postback, the debug process STILL stops at HelloWorld.ascx's Page_Load method. However, this shouldn't happen because the Clear() method in phTest was called.
Page.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Page.aspx.cs" Inherits="WebApplication1.Page" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Control C = LoadControl("HelloWorld.ascx");
phTest.Controls.Add(C);
}
protected void Remove_OnClick(object sender, EventArgs e)
{
phTest.Controls.Clear();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button Text="Does nothing" runat="server" />
<asp:Button Text="Remove HelloWorld.ascx" OnClick="Remove_OnClick" runat="server" />
<asp:PlaceHolder ID="phTest" Visible="false" runat="server">
</asp:PlaceHolder>
</div>
</form>
</body>
</html>
HelloWorld.ascx
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="HelloWorld.ascx.cs" Inherits="WebApplication1.HelloWorld" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
int i = 0;
}
</script>
<b>Hello World</b>
The OnClick event happens after the Page_Load event in the Page Life Cycle. On a postback the user controls Page_Load event will still be called. If you put the Clear() command later in the parent page life cycle, that should help.
EDIT As per your update. The reason that the Page_Load of the user control is happening every time is because you have the code to add that control to the page in the Page_Load event of your .aspx page. Basically, every time the page is requested, that user control is going to be added to the page(and hit the user control Page_Load event).
If you want a certain action to happen/not happen on a postback use the IsPostBack property of the page. Not sure exactly what you want to do with the page, but hopefully that helps a little bit.
Clear() definitely removes the controls from the control tree and hence the page lifecycle. Internally it sets the reference to the control to null.
The only way this could still be happening is if another instance of that control lives somewhere else in the control tree, or you're not calling Clear as early as you think, or not calling it on the right collection. At what point in the page lifecycle are you calling Clear()?
Edit: you are calling Clear in the Remove_OnClick evnet handler, which is long after Page_Load has fired for all controls. OnLoad will fire for the page and for all controls before it moves on to the next event.
Here's a few relevant highlights from the webforms page life cycle:
Page load event handled
Postback events, including click events handled
Page pre-render event handled
So you have the option of performing whatever processing you need to do in the control in the Page_PreRender event handler instead of Page_Load. As this occurs after the postback events are handled, it will not happen if the control has been removed.

Resources