Думаю каждый, кто разрабатывал и разрабатывает проекты с использованием ASP.NET Web Forms сталкивался с подобной задачей. А дело в том, что стандартный метод, который позволяет получить доступ к элементам непосредственно не доступным напрямую, FindControl() не даёт желаемых результатов, при нескольких уровнях вложенности.
Метод не выполняет поиск в иерархии элементов управления, поэтому для поиска чего-либо при использовании нескольких уровней вложенности, он не годится. На самом деле многие начинающие, да и не только, очень часто спотыкаются, когда встречаются с подобным. А решение данной задачи - просто. Вот первый вариант рекурсивного метода поиска элемента с использованием его идентификатора:
public static T FindControlRecursive<T>(Control sourceControl, string targetControlId)
where T : class
{
if (sourceControl == null)
throw new ArgumentNullException("sourceControl");
if (targetControlId == null)
throw new ArgumentNullException("targetControlId");
if (sourceControl.ID == targetControlId) return sourceControl as T;
foreach (Control control in sourceControl.Controls)
{
T controlToReturn = FindControlRecursive<T>(control, targetControlId);
if (controlToReturn != null) return controlToReturn as T;
}
return null;
}
Данный метод найдёт элемент на любом уровне вложенности. Только нужно учитывать то, что на разных уровнях иерархии может быть несколько элементов, с одинаковым ID, поэтому может потребоваться внести ещё и дополнительные критерии помимо идентификарора. Такое часто наблюдается в сложных элементах управления данными, такими как GridView, Repeater и т.п. Ещё один тонкий момент связанный с рекурсией, когда спрашивают, а не может ли подобный метод переполнить стек? Может. Как известно размер стека в .Net ограничен по умолчанию в 1 МБ. В ASP.NET и того меньше, 256 КБ, хотя это значение можно переопределить. Но даже 256 КБ это большое значение, чтобы его переполнить надо сильно постараться. Нужен очень широкий и глубокий уровень вложенности элементов DOM, что кроме как преднамеренно сделать нельзя. Так что, опасность, если можно назвать так, в реальных приложениях не актуальна, если всё правильно реализовано. Вот ещё пара рекурсивных методов для получения всех элементов в иерархии:
public static IEnumerable<Control> SelectAllControls1(this Control sourceControl)
{
if (sourceControl == null)
throw new ArgumentNullException("sourceControl");
foreach (Control control in sourceControl.Controls)
{
yield return control;
foreach (Control descendant in control.SelectAllControls1())
{
yield return descendant;
}
}
}
public static IEnumerable<Control> SelectAllControls2(this Control sourceControl)
{
if (sourceControl == null)
throw new ArgumentNullException("sourceControl");
IEnumerable<Control> controls = sourceControl.Controls.OfType<Control>();
return controls.SelectMany(c => SelectAllControls2(c)).Concat(controls);
}
И самый последний метод, который собирает элементы уже без рекурсии:
public static IEnumerable<Control> SelectAllControlsIterative(this Control sourceControl)
{
if (sourceControl == null)
throw new ArgumentNullException("sourceControl");
List<Control> result = new List<Control>();
Stack<Control> controls = new Stack<Control>();
controls.Push(sourceControl);
while (controls.Count > 0)
{
Control current = controls.Pop();
ControlCollection childs = current.Controls;
result.AddRange(childs.OfType<Control>());
foreach (Control item in childs)
{
controls.Push(item);
}
}
return result;
}
Он использует алгоритм поиска в глубину и никакое переполнение ему уже не грозит. Можно было бы использовать поиск в ширину и ещё другие варианты напридумать. Хотя это уже излишне. Думаю этих методов вполне достаточно, для применения их в своих приложениях.