Traversing the ASP.NET Control Tree with LINQ

by Jan Blomquist 4. February 2009 05:00

Many users of ASP.NET has over the years created various helper classes and utilities to simplify traversing the ASP.NET Control structure. Samples include the Generic FindControl<T> function which I've seen countless implementations of.


Well, we all know that re-inventing the wheel is unwise if you want to leverage high productivity. Fortunately with the arrival of C# 3.0 we've got this little thing called LINQ which is basically just a collection of related inventions like Extension Methods, Lambdas, Anonymous Types, Object Initializers, etc. It turns out that LINQ enables us to apply some powerful querying on top of ASP.NET and in many cases with simple one liners. Using Linq directly makes code alot easier to read. Here's an example of how using Linq to validate if a Question is answered correctly just to show the power of the syntax. 

 

   1:  public override bool AnsweredCorrectly
   2:  {
   3:      get
   4:      {
   5:          if (!Answered) return false;
   6:          return (from a in Answers where .CoordsToRectangle()
   7:                  .Contains(new Rectangle(UserAnswer.X, UserAnswer.Y, 3,3))
   8:              select a).Count() > 0;
   9:      }
  10:  }
 

In this blog I am going to walk through a few ways to use LINQ in the context of ASP.NET. Let's begin my adding ListItems to a ListControl by transforming an existing entity collection to ListItems. In this example I am using method chains instead of the LINQ syntax directly. For some queries I prefer the LINQ syntactic variant, other times method chaining makes more sense. We use the Select method to get items and return new ListItems where we use both the constructor and object initialization before we transform the result into an Array because that's what the  AddRange() function wants.

   1:  lstControl.Items.AddRange(question.Answers
   2:         .Select(a => new ListItem(FormatFriendlyText(a), a.Id.ToString())
   3:         { Selected = IsEditable ? a.Correct : a.Selected }).ToArray());

 

Here's a slight change where also use the OrderBy function to automatically order the Items in the collection before they are added to the ListControl.

   1:  lstControl.Items.AddRange(question.Answers
   2:            .OrderBy(a => IsEditable ? a.Order : a.UserOrder)
   3:            .Select(a => new ASP.ListItem(a.Text, a.Id.ToString())).ToArray());

Getting the data in there is one thing, but what if you want to get the data out again? By using the OfType you can get a strongly typed collection of the Items in the Items collection and thereby use LINQ to perform the select again. In the example below we create new objects based on an anonymous type since we're just going to throw the objects away after usage anyway. Also notice that here we're using the pure LINQ syntax.

   1:  from li in questionControl.Items.OfType<ListItem>()                                                
   2:  where li.Selected
   3:  select new { Answer = GetAnswer(li), Item = li };

 

Or even better, let's also use an indexer and then execute Except() to filter out some items.

   1:  lstControl.Items.OfType<ASP.ListItem>()
   2:            .Select((li, indexed) => li.Selected ? indexed : -1)
   3:            .Except(new[] { -1 }).ToArray();

Another thing we did recently was that we wanted to do continious calculation based on an arbitrary amount of TextControls. The thing we did here was to use Gaia Ajax to automatically monitor the TextChanged event. Here we automatically select all TextControls where the .Text property is not empty and contains a Number. Then we apply the Sum() function on this result.

   1:  void textbox_TextChanged(object sender, EventArgs e) 
   2:  {
   3:       FoodCalorieCountTextControl.Text = 
   4:       TextControls.Where(t => !string.IsNullOrEmpty(t.Text) && t.Text.IsNumber())
   5:                   .Sum(t => int.Parse(t.Text)).ToString();
   6:  }

 

Ok, so how can we use this knowledge to replace the old FindControl. Let's create our own Extension Method called All() which simply returns all controls in a Controls Collection recursively. The code looks like this.

   1:  namespace Gaia.Ajax.Web.UI
   2:  {
   3:      using System.Collections.Generic;
   4:      using System.Web.UI;
   5:   
   6:      public static class ControlExtensions
   7:      {
   8:          public static IEnumerable<Control> All(this ControlCollection controls)
   9:          {
  10:              foreach (Control control in controls)
  11:              {
  12:                  foreach (var grandchild in control.Controls.All())
  13:                      yield return grandchild;
  14:   
  15:                  yield return control;
  16:              }
  17:          }
  18:      }
  19:  }

 

By importing the namespace you get access to the static function All() which will be available on the Controls collection. This newly created extension method allows us to write Code like this

   1:  var selectedPanels = root.Controls.All().OfType<Panel>
.Where(c=> c.CssClass == "selected"); 

 

This simple code will return all Panels in the root control recursively and only select the Panels which has a CssClass of "selected". Using these selectors you can easily create flow in your application logic. Let's say you wanted to un-select all these panels by setting the cssClass to unselected. That would be as easy as follows.

   1:  selectedPanels.ToList().ForEach(p=> p.CssClass = "unselected");

Imagine how easy it is to manipulate a TreeView for example using these simple techniques? Below is an Image of the Gaia Ajax TreeView

 

When building Ajax applications with Gaia Ajax you have full control on the server to do this stuff easily and the response back to the client will be compact and optimized. If you experienced a full postback or partial re-rendering of some huge portion of the page, this technique wouldn't be so effective. To avoid creating a book out of this I am going to stop here and leave the rest to your imagination. Hopefully you've learned that using LINQ can be quite effective for your application development. If you're still not using Linq or maintaining some library for this, you know what to do.

Happy Linq-In

Comments

2/4/2009 3:36:32 PM #

Trackback from DotNetKicks.com

Traversing the ASP.NET Control Tree with LINQ

DotNetKicks.com

2/4/2009 7:27:24 PM #

Trackback from Web Development Community

Official Gaiaware Blog | Traversing the ASP.NET Control Tree with LINQ

Web Development Community

2/6/2009 8:48:43 PM #

Trackback from DotNetShoutout

Traversing the ASP.NET Control Tree with LINQ

DotNetShoutout

Comments are closed