Erik Porter's Blog

Life and Development at Microsoft and Other Technology Discussions

News

    ASP.NET FindControl Recursive with Generics

    While working on the new version of Channel 9, part of my job this time around was to "templatize" our entire community platform.  The way everything is set up now, aspx files are read in through the Virtual Path Provider (VPP), additional settings in the Page directive are set based off of database settings and the page is rendered.  All our controls are now ascx files with the CodeFileBaseClass set to a class that implements all the code.  This allows us to easily setup new controls and templates for our different sites if new ones are needed.  Right now, Channel 9 and Channel 10 are the only two sites that will be running this code.  In the halfway near future, VisitMIX and a yet to be named Student focused site will run this code as well, so templating was very important.  My other job for this sprint was to add ASP.NET AJAX functionality to the site.  As we get closer to launching the beta you'll see more posts from me sampling some of our code and techniques especially around ASP.NET AJAX.

    The new EntryList control that I created takes a List<Entry>.  The control has a setting to let us set which ascx file we want to use to represent each Entry in the list.  These are loaded using Page.LoadControl.  The same thing goes for the Pager and Filters control in the header and footer of the EntryList.  All of these controls implement different interfaces so we can throw in different ones and look them up generically.  I don't necessarily know the names of these controls and needed a way to look them up by the interfaces they implement.  So I thought, "hey, I'll bet generics could help me out here."  I got the below code written, but unfortunately couldn't figure out how to properly cast the control by the type passed in.  My next door neighbor, our resident Sampy, helped me figure out that I needed to force the type coming in to be a reference (hence the "where T : class" part).  Here's the resulting code...

    public static T FindControl<T>(System.Web.UI.ControlCollection Controls) where T : class
    {
         T found =
    default(T);

        
    if (Controls != null && Controls.Count > 0)
         {
             
    for (int i = 0; i < Controls.Count; i++)
              {
                  
    if (Controls[i] is T)
                   {
                        found = Controls[i]
    as T;
                       
    break;
                   }
                  
    else
                       
    found = FindControl<T>(Controls[i].Controls);
              }
         }

        
    return found;
    }

    Unlike a recursive method that's not generic, you would have to pass in the type as a parameter to the method, which would get passed down through every method call, and you'd have to cast the found control on every call you made to FindControl.  That wouldn't be a huge deal, but there is something nice about simplicity.  :)

    IMyInterface myControl = FindControl<IMyInterface>(this.Controls);

    Note: This only returns the first instance of a control.  You could easily pass in a references List<IMyInterface> and add to it as you find them. 

    This isn't break through code or anything, but it's fun for us, the dev geeks.  Expect to see more posts soon about coding techniques in our platform.

    Comments

    Jonathan Sampson said:

    Dude,

    I so love posts that contain code :) Definitely looking forward to more on the topic of c9v4 development, and the code-journey's you guys explore!

    Best Wishes,

    Jonathan (jsampsonpc)

    # February 25, 2007 1:28 AM

    jayson knight said:

    I too am discovering the power of generic methods...makes for much tighter/cleaner looking code. Looking forward to the coming posts on C9!

    # February 25, 2007 1:29 AM

    Tommy Carlier said:

    I had to do something similar in Windows Forms, and used the following code:

    public static T FindControl<T>(Control rootControl) where T:class

    {

     if (rootControl == null) throw new ArgumentNullException("rootControl");

     T lFoundControl;

     foreach(Control lControl in rootControl.Controls)

     {

       if ((lFoundControl = lControl as T) != null)

         return lFoundControl;

       if ((lFoundControl = FindControl<T>(lControl)) != null)

         return lFoundControl;

     }

     return null;

    }

    And to find all the controls of a certain type:

    public static IEnumerable<T> FindControls<T>(Control rootControl) where T:class

    {

     if (rootControl == null) throw new ArgumentNullException("rootControl");

     T lFoundControl;

     foreach(Control lControl in rootControl.Controls)

     {

       if ((lFoundControl = lControl as T) != null)

         yield return lFoundControl;

       foreach(T lFoundChildControl in FindControls<T>(lControl))

         yield return lFoundChildControl;

     }

    }

    # February 25, 2007 5:55 AM

    Palermo4 said:

    I had to make a revision to your code for it to work in my environment.  Here is the revision:

    for (int i = 0; i < Controls.Count; i++)

    {

        // I had to add this line

        if (found != null) break;

        if (Controls[i] is T)

        {

           found = Controls[i] as T;

           break;

        }

        else

        found = FindControl<T>(Controls[i].Controls);

    }

    # February 26, 2007 6:02 AM

    ialn said:

    i needed the added line also, without it the recurse never stop and u loose the "founded" control

    thanks all :-), it realy helped.

    # May 2, 2007 12:53 PM

    atarikg said:

    What is T up there ? We should change T to a type such as Button or something ? Thanks.. and if i wanna find controls typed radiobutton in my page. how am i supposed to modify the code written above by you. Thanks again :)
    # June 17, 2007 6:08 PM

    Morten Post said:

    If this is going to be used in lots of places, I'd optimize it in the following way.

    public static T FindControl<T>(System.Web.UI.ControlCollection Controls) where T : class

    {

        T found = default(T);

        if (Controls != null && Controls.Count > 0)

        {

             for (int i = 0; i < Controls.Count; i++)

             {

                  found = Controls[i] as T;

                  if (found != null)

                  {

                       break;

                  }

                  else

                       found = FindControl<T>(Controls[i].Controls);

             }

        }

        return found;

    }

    # June 18, 2007 2:52 AM

    dontera said:

    My contribution; modified to return a List<T> of all controls matching T using a ref variable.

           public static void FindControlsByType<T>(ControlCollection Controls, ref List<T> returnList) where T : class

           {

               if (Controls != null && Controls.Count > 0)

                   foreach (Control controlItem in Controls)

                   {

                       if (controlItem is T)

                           returnList.Add(controlItem as T);

                       FindControlsByType<T>(controlItem.Controls, ref returnList);

                   }

           }

    # October 3, 2007 5:47 PM

    David said:

    Hi Eric, nice post!

    Iam curious about how you "templatized" your user controls. Do you have any more post about this?

    "All our controls are now ascx files with the CodeFileBaseClass set to a class that implements all the code" , are there any place where you explains this a little more?

    Iam using a VPP where the files are read throw, but when it comes to the "templatizing" iam lost :)

    Any response on this comment would be appreciated! Thanks!

    # February 12, 2008 2:35 AM

    HumanCompiler said:

    David, I don't really have anything to post.  Basically we just have ascx files and their code is in a base class and you can use that class for many ascx files, so if you want the layout order to be different, then just create a new ascx and point it to the same base class and it will render differently.  We plan to eventually post all our source code on CodePlex, just haven't had enough time yet to clean it up.  Keep an eye on the project here ( http://www.codeplex.com/EvNet ).  That's where we'll eventually post the source for our platform that runs all our sites.

    # February 13, 2008 3:51 AM

    strd said:

    What beautiful piece of code!

    # August 31, 2008 1:07 PM