29

I have used FindControl in the past, prior to .NET 2.0/3.0. It seems like now, for some reason, the ID's of my controls get a funky named assigned. For example I assigned an id "cbSelect" to a checkbox, but FindControl does not find it. When I view the HTML it was assigned ctl00_bodyPlaceHolder_ctl02_cbSelect.

I have not found one example of FindControl that mentions that. In fact everyone seems to just use find control like normal.

So, am I doing something wrong? Did .Net change? Can anyone shed some light onto this for me, it is really frustrating!

Gavin Miller
  • 40,636
  • 19
  • 113
  • 178
LilMoke
  • 2,812
  • 6
  • 44
  • 80

7 Answers7

29

You are probably using a MasterPage or user controls (ascx) and this is the reason the for client ids change. Imagine you have a control in the master page with the same id as one in the page. This would result in clashes. The id changes ensures all ClientID properties are unique on a page.

FindControl needs some special attention when working with MasterPages. Have a look at ASP.NET 2.0 MasterPages and FindControl(). The FindControl works inside a naming container. The MastePage and the page are different naming containers.

Aleris
  • 7,631
  • 3
  • 32
  • 42
  • 11
    The way Microsoft implemented this is such a joke, it should just work. Oh you have a masterpage? How about 500 nested master pages? The method should figure it out and do what's necessary to find the control, period. – The Muffin Man May 19 '13 at 01:51
  • Do you know you're using "should" and "Microsoft" in the same thought? – user1566694 Dec 13 '16 at 20:32
10

You could write extender to find any control on page using recursion. This could be in some Util/Helper class.

 public static Control FindAnyControl(this Page page, string controlId)
    {
        return FindControlRecursive(controlId, page.Form);
    }

    public static Control FindAnyControl(this UserControl control, string controlId)
    {
        return FindControlRecursive(controlId, control);
    }

    public static Control FindControlRecursive(string controlId, Control parent)
    {
        foreach (Control control in parent.Controls)
        {
            Control result = FindControlRecursive(controlId, control);
            if (result != null)
            {
                return result;
            }
        }
        return parent.FindControl(controlId);
    }
nemke
  • 2,310
  • 3
  • 36
  • 56
8

I've had pretty good luck working around this problem in "most" cases with a simple extension method

You can call it on whatever higher-level container control you think best, including the Page itself if you want to scan the entire control hierarchy.

private static Control FindControlIterative(this Control control, string id)
{
    Control ctl = control;

    LinkedList<Control> controls = new LinkedList<Control>();

    while(ctl != null)
    {
        if(ctl.ID == id)
        {
            return ctl;
        }

        foreach(Control child in ctl.Controls)
        {
            if(child.ID == id)
            {
                return child;
            }

            if(child.HasControls())
            {
                controls.AddLast(child);
            }
        }

        ctl = controls.First.Value;
        controls.Remove(ctl);
    }

    return null;
}
Community
  • 1
  • 1
Stephen M. Redd
  • 5,318
  • 1
  • 22
  • 32
7

When searching for a control in a control collection, always use the id you assigned the control, not the one you see in the source post render. If FindControl() does not find the control you know exists, there is a good chance that you are not searching in the right branch of the control hierarchy. A recursive function has been successful for me.

Here is my example of what I use for VB.NET 3.5:

Function FindControlRecursive(ByVal ctrl As Control, ByVal id As String) As Control
    Dim c As Control = Nothing

    If ctrl.ID = id Then
        c = ctrl
    Else
        For Each childCtrl In ctrl.Controls
            Dim resCtrl As Control = FindControlRecursive(childCtrl, id)
            If resCtrl IsNot Nothing Then c = resCtrl
        Next
    End If

    Return c
End Function

Here is an example of how I would topically implement this function in my base page class:

Dim form HtmlForm = CType(FindControlRecursive(Me, "Form"), HtmlForm)
  • 1
    +1 because without seeing yours I had already implemented it in C# with the same approach: `public static System.Web.UI.Control FindControlRecursive(Control ctl, string id){` if (ctl == null) return null; if (ctl.ID == id) return ctl; if (ctl.Controls.Count > 0) foreach(Control sc in ctl.Controls) { Control fnd = FindControlRecursive(sc, id); if (fnd != null) return fnd; } return null; }` – gkakas Nov 17 '13 at 10:04
3

This is the VB.NET code that worked for me:

<Extension()> _
Function FindChildControlById(ByVal controlToStartWith As Control, ByVal controlIdToFind As String) As Control
    If controlToStartWith Is Nothing Then Return Nothing
    If controlToStartWith.ID = controlIdToFind Then Return controlToStartWith
    For Each childControl As Control In controlToStartWith.Controls
        Dim resCtrl As Control = FindChildControlById(childControl, controlIdToFind)
        If resCtrl IsNot Nothing Then Return resCtrl
    Next childControl
    Return Nothing
End Function ' Function FindChildControlById(ByVal controlToStartWith As Control, ByVal controlIdToFind As String) As Control

Credit goes to George for the initial VB.NET code. I only modified it a teeny bit, with 2 functional change: mine doesn't error if/when null/Nothing is passed as the input control, and mine is implemented as an Extension. My other 3 minor changes don't affect the functionality, but to me, they were code simplifications. But I know it's very subjective.

So this method can be used with:

Dim c1 As Control = Page.FindChildControlById("aspControlID")

And if you want to convert it into a specific child class of a Control, like this:

Dim c1 As Control = Page.FindChildControlById("aspControlID")
Dim c As HyperLink = TryCast(c1, HyperLink)

Update: My function is now named 'FindChildControlById' (previously was 'FindMiControl'). I liked SpeedNet's suggestion better.

Shawn Kovac
  • 1,317
  • 13
  • 17
1

When it's rendering the html, ASP.NET will prefix all the control IDs with the IDs of the naming containers (User Controls etc..) in a hierarchy going back all the way to the document root. This ensures that all the IDs are unique for post backs etc..

This does not effect using FindControl where you should use the ID in the original markup.

Nick
  • 5,498
  • 9
  • 50
  • 72
0

Here is a reference as to how web form controls are named...

Web Forms Control Identification

George
  • 7,572
  • 5
  • 28
  • 24