11
votes

I'm under the impression that a control in a nested UpdatePanel will cause the top level UpdatePanel to refresh (thus refreshing both UpdatePanels) because any events on that control act as an "implicit" trigger. Is that correct?

I've been trying to wire something like this up-

UserControl

   Parent UpdatePanel

   "Show" button

      ASP:Panel

         Dynamically added UserControls, each with UpdatePanels

When the Show button is clicked, the ASP:Panel becomes visible and starts adding UserControls to itself dynamically, based on some back-end logic.

Each of the dynamically added controls (henceforth: UserControls) have their own Atlas-enabled buttons and links, so they have UpdatePanels too. Currently when I click on a link in one of the UserControls, the entire contents of the ASP:Panel disappear, as if it's being re-rendered. All of my dynamically-added controls disappear, and none of their click events are caught in the debugger.

I'm assuming what's happening here is that controls that reside in nested update panels are causing the parent UpdatePanel to post back because they're firing "implicit" triggers. Is there a way for my UserControls to operate autonomously and not mess with the ASP:Panel that contains them?

If not, what strategy should I be pursuing here? If I have to re-render the entire ASP:Panel every time an event happens on one of the (possibly many) UserControls, that means I'll have to recreate the UserControls, which take a bit of effort to create. I'll also have to preserve some kind of view state to recreate them. I'm somewhat new to ASP.NET and that sounds intimidating. I'd rather never refresh the top leve UserControl and ASP:Panel if I can avoid it, and let each of the dynamically-added UserControls fire and handle their own events asynchronously.

EDIT: Instead of adding the controls dynamically, I added them to the markup(not a bad solution). So got rid of the controls disappearing problem, because now the controls are not added dynamically but instead exist in the markup. But still the parent UpdatePanel posting is a big performance hit, because all the UserControls are getting posted instead of one. How do I make only one UserControl postback? Also, I would like to know how to get rid of the problem of controls disappearing if added dynamically?

2

2 Answers

17
votes

First off, keep in mind: UpdatePanels do not alter the lifecycle of a page.

All of the Control Tree (including the UpdatePanels) must be reconstructed just as with a Normal Postback Life-cycle.12 The UpdatePanels ensure that only a portion of the rendered (HTML) view is returned. Removing all UpdatePanels should result in the same behavior, except with a full postback. For instance, only the HTML representing a nested UpdatePanel (presumably because data changed) might be sent back in the XHR response.

To get "true" AJAX consider Page Methods. Alternatively, DevExpress (and perhaps Telerik and others?) offer their own form of "Callback Panels", which are similar to UpdatePanels, but can bypass parts of the life-cycle (and, as a result often do not support the ViewState model entirely or may introduce their own quirks).


While not understanding the above is the most likely reason for the controls "disappearing", here is my rule: Do Not Let [Nested] UpdatePanels Work "Automatically".

Edge cases with dynamic controls and nested UpdatePanels will be encountered. There might be a nice way to handle this, but I have failed on multiple different attempts.

Instead, for every update panel, run with:

UpdateMode="Conditional"
ChildrenAsTriggers="False"

With the "Conditional" UpdateMode, make sure to manually specify the Trigger control or call panel.Update() (although this hard-wires the Control) as required. Depending on needs ChildrenAsTriggers="True" might work as well. The big thing is that UpdateMode is not "Always" which is the default.

After switching to this approach, I have no problem -- well, almost none -- with nested UpdatePanels.

Happy coding!


1 If the page doesn't render correctly where Partial Rendering (in the ScriptManager) is disabled (e.g. all requests are full postbacks) then there is no reason to expect/believe it will work correctly with UpdatePanels.

2 There are times when it's warranted to "cheat" expensive recomputations in the control tree for controls that will not be re-rendered. However, I would consider these advanced cases that should only be done when performance analysis indicates there is a specific need.

3
votes

I had a similar issue with a heavy ajax Gridview control, and a HTML page with multiple UpdatePanels, some nested, some not.

It took me over 3 weeks of trial and error, reading, testing, debugging and prototyping to finally resolve all the post backs and partial postbacks.

However I did notice a pattern, which worked for me.

Like the @user above's response, make sure your UpdatePanels are set Conditional and only specificy asp:PostBackTrigger on the final or decisive control that you want the page to do a full postback on. I always use asp:AsyncPostBackTrigger where ever possible.

So for example, if you're using UpdatePanels inside a GridView and you want a popup inside a cell of the gridview's row, then you need to use a nested UpdatePanel.

ie

 <asp:TemplateField HeaderText="Actions &amp; Communications">
    <ItemTemplate>
        <asp:UpdatePanel ID="upAction1" runat="server" UpdateMode="Conditional">
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="btnActionOK" />
            </Triggers>
            <ContentTemplate>
...
...
                            <ajaxToolkit:ModalPopupExtender ID="ajaxMPE" runat="server"
                                BackgroundCssClass="modalBackground"
                                PopupControlID="upAction2"
                                TargetControlID="btnADDaction">
                            </ajaxToolkit:ModalPopupExtender>
                            <asp:UpdatePanel ID="upAction2" runat="server" UpdateMode="Conditional">
                                <ContentTemplate>
                                    <asp:Panel ID="pnlACTION" runat="server" CssClass="pnlACTION">
                                        <div id="divHDR">
                                            <asp:Label ID="lblActionHdr" runat="server">** header **</asp:Label>
                                        </div>
                                        <div id="divBOD">
                                            <table style="width: 98%; text-align:left">
                                                <tr>
                                                    <td colspan="2">
                                                    Please enter action item:<br />
                                                        <asp:TextBox ID="txtAction" runat="server" ValidationGroup="vgAction" TextMode="MultiLine" Rows="3" Width="98%"></asp:TextBox>
                                                        <asp:RequiredFieldValidator ID="rfvAction" runat="server" ControlToValidate="txtAction" ValidationGroup="vgAction"
                                                            ErrorMessage="* Required" SetFocusOnError="True"></asp:RequiredFieldValidator></td>
                                                </tr>
                                                <tr>
                                                    <td colspan="2">
                                                    Select staff assigned to this task:<br />
                                                        <asp:DropDownList ID="ddlActionStaff" runat="server" AppendDataBoundItems="true" ValidationGroup="vgAction" />
                                                        <asp:RequiredFieldValidator ID="rfvStaff" runat="server" ControlToValidate="ddlActionStaff" InitialValue="0" ValidationGroup="vgAction"
                                                            ErrorMessage="* Required" SetFocusOnError="True" Width="98%"></asp:RequiredFieldValidator></td>
                                                </tr>
                                                <tr>
                                                    <td colspan="2" style="text-align: center">
                                                            <asp:Button ID="btnActionOK" runat="server" Text="OK" OnClick="btnActionOK_Click" ValidationGroup="vgAction" CausesValidation="false" />
                                                            <asp:Button ID="btnActionCANCEL" runat="server" Text="CANCEL" OnClick="btnActionCANCEL_Click" ValidationGroup="vgAction" CausesValidation="false" />
                                                    </td>
                                                </tr>
                                            </table>
                                        </div>
                                    </asp:Panel>
                                </ContentTemplate>
                            </asp:UpdatePanel>
                        </td>
                    </tr>
                </table>
                <asp:SqlDataSource ID="sdsTETactions" runat="server" ConnectionString="<%$ ConnectionStrings:ATCNTV1ConnectionString %>"
...
...
                </asp:SqlDataSource>
            </ContentTemplate>
        </asp:UpdatePanel>
    </ItemTemplate>
    <ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>

Notice a few things here:

  • The ModalPopupExtender does not refer to the Ok and Cancel buttons. This is because I want them to trigger a partial postback to the server (code-behind)
  • The nested UpdatePanel (upAction2) is purely to force a partial postback on that panel only (pnlAction)! Therefore this forces the data validation triggers to work and keep the panel open if the user does not provide data in the requiredfield validators
  • The main UpdatePanel (upAction1) controls the entire lot and keeps all postbacks held within that panel only and NOT affecting the gridview (not shown entirely).

There is a great article here, which explains it better.

I hope this helps someone