AjaxControlToolkit issue in Async WebForms pages - asp.net

There is an issue with AjaxControlToolkit extenders (or any other extenders) in WebForms pages that run Async tasks. If your extender is not visible initially, and you run an async task where you make it visible (e.g. if the visibility should be determined based on the data that you read asynchronously), then you will get the following System.ArgumentException:
Extender control '[ControlID]' is not a registered extender control.
Extender controls must be registered using RegisterExtenderControl() before calling RegisterScriptDescriptors().
After some analysis, here is what causes this.
ExtenderControl base class, which all AjaxControlToolkit extenders
subclass, calls ScriptManager.RegisterExtenderControl() in its
OnPreRender method. Moreover, ScriptManager will throw an exception
if RegisterExtenderControl() is called during any stage other than
PreRender.
For controls that are not visible during the PreRender stage, the
OnPreRender method will not be called, and hence the extender
control will not be registered.
WebForms pages run all async tasks after the PreRender stage, and
before the Render stage. So, if you make your extender visible in
the async task, then it will not be visible yet in the PreRender
stage, and only in the Render stage.
Finally, extender controls call
ScriptManager.RegisterScriptDescriptors() during the Render stage,
which throws the above exception due to the control not having been
registered in the PreRender stage.
Has anyone found a fix or workaround for this?
This seems to be a huge limitation in WebForms, where you cannot effectively use both async tasks and extender controls in the same page.
Below is a sample web page that illustrates this issue.
<%# Page Async="true" MasterPageFile="~/Site.Master" Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>
<%# Import Namespace="System.Threading.Tasks" %>
<%# Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="Panel1" runat="server">
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<ajaxToolkit:CalendarExtender ID="CalendarExtender1" runat="server" TargetControlID="TextBox1" />
</asp:Panel>
</asp:Content>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Panel1.Visible = false;
RegisterAsyncTask(new PageAsyncTask(ReadAsync));
}
// Making the panel visible below will result in the following exception:
// Extender control 'CalendarExtender1' is not a registered extender control.
// Extender controls must be registered using RegisterExtenderControl() before calling RegisterScriptDescriptors().
private async Task ReadAsync()
{
Panel1.Visible = true;
await Task.CompletedTask;
}
</script>

Based on suggestions posted to this question here
https://forums.asp.net/p/2163988/6293999.aspx?p=True&t=637169211245961059
and here https://github.com/DevExpress/AjaxControlToolkit/issues/523, I have come up with a generic way to work around this problem.
Basically, your page would track controls that have extenders and can change their visibility while running in an async task. Then it will make those controls temporarily visible at the proper time on PreRender to avoid the error, and then hide them as needed after the PreRender is complete. The key here is to use the page's new SetControlVisible method to set control's visibility.
The full source code for this workaround is posted here: https://github.com/Xomega-Net/XomegaFramework/blob/master/src/Xomega.Framework.Web/Views/WebPage.cs

Related

Using Code Nuggets as property values

I have the following code
<asp:Content ID="Content1" runat="server">
<asp:Label runat="server" ID="Label1" Text="<%= MyHelperClass.Value%>"></asp:Label>
<%= MyHelperClass.Value%>
</asp:Content>
The Label doesn't work, it has the Text <%= MyHelperClass.Value%> the next row returns the expected value.
Question: can i use those code nuggets to set values of the property of an control?
Why it's working outside and not with Control?
Well <%= %> is called Content Code Nuggets because they inject content in the html which is sent by server to browser. It does the work of Reponse.Write. We use this mainly to call any code written in code behind file for example, you have a simple method in your code behind:-
public string UserCity()
{
return "London";
}
Then you can call it from aspx page like this:-
You live in <%= UserCity() %>.
Content code nuggets used to inject the html to response at the last in PreRender event and thus it's called late binding. This is the main reason why it is NOT working with your control.
How to fix this?
You can use the data binding code nuggets (<%# %>). These are used with DataBound controls but you can force the Page's DataBound or control's DataBound method to bind your control like this:-
<asp:Label runat="server" ID="Label1" Text="<%# MyHelperClass.Value%>"></asp:Label>
Call DataBind method in code behind:-
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Page.DataBind();
}
}

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

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.

TemplateField button causing GridView Invalid Postback

Ok, so I've got a template field in a gridview that contains just a simple button...
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Administration.aspx.cs"
Inherits="Administration" %>
<%# Register TagPrefix="ajaxToolkit" Namespace="AjaxControlToolkit" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Keywords Administration</title>
</head>
<body class="popupbody">
<form id="form1" runat="server">
<ajaxToolkit:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server" EnablePartialRendering="true" CombineScripts="false"></ajaxToolkit:ToolkitScriptManager>
<asp:Label ID="AddLabel" runat="server">Add a Keyword</asp:Label>
<br />
<asp:TextBox ID="AddTextBox" runat="server" />
<asp:Button ID="AddButton" Text="Add" runat="server" OnClick="AddKeyword_Click" />
<asp:GridView ID="KeywordsGridView" AllowPaging="false" AutoGenerateColumns="false" BackColor="white"
GridLines="None" HeaderStyle-CssClass="Table_Header" RowStyle-CssClass="Table_Style"
OnRowDataBound="RowBound" runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="References" SortExpression="References" HeaderText="Total References" />
<asp:BoundField DataField="Keyword" SortExpression="Keyword" HeaderText="Keyword" />
</Columns>
</asp:GridView>
</form>
</body>
</html>
Whenever I click the button I get the error...
Invalid postback or callback argument. Event validation is enabled using
<pages enableEventValidation="true"/> in configuration or <%# Page
EnableEventValidation="true" %> in a page. For security purposes, this feature
verifies that arguments to postback or callback events originate from the server
control that originally rendered them. If the data is valid and expected, use the
ClientScriptManager.RegisterForEventValidation method in order to register the
postback or callback data for validation.
I've found a decent amount of articles referencing this issue, including a couple on SO, for example...
Invalid postback or callback argument. Event validation is enabled using '<pages enableEventValidation="true"/>'
and...
Invalid postback or callback argument
I might just be misunderstanding, but as far as I can tell they don't really help me. How do I get this to go away without setting enableEventValidation="false"?
EDIT Posted all the code for my page.
I know this post is old, but I also had this problem recently.
After checking the how the code was generated for the asp:ButtonField and also for the asp:Button that I have added in an ItemTemplate I discovered quite a big difference:
asp:ButtonField:
input type="button" onclick="javascript:__doPostBack('ctl00$ctl00$cphMain$cphContent$gvEmails','SendFromBatch$0')" value="Continue">
asp:Button:
input id="ctl00_ctl00_cphMain_cphContent_gvEmails_ctl02_btnCont" type="submit" onclick="FreezeScreen('Emails are being sent...');" value="Continue" name="ctl00$ctl00$cphMain$cphContent$gvEmails$ctl02$btnCont">
The Type was the problem! So, after i changed the UseSubmitBehavior from "Submit" to "False" I did not get the error anymore.
EDIT:
I recently wanted to change from the normal btns to imgBtns so that I can save some space and also make the gridview nicer :D. Of course I had the same problem popping up and the imagebtn does not have UseSubmitBehavior property, so I started looking after a solution.
Found the following (The code below is in a UserControl).
1) Bind in !IsPostBack
2) Register the usercontrol itself in the Render
and everything works just as planned - no validation errors.
protected void Page_Load(object sender, EventArgs e)
{
OnLoadingEvent(new EventArgs());
if (!Page.IsPostBack)
{
gvEmails.DataSource = odsEmails;
try
{
gvEmails.DataBind();
}
catch (Exception)
{
}
}
if (!writeText) divWriteEmail.Visible = false;
}
protected override void Render(HtmlTextWriter writer)
{
Page.ClientScript.RegisterForEventValidation(this.UniqueID);
base.Render(writer);
}
EDIT:
I was playing around with the code above and asked myself, what if you actually need to rebing your objectdatasource - what happens then? Well the code above will not work if you rebind in the page load, because the gridview will be rebound again when you click a btn in the gridview's row and it will not be recognized as being generated on the server. Fair and square, but how to avoid this behaviour? Well... i figured out a way, maybe not the best but it does the trick.
Actually, you do not want to rebind the gridview when you click a btn on the gv's row... but how do we know that since the page load method is first called? Well, actually i do not want to rebind the gridview if the select param of the object data source do not change. So, I keep the select param of the objectDataSource in the session view and I rebind my gridview only when one of them changes - this behaviour is only during the PageLoad event. In order to see latest rows i click refresh and get the latest rows with no problem and at the moment i click a gvRow btn, the error dissapears.
To make all of this happen you have to
Directly call the .Select() of the object data source
Catch the OnSelecting event from the datasource, save the select param set the e.Cancel = true
Check if the select param are different and then bind the gridview and this time when catching the OnSelecting you have to set e.Cancel = false in order to retrieve the data from the DB - only one time.
Hope this helps!
This fellow found a solution to a similar problem (scroll down to about the 4th comment), which was to set unique id's for the GridView buttons.
I had the similar error today but with a different solution. I've worked with gridviews for years now and never had the issue so I figured it must be something stupid. Turns out I had forgotten to put my code that loads the gridview into a if(!Page.IsPostBack) block which caused the button to be recreated after I clicked it, thus causing the error. Placing the loading code in said block eliminated the issue.
You need to give your button an ID. Just a runat="server" does not meet the minimum information that needs to be provided for a server control to be created.

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