开发者

asp.net FindControl Recursively

This is a really weird one - I will do my best to explain.

I have a basic master page:

<%@ Master Language="VB" CodeFile="MasterPage.master.vb" Inherits="master_MasterPage" %>

<!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>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
        <asp:PlaceHolder ID="PH1" runat="server" />
        <asp:PlaceHolder ID="PH2" runat="server" />
    </div>
    </form>
</body>
</html>

And a standard child page:

  <%@ Page Title="" Language="VB" MasterPageFile="~/master/MasterPage.master" AutoEventWi开发者_StackOverflow社区reup="false" CodeFile="Default.aspx.vb" Inherits="master_Default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
</asp:Content>

I have the following extension methods for finding controls recursively:

Option Strict On
Option Explicit On

Imports System.Runtime.CompilerServices
Imports System.Web.UI

Public Module ExtensionMethods

    <Extension()> _
    Public Function FindControlRecursively(ByVal parentControl As System.Web.UI.Control, ByVal controlID As String) As System.Web.UI.Control

        If parentControl.ID = controlID Then
            Return parentControl
        End If

        For Each c As System.Web.UI.Control In parentControl.Controls
            Dim child As System.Web.UI.Control = FindControlRecursively(c, controlID)
            If child IsNot Nothing Then
                Return child
            End If
        Next

        Return Nothing

    End Function

    <Extension()> _
    Public Function FindControlIterative(ByVal rootControl As Control, ByVal controlId As String) As Control

        Dim rc As Control = rootControl
        Dim ll As LinkedList(Of Control) = New LinkedList(Of Control)

        Do While (rc IsNot Nothing)
            If rc.ID = controlId Then
                Return rc
            End If
            For Each child As Control In rc.Controls
                If child.ID = controlId Then
                    Return child
                End If
                If child.HasControls() Then
                    ll.AddLast(child)
                End If
            Next
            rc = ll.First.Value
            ll.Remove(rc)
        Loop

        Return Nothing

    End Function

End Module

I have a control with a listview:

<%@ Control Language="VB" AutoEventWireup="false" CodeFile="control-1.ascx.vb" Inherits="controls_control_1" %>
<p>
    Control 1</p>
<asp:ListView ID="lv" runat="server">
    <ItemTemplate>
        <div>
            <asp:Literal ID="Name" runat="server" Text='<%#Eval("Name") %>' />
            <asp:LinkButton ID="TestButton" runat="server">Test</asp:LinkButton>
        </div>
    </ItemTemplate>
</asp:ListView>

That is databound:

Partial Class controls_control_1
    Inherits System.Web.UI.UserControl

    Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        If Not Page.IsPostBack Then

            Dim l As New List(Of Person)
            Dim j As New Person
            j.Name = "John"
            l.Add(j)

            lv.DataSource = l
            lv.DataBind()

        End If

    End Sub

End Class

Public Class Person
    Public Property Name As String
End Class

I have a second control that is very basic:

<%@ Control Language="VB" AutoEventWireup="false" CodeFile="control-2.ascx.vb" Inherits="controls_control_2" %>
<p>Control 2</p>

In my child page, I have the following code to load the controls:

Option Strict On
Option Explicit On

Partial Class master_Default
    Inherits System.Web.UI.Page

    Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init

        Dim controlInstance1 As System.Web.UI.Control = LoadControl("~/controls/control-1.ascx")
        controlInstance1.ID = "control_1"

        Dim zone As System.Web.UI.Control = Me.Master.FindControlRecursively("PH1")

        zone.Controls.Add(controlInstance1)

        Dim controlInstance2 As System.Web.UI.Control = LoadControl("~/controls/control-2.ascx")
        controlInstance2.ID = "control_2"

        Dim zone2 As System.Web.UI.Control = Me.Master.FindControlRecursively("PH2")

        zone2.Controls.Add(controlInstance2)

    End Sub

End Class

This loads the controls, but if I click the Test button in the listview, the page loses the data in the listview after postback.

If I change the FindControlRecursively calls to FindControlIterative, when I click the test button, the data in the listview is retained after the postback.

Anybody have any idea what the FindControlRecursively call might be doing to cause the listview to lose it's data? This only happens if control-2 is added to the page - if it is not, and control-1 is loaded using FindControlRecursively, data is retained correctly after postback.

Thanks in advance...this one is driving me nuts, and it took me a while to figure out where exactly it was breaking down.


Why don't you simply expose properties that return PH1 and PH2, because the master has the reference of them and you don't need to iterate all child controls of master:

Public ReadOnly Property Container1 As PlaceHolder
    Get
        Return Me.PH1
    End Get
End Property

Public ReadOnly Property Container2 As PlaceHolder
    Get
        Return Me.PH2
    End Get
End Property

You can access them:

Dim ph1 As PlaceHolder = DirectCast(Me.Master, myMaster).Container1
Dim ph2 As PlaceHolder = DirectCast(Me.Master, myMaster).Container2

Another problem is this line:

controlInstance1.ID = "control_2"

You are setting only controlInstance1's ID twice, but that doesn't cause your issue.

Your main problem is that you are adding the controls to the placeholders in Page_Init instead of Page_Load. Therefore the UserControls can't load their ViewState and the ListView is empty. Recreate them in Page_Load and it will work.

Edit: But i must admit that i don't know why your iterate extension wins over the recursive. The reference on the placeholders are the same, they shouldn't work both, weird.

summary:

  • it works with my properties,
  • putting all in page's load event handler instead init
  • with your iterate-extension(for whatever reasons)

It also works if you add both UserControls at last, after you've found the placeholders via FindControlRecursively.

zone.Controls.Add(controlInstance1)
zone2.Controls.Add(controlInstance2)

I'm losing motivation on this, but i'm sure you'll find the answer here. Controls.Add loads it's parent's ViewState into all childs and therefore it depends on when you add the controls, also the index of the controls in their parent controls must be the same on postback to reload the ViewState.

Your recursive extension method touches control1's ID after you've added it to PH1(while searching PH2), the iterative extension does not. I assume that this corrupts it's ViewState in Page_Init.

Conclusion Use properties instead


I figured out why I was seeing the behavior I described above. I changed the recursive function to the following:

<Extension()> _
    Public Function FindControlRecursively(ByVal parentControl As System.Web.UI.Control, ByVal controlId As String) As System.Web.UI.Control

        If String.IsNullOrEmpty(controlId) = True OrElse controlId = String.Empty Then
            Return Nothing
        End If

        If parentControl.ID = controlId Then
            Return parentControl
        End If

        If parentControl.HasControls Then
            For Each c As System.Web.UI.Control In parentControl.Controls
                Dim child As System.Web.UI.Control = FindControlRecursively(c, controlId)
                If child IsNot Nothing Then
                    Return child
                End If
            Next
        End If

        Return Nothing

    End Function

By adding the parentControl.HasControls check, I am preventing the function from searching the listview for child controls, and this allows the listview to load its viewstate later on in the page/control lifecycle.

Also, I tweaked my iterative function to make it more efficient and prevent it from bugging out if a control was not returned:

<Extension()> _
    Public Function FindControlIteratively(ByVal parentControl As Web.UI.Control, ByVal controlId As String) As Web.UI.Control

        Dim ll As New LinkedList(Of Web.UI.Control)

        While parentControl IsNot Nothing
            If parentControl.ID = controlId Then
                Return parentControl
            End If
            For Each child As Web.UI.Control In parentControl.Controls
                If child.ID = controlId Then
                    Return child
                End If
                If child.HasControls() Then
                    ll.AddLast(child)
                End If
            Next
            If (ll.Count > 0) Then
                parentControl = ll.First.Value
                ll.Remove(parentControl)
            Else
                parentControl = Nothing
            End If
        End While

        Return Nothing

    End Function

Also, to follow up on my earlier description of the problem - I was able to reproduce the recursive function's intially weird behavior using the iterative function if I removed the If child.HasControls() Then check from the iterative function. Hope that makes sense.

In the end I am sticking with the iterative function because looping should be less expensive than recursion, though in real world scenarios the difference probably will not be noticeable.

The following links were extremely helpful to me in working this out:

http://msdn.microsoft.com/en-us/library/ms972976.aspx#viewstate_topic4

https://web.archive.org/web/20210330142645/http://www.4guysfromrolla.com/articles/092904-1.aspx

http://scottonwriting.net/sowblog/archive/2004/10/06/162995.aspx

http://scottonwriting.net/sowblog/archive/2004/10/08/162998.aspx

Extra thanks to Tim for pointing me in the right direction.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜