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

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.

Related

Fetch cached control in ASP.NET

I am using the following in ASP.NET webcontrols :
<%# OutputCache Duration="86400" VaryByParam="none" %>
This means that the control will be null on reload if it is already added to the cache. The problem is that on some page I want to hide this control and it would be great if this could be done from the MasterPage codebehind file(where it is loaded).
I have tried this :
if (Request.AppRelativeCurrentExecutionFilePath.ToLower().EndsWith("/sites/MySite/default.aspx") || Request.AppRelativeCurrentExecutionFilePath.ToLower().EndsWith("MySite.net"))
{
if(topGames_Mini1 != null)
{
//Load control
topGames_Mini1.visible=true;
}
}
else
{
Page.LoadControl("topGames_Mini1").Visible = false;
}
It will however throw the following exception in the else :
The file '/Bradspel/sites/MySite/community/topGames_Mini1' does not
exist.
you should better place the UserControl inside a Placeholder control. Then simply hide/show the Placeholder depending on your conditions.
The Placeholder does not render any tags for itself, so there is NO overhead of outer HTML tags.
I Assume you must have registered your UserControl in your Master page. So, place the userControl now inside a PlaceHolder control.
<asp:ContentPlaceHolder ID="MainContent" runat="server"><!-- Of Master Page -->
<asp:PlaceHolder ID="place1" runat="server">
<uc1:Test ID="Test1" runat="server" /><!-- Our User Control-->
</asp:PlaceHolder>
</asp:ContentPlaceHolder>
and in Code behind::
protected void Page_Load(object sender, EventArgs e)
{
if( _Some_Condition_)
place1.Visible = true;
else
// Hide PlaceHolder and thus all controls inside it
place1.Visible = false;
}

Keep a value accessible in Page.Init between postbacks in 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

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.

How to change public properties of User control?

I have a very simple user control in my web site project which has public property declarations as following
public DateTime StartDate
{
get
{
return _startDate;
}
set
{
_startDate = value;
}
}
public DateTime EndDate
{
get
{
return _endDate;
}
set
{
_endDate = value;
}
}
When i drag the ascx file to one of my aspx page and when i go to code behind of aspx page i can access the controls properties through intelisense, but when i run the project through visual studio i get error "The name 'uctTest1' does not exist in the current context " any suggetions to fix the error?
This is the line where Error shows when i run the project uctTest.StartDate = DateTime.Now;
aspx page markup :
<%# Page Language="C#" AutoEventWireup="true" CodeFile="removetest.aspx.cs" Inherits="removetest" %>
<%# Register src="~/uctTest.ascx" tagname="testCtl" 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>
<uc1:testCtl ID="uctTest1" runat="server" />
</div>
</form>
</body>
</html>
aspx page code behind :
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
uctTest1.StartDate = DateTime.Now;
}
}
protected void btnEdit_Click(object sender, EventArgs e)
{
}
It seems that you are missing the #Register directive in the .aspx file referring to path of .ascx control.
http://msdn.microsoft.com/en-us/library/c76dd5k1(v=vs.71).aspx
I had another aspx page with different file name in the website project that another developer had copied from my aspx page which had same <%# Page Language="C#" AutoEventWireup="true" CodeFile="aspxpagename.aspx.cs" Inherits="aspxpageClass" %> directive in that copied page. Even though that copied paged didnt have user control strangely page I was working reported the error. Changing that page directive's codefile,inherits attributes to different values fixed the issue.

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