Dynamic Validation Controls Don't Work in Update Panel - asp.net

I'm dynamically creating validation controls and adding them to an update panel. However the client side validation never fires.
Here is the aspx file:
<div>
<asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Conditional" runat="server">
<ContentTemplate>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</ContentTemplate>
<Triggers >
<asp:AsyncPostBackTrigger ControlID ="Button1" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<asp:Button ID="Button1" runat="server" Text="Button" CausesValidation="true"/>
</div>
Here is the code behind:
Dim Survey As New Survey
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Survey.RenderPage(PlaceHolder1)
End Sub
Here is the class that creates the validation control:
Public Class Survey
Public Sub RenderPage(ByVal PlaceHolder As PlaceHolder)
Dim textbox As New TextBox
textbox.ID = "testing"
PlaceHolder.Controls.Add(textbox)
Dim val As New RequiredFieldValidator
val.ControlToValidate = textbox.ID
val.Text = "required"
val.EnableClientScript = True
PlaceHolder.Controls.Add(val)
End Sub
End Class
When you hit next, client side validation never fires. What's really weird is that when you wrap the button inside another update panel, the validation fires (in IE and Firefox, but not in Chrome or Safari).
Anyone have any ideas what's going on? I know that the first versions of Asp.net AJAX didnt support the validation controls, but everything is up to date on my end.

I see there 2 problems
When update panel causes async post back to server it cannot create tree of controls with your dynamic controls - so check that you call RenderPage from Page_Load for ScriptManager.IsInAsyncPostBack == true
There is issue of usage validators under update panel - the scripts must be loaded before update panel works. I can propose you to allocate fictive RequiredFieldValidator under UpdatePanel. Set them Display=none (but not Visible=false !!!) or place to nonexistence ValidationGroup. This allows to render JScript into you page.

Related

Losing update panel trigger from databound gridview in tab container

I'm having some trouble getting a command link in a gridview to maintain it's ability to change tabs after the initial postback. So below you will see the structure of my content (heavily simplified):
<ajaxToolkit:TabContainer runat="server" ID="tabBody">
<ajaxToolkit:TabPanel runat="server" ID="tabPanel1">
<ContentTemplate>
<asp:UpdatePanel runat="server" ID="updPanel1">
<ContentTemplate>
<asp:Gridview runat="server" ID="grd1" OnRowCommand="grd1_RowCommand" OnRowDataBound="grd1_RowDataBound">
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="lnkChangePanels" runat="server" CommandArgument='<%#Eval("id") %>' CommandName="gotopanel2" Text='<%#Eval("FirstName") & " " & Eval("LastName")%>' />
</ItemTemplate>
</asp:TemplateField>
</asp:Gridview>
</ContentTemplate>
</asp:UpdatePanel>
</ContentTemplate>
</ajaxToolkit:TabPanel>
<ajaxToolkit:TabPanel runat="server" ID="tabPanel2">
<ContentTemplate>
<asp:UpdatePanel runat="server" ID="updPanel2">
<ContentTemplate>
<asp:Gridview runat="server" ID="grd2">
</asp:Gridview>
</ContentTemplate>
</asp:UpdatePanel>
</ContentTemplate>
</ajaxToolkit:TabPanel>
</ajaxToolkit:TabContainer>
In order to fill the gridview on panel 1, there is a search box which the user types into and I call a function to bind a linq query to it.
Now I add the rowcommand as a postback trigger on rowdatabound:
Protected Sub grd1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs)
If e.Row.RowType = DataControlRowType.DataRow Then
Dim lb As LinkButton = CType(e.Row.FindControl("lnkChangePanels"), LinkButton)
If Not lb Is Nothing Then
ToolkitScriptManager1.RegisterPostBackControl(lb)
End If
End If
End Sub
Then here is the code I have to trigger the tab panel to change (and do some other stuff):
Protected Sub grd1_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles grd1.RowCommand
Dim id = e.CommandArgument.ToString()
Select Case e.CommandName
Case "gotopanel2"
eventDetails(id, "C")
tabBody.ActiveTab = tabPanel2
End Select
End Sub
This causes a proper postback and changes the tab, everything works as intended. But if I go back to the first tab and try clicking another row in gridview 1, nothing happens.
Is there a way to structure this so the that either the tab can change without losing the postback trigger or am I going about this all wrong?
Thanks.
Postback trigger is not lost. Problem is caused by individual UpdatePanels in each tab.
Put entire TabContainer within UpdatePanel and you can remove UpdatePanels from tabs (but you don't have to). Make sure that UpdateMode of that new panel is set to "Always".
I think the reason why it does not change in your example is that UpdatePanel only refreshes it's own content and attribute that decides if tab is visible or not is set for div (tabPanel) outside that UpdatePanel. When you go back to tab with grid you do it client-side by clicking on it and that's when it goes wrong.
To get to the bottom of the problem and figure out why it does work during the first postback you would probably have to debug ajax toolkit javascript for TabContainer control.

Dropdown autopost inside updatepanel never enters code behind SelectedIndexChanged event

This is a difficult scenerio to explain, so I coded up a simple example.
<asp:UpdatePanel runat="server" ID="upTest" ChildrenAsTriggers="true"
UpdateMode="Conditional">
<ContentTemplate>
<asp:DropDownList ID="ddlTest" runat="server" AutoPostBack="true"></asp:DropDownList>
<br /><br />
In page: <asp:TextBox runat="server" ID="txtTest" Columns="50" Text="OnLoad</asp:TextBox>
<br />
<br />
<asp:Button runat="server" ID="btnTest" Text="Click it" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="ddlTest" />
</Triggers>
</UpdatePanel>
Code-Behind
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Page.IsPostBack Then
BindDropDown(Request.Form(ddlTest.UniqueID))
Else
BindDropDown(0)
End If
End Sub
Public Sub BindDropDown(val As Integer)
ddlTest.Items.Add(New ListItem("", 0))
ddlTest.Items.Add(New ListItem("One", 1))
ddlTest.Items.Add(New ListItem("Two", 2))
ddlTest.Items.Add(New ListItem("Three", 3))
ddlTest.Items.Add(New ListItem("Four", 4))
ddlTest.SelectedValue = val
End Sub
Private Sub ddlTest_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles ddlTest.SelectedIndexChanged
txtTest.Text = "Dropdown changed"
End Sub
Private Sub btnTest_Click(sender As Object, e As System.EventArgs) Handles btnTest.Click
txtTest.Text = "Button clicked"
End Sub
Also to note, I have EnableViewState="false" and ClientIdMode="Static" for the page.
When I click the button the partial postback occurs and the textbox has the expected value of 'Button Clicked'
When I change the dropdown the partial postback occurs, however the textbox does not have the expected value of 'Dropdown changed' because the code in ddlTest_SelectedIndexChanged is never hit (breakpoint placement on that line of code also never hits)
In the larger scope of my project this is the crux of the problem and I can't determine why this event is never hit (the core problem is when I load a dynamic user control, the data in the control is bound correctly, but then is overridden with the pre-post data after the user control is loaded - the override is occurring somewhere in the post back events). It appears to me it has something to do with the binding of the dropdown and where it happens in the page cycle, but I haven't been able to nail anything down, nor come up with google solutions.
One odd thing I did notice when looking at the post in Firebug - the EVENTTARGET value for the dropdown was the UniqueId of the dropdown, but the EVENTTARGET value is empty for the button click. My brain is thinking there is some connection here.
FYI - I have come up with a work around that seems to do the trick. I set autopostback on dropdown to false, and using jQuery I assign the change event on the dropdown to fire the button click - which causes the postback and fires btn_click event in code.
I solved the initial issue here by moving the dropdown fill to the init event and then letting .NET load the selected value from view state
However in doing this, now the selectedindex event fires on EVERY postback.

How to determine which button caused postback

I have 2 button controls. When I click one i'm trying to determine which one caused a postback in the page load. How to do determine this?
What about using CommandName and CommandArgument has shown in this example. This way you can have just one handler.
<asp:Button id="Button1"
Text="Sort Ascending"
CommandName="Sort"
CommandArgument="Ascending"
OnCommand="CommandBtn_Click"
runat="server"/>
<asp:Button id="Button2"
Text="Sort Descending"
CommandName="Sort"
CommandArgument="Descending"
OnCommand="CommandBtn_Click"
runat="server"/>
Do you come from a Classic ASP background? When I first used ASP.NET, the same question occurred to me.
Consider an alternative approach:
Rather than detect the postback in the Form_Load, and then figure out what triggered it, create a specific event handler for each of your buttons. This is the whole point of Web Forms - so you can develop apps in very similar ways as you would Windows applications.
Really input with type button sends its value within post request. For example if you have you'll get in Post button-name=Quote like it's simple text input. So you can just check if post contains value for the button using code like following (sorry for my vb):
Dim isQuote As Boolean = HttpContext.Current.Request.Form(SubmitQuote.UniqueID) IsNot Nothing
so if it's not Nothing (null) then post has been sent by SubmitQuote button.
BTW for me HttpContext.Current.Request("__EVENTTARGET") didn't work either.
In my implementation there are several forms on my page; if a post-back was triggered by certain button controls further operations are necessary.
The controls are of the following type, which do not populate Request["__EVENTTARGET"]
Button (at the root of the form)
Image Button (nested within a Datagrid)
I determine if the following button controls instigated the post-back, by reviewing that the UniqueID of the control was passed to the form request within the Page_Load sub:
- ASP.NET:
<asp:Button ID="Button1" runat="server" />
<asp:Button ID="Button2" runat="server" />
To simply handle whether the following nested image button instigated the post-back I take advantage of the OnClientClick attribute which calls to a javascript function that will populate the value of a supplementary hidden field control with the UniqueID of the instigating control, then review the hidden control value similarly in the Page_Lode sub:
- ASP.NET:
<script type="text/javascript">
function SetSource(SourceID) {
var hiddenField = document.getElementById("<%=HiddenField1.ClientID%>");
hiddenField.value = SourceID;
}
</script>
<asp:HiddenField ID="HiddenField1" runat="server" Value="" />
<asp:ImageButton ID="ImageButton1" runat="server" OnClientClick="SetSource(this.id)" />
The Page_Load would then implement by some means:
-VB.NET
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
' ...
If Not Page.IsPostBack Then
If Not String.IsNullOrEmpty(Me.Page.Request.Form(Button1.UniqueID)) Then
' ...
ElseIf Not String.IsNullOrEmpty(Me.Page.Request.Form(Button2.UniqueID)) Then
' ...
ElseIf Not Me.Page.Request.Form(HiddenField1.UniqueID) Is Nothing _
And Not String.IsNullOrEmpty(Me.Page.Request.Form(HiddenField1.UniqueID)) Then
' ...
HiddenField1.Value = String.Empty
Else
' ...
End If
End If
End Sub
on page load check this
String ButtonID = Request["__EVENTTARGET"];

PlaceHolders and Persistent Dynamic Controls (ADVANCED)

Warning! This problem is not for the feint of heart. I've spent several days in all trying to troubleshoot it.
I have a Wizard control with about 5 steps, each with several controls ranging from very simple to very complex such as custom jQuery combo boxes based on DropDownLists and TextBoxes. Each step has several of the controls wrapped in a PlaceHolder control.
The Wizard along with all PlaceHolders and their child Controls are nested inside of a View of a MultiView. On the same page I have another View styled like a Form, but not one. This view has corresponding PlaceHolders for each of PlaceHolders within each step of the Wizard.
Depending on the ReferralUrl I call the following function to "Toggle" the view from the Wizard to the form style view by moving all the controls, then setting the active view as follows:
Protected Sub ToggleView() Handles ViewToggle.Click
If Wizard_mv.ActiveViewIndex = 0 Then
ViewToggle.Text = "Toggle Wizard View"
fPH1.Controls.Add(wPH1)
fPH2.Controls.Add(wPH2)
fPH3.Controls.Add(wPH3)
fPH4.Controls.Add(wPH4)
fPH5.Controls.Add(wPH5)
Wizard_mv.ActiveViewIndex = 1
ElseIf Wizard_mv.ActiveViewIndex = 1 Then
ViewToggle.Text = "Toggle Form View"
wPH1.Controls.Add(fPH1)
wPH2.Controls.Add(fPH2)
wPH3.Controls.Add(fPH3)
wPH4.Controls.Add(fPH4)
wPH5.Controls.Add(fPH5)
Wizard_mv.ActiveViewIndex = 0
End If
End Sub
Immediately after this, I use another function for pre-filling the controls with values from a record in a database. After allowing the users to make some changes, they may resubmit the updated record to the database. The problem is that this works just fine if I do so from the Wizard but not after toggling. The trace shows that the Control Tree is empty at the time of submitting the updated record, and hence I cannot grab the user-entered/pre-filled values from this view. The pre-filling works great and the "Selected" values are all correct. The problem arises on the PostBack after clicking submit and it loses all the values and controls.
Please do not answer unless you fully understand my problem and are willing to help. I think the problem very well lies within the page lifecycle. Mysteriously, when I submit from my Wizard, on the postback in Page_Init I get my control values loaded, however when I submit from my form view, neither the controls nor their values are loaded. From what I read, this is an issue of persistence. Really hoping there's a relatively easy solution for this.
Here's a sample of my markup in my Wizard (all styling removed for brevity):
<asp:WizardStep ID="WizardStep1" runat="server" Title="Product Info">
<asp:PlaceHolder ID="wPH1" runat="server" ViewStateMode="Enabled">
<asp:Table ID="ProductInfoTable" runat="server" Width="100%">
<asp:TableHeaderRow>
<asp:TableHeaderCell><h3>Product Line</h3></asp:TableHeaderCell>
<asp:TableHeaderCell><h3>Publication Type</h3></asp:TableHeaderCell>
<asp:TableHeaderCell><h3>Request Type</h3></asp:TableHeaderCell>
</asp:TableHeaderRow>
<asp:TableRow>
<asp:TableCell>
<div class="ui-widget">
<!-- Autocomplete Combobox -->
<asp:DropDownList ID="productLine_ddl" runat="server" DataSourceID="productLineSDS" ViewStateMode="Enabled" DataTextField="Product" DataValueField="ID"></asp:DropDownList>
<asp:TextBox ID="productLine_cb" runat="server" EnableViewState="True"></asp:TextBox>
<button id="productLine_btn" type="button" title="Show All Items"></button>
</div>
</asp:TableCell>
<asp:TableCell>
<asp:DropDownList ID="docType_ddl" runat="server" DataSourceID="docTypeSDS" DataTextField="DocType" DataValueField="ID"></asp:DropDownList>
</asp:TableCell>
<asp:TableCell>
<asp:DropDownList ID="requestType_ddl" runat="server" DataSourceID="requestTypeSDS" DataTextField="RequestType" DataValueField="ID"></asp:DropDownList>
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell ColumnSpan="2">
<asp:MultiView ID="Attachment_mv" runat="server" ActiveViewIndex="0">
<!-- File Upload/Browsing Display -->
<asp:View ID="AttachmentUploadView" runat="server">
<h3 class="inlineH">Attach File: </h3>
<asp:FileUpload ID="AttachmentFile_btn" runat="server" />
<asp:Button ID="UploadFile_btn" runat="server" Text="Upload File" />
</asp:View>
<!-- File Attached Display -->
<asp:View ID="FileAttachedView" runat="server">
<h3 class="inlineH">Uploaded File: </h3>
<asp:Label ID="FileAttachedLabel" runat="server" Text="Label"></asp:Label>
<asp:Literal ID="FilesOnServer" runat="server" />
</asp:View>
</asp:MultiView>
</asp:TableCell>
</asp:TableRow>
</asp:Table>
</asp:PlaceHolder>
</asp:WizardStep>
My Page Lifecycle Events, as requested (in chronological order for your convenience) :)
Dim referrerPage As String
'Initialize Dynamic controls here. These are controls that need to be prefilled, etc.
Private Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Init
'DO NOT PREFILL at this stage, as the Controls are not yet rendered and will cause errors.
' Use this section for detecting the "referrerPage" and missing "id" parameters when expected, etc.
If Not IsPostBack Then
ViewState.Clear()
End If
Try
referrerPage = Right(Request.UrlReferrer.AbsolutePath, Len(Request.UrlReferrer.AbsolutePath) - Request.UrlReferrer.AbsolutePath.LastIndexOf("/"c) - 1)
Catch ex As Exception
If Not String.IsNullOrEmpty(Session.Item("referrerPage")) Then
referrerPage = Session.Item("referrerPage")
End If
End Try
If StrComp(referrerPage, "wizard.aspx") <> 0 And String.IsNullOrEmpty(Session.Item("referrerPage")) Then 'Initialize Session state to remember "referrerPage"
Session.Add("referrerPage", referrerPage)
End If
If StrComp(referrerPage, "formdetails.aspx") = 0 Then
If String.IsNullOrEmpty(Request.Params("id")) Then
'This line checks for an expected "id" param value and if none exists, forwards the page back to "tcom.aspx"
Response.Redirect(Request.UrlReferrer.AbsolutePath)
Else
ToggleView()
End If
End If
End Sub
'Prefill Dynamic controls here.
Private Sub Page_PreLoad(sender As Object, e As EventArgs) Handles Me.PreLoad
If Not IsPostBack Then
productLine_ddl.DataBind()
docType_ddl.DataBind()
requestType_ddl.DataBind()
'...and several more DataBinds for each individual
' control in the wizard. Nothing more.
End If
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
If Not IsPostBack Then
'Benign code for querying the database to get User info for Page.User.Identity.Name
End If
End Sub
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As EventArgs) Handles Me.PreRender
If Not IsPostBack Then
'Here is completely benign code for hiding a couple controls.
End If
End Sub
Protected Sub Page_PreRenderComplete(ByVal sender As Object, ByVal e As EventArgs) Handles Me.PreRenderComplete
'PREFILL HERE when appropriate. This occurs after checking the "referrerPage" and ensuring a value for the "id" parameter.
If Not IsPostBack Then
Try
If Not String.IsNullOrEmpty(Request.Params("id")) Then
PrefillWizard(Request.Params("id"))
Else : output.Text += "Source: " + Request.UrlReferrer.AbsolutePath
End If
Catch ex As Exception
End Try
End If
End Sub
To anyone who can help with this, you have my eternal gratitude. ;)
The problem is that the control tree of the page must be exactly the same during every postback. That means that you have to add all the control every time no mater which ActiveViewIndex is set. Preferably in CreateChildControls or page init. In the ToggleView function you can than set the visibility of the controls.

Hidden/Shown AsyncFileUpload Control Doesn't Fire Server-Side UploadedComplete Event

I recently came across the AsyncFileUpload control in the latest (3.0.40412) release of the ASP.Net Ajax Control Toolkit. There appears to be an issue when using it in a hidden control that is later revealed, such as a <div> tag with visible=false.
Example:
Page code -
<%# Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="act" %>
.
.
.
<act:ToolkitScriptManager runat="server" ID="ScriptManager1" />
<asp:UpdatePanel runat="server" ID="upnlFileUpload">
<ContentTemplate>
<asp:Button runat="server" ID="btnShowUpload" Text="Show Upload" />
<div runat="server" id="divUpload" visible="false">
<act:AsyncFileUpload runat="server" id="ctlFileUpload" />
</div>
</ContentTemplate>
</asp:UpdatePanel>
Server-side Code -
Protected Sub ctlFileUpload_UploadedComplete(ByVal sender As Object, ByVal e As AjaxControlToolkit.AsyncFileUploadEventArgs) Handles ctlFileUpload.UploadedComplete
End Sub
Protected Sub btnShowUpload_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnShowUpload.Click
divUpload.Visible = True
End Sub
I have a breakpoint on the UploadedComplete event but it never fires. However, if you take the AsyncFileUpload control out of the <div>, making it visible at initial page render, the control works as expected.
So, is this a bug within the AsynchUploadControl, or am I not grasping a fundamental concept (which happens regularly)?
First, make sure your tag has the following attribuytes - enctype="multipart/form-data" method="post" Secondly, you have to have the AsyncFileUpload in an invisible DIV within a visible DIV. See these two threads on it.
http://forums.asp.net/t/1489399.aspx
http://forums.asp.net/t/1479689.aspx?PageIndex=2

Resources