Building A Better FindControl

April 3, 2008 • Damien White

I’ve come across a lot of times when I wish I could just use the standard FindControl method on a Page or on a container control and have it be found without having to make sure that the control was a direct child of my current container. Also, I wish I could find all controls of a particular type on a page. Why couldn’t the Control class be given these gems out of the box? Well, thanks to extension methods, custom iterators and a dash of linq, I’ve built my own Control Finding mechanism that gives a broader search and adds some type safety to the mix.

FindControl 101

Let’s look at our basic implementation. Finding controls is a recursive process by nature, but any recursive algorithm can be turned into an iterative (and thus more performant) one, so for our first pass, let’s just implement the FindControl method as it is now but with a deeper searching algorithm:

public static ControlAdvancedFindControl(Control container, string controlId)
{
  Queue<Control> controlQueue = new Queue<Control>(new[] { container });
  Control currentControl;

  while(controlQueue.Count > 0)
  {
    currentControl = controlQueue.Dequeue();

    foreach (Control child in currentControl.Controls) { controlQueue.Enqueue(child); }

    if(currentControl.ID == controlId)
       return currentControl;
  }

  return null;
}

In the most basic sense, that’s all we have to do. We can now add this to a helpers class and call it statically. But there’s so much more that can be done…

Pour Some Syntactic Sugar On Me

Wouldn’t it be nice if I could call my new method from ANY control as if Microsoft had actually thought I might want this much better working version of its FindControl method when they were building the Control class? well, extension methods allow me to fake it. This will require that I make a static container class for my new method and change the method signature a bit.. let’s take a look at our improved version:

public static class Extensions
{
  public static Control AdvancedFindControl(this Control container, string controlId)
  {
    Queue<Control> controlQueue = new Queue<Control>(new[] { container });
    Control currentControl;

    while (controlQueue.Count > 0) {
      currentControl = controlQueue.Dequeue();
      foreach (Control child in currentControl.Controls) { controlQueue.Enqueue(child); }

      if (currentControl.ID == controlId)
        return currentControl;
    }

    return null;
  }
}

Not much of a difference in code, but a huge difference in how I can use it. Now, instead of having to invoke our find this way:

Control found = UtilityFunctions.AdvancedFindControl(Page, TextBox1);

We can now invoke it like this:

Control found = Page.AdvancedFindControl(TextBox1);

The key is the word this in the signature of our method. It tells the compiler that any object that inherits from Control will be able to invoke this method as if it was a part of the class. Of course it isn’t really adding the new method to the class, but it makes it MUCH easier to get to our methods. I’m still not satisfied, because I’m looking for a textbox and I’m getting back a control that might be a textbox (or might not). I’d like not to have to cast the control when it comes back.

Generically Speaking…

We can now change our AdvancedFindControl to return any control we want by using generics… the new version is below:

public static class Extensions
{
  public static TControl AdvancedFindControl(this Control container, string controlId) where TControl : Control
  {
    Queue<Control> controlQueue = new Queue<Control>(new[] { container });
    Control currentControl;

    while (controlQueue.Count > 0) {
      currentControl = controlQueue.Dequeue();
      foreach (Control child in currentControl.Controls) { controlQueue.Enqueue(child); }

      if (currentControl is TControl && currentControl.ID == controlId)
        return (TControl)currentControl;
    }

    return null;
  }
}

Now, let’s say I want that textbox named TextBox1;. I can call it this way:

var result = Page.AdvanceFindControl<TextBox>("TextBox1");
Response.Write(result.Text);

Have It Your Way

Ok, now for the grand finale. It’s nice to be able to find a typed control by ID, but sometimes you want to find a control by more than one criteria. And, for that matter, what if I want to find all the controls that match a particular set of criteria. We can easily modify our function to do this with custom iterators and a bit of linq to make it fancy. Our final set of functions is below:

public static class Extensions
{
  public static TControl AdvancedFindControl(this Control container, Predicate criteria) where TControl : Control
  {
    return DoTheWork(container, criteria).FirstOrDefault();
  }

  public static IEnumerable AdvancedFindControls(this Control container, Predicate criteria) where TControl : Control
  {
    return DoTheWork(container, criteria);
  }

  private static IEnumerable DoTheWork(Control container, Predicate criteria) where TControl : Control
  {
    Queue<Control> controlQueue = new Queue<Control>(new[] { container });
    Control currentControl;

    while (controlQueue.Count > 0) {
      currentControl = controlQueue.Dequeue();
      foreach(Control child in currentControl.Controls) { controlQueue.Enqueue(child); }

      if(currentControl is TControl && criteria((TControl)currentControl))                     yield return (TControl)currentControl;
    }
  }
}

This final set of functions moves all the heavy lifting into a private method called DoTheWork, which now returns an IEnumerable of our searched control type. This is enabled by replacing our old return with yield return, a .NET 2.0 feature. Our original static extension method calls this private method and I renamed it AdvancedFindControls so that it’s clear it returns more than one. I also created another extension method called AdvancedFindControl which also makes use of the private method and then uses some linq filtering (in this case the FirstOrDefault() extension method) to give us only the first. You could easily write additional methods to give you the last, middle or even every other result of your search with similar functions available on the IEnumerable type. So, using these new functions, how can I search for all of the textboxes on my page? Simple!

// get all textboxes on my Page
var allTextBoxes = Page.AdvancedFindControls<TextBox>(control => true);

// get all my textboxes for which the user entered some text and the mode is MultiLine
var filledTextAreas = Page.AdvancedFindControls<TextBox>(control =>
  !String.IsNullOrEmpty(control.Text) && control.TextMode ==  TextBoxMode.MultiLine
);

So as you can plainly see the features of extension methods, custom iterators and linq can be combined to give us a significantly better way to search through controls.

Posted in .net, asp.net, c#, linq and tagged with ASP.NET, C#

Damien White

I am a software architect with over 16 years of experience. I simply love coding! I have a driving passion for computers and software development, and a thirst for knowledge that just cannot be quenched. I'm happy to share what I know in my quest to learn as much as possible. I focus most of my time on web development using Ruby on Rails, Ember.js, and ASP.NET MVC.

comments powered by Disqus