I have a class called LayoutManager. The purpose of this class is to hold references to objects which are currently represented on my ASP.NET page. I originally created this class because I was experiencing a large amount of frustration with Page.FindControl(). My frustrations were two-fold:
- Page.FindControl() in its native implementation only searches through direct children of Page. A recursive implementation is necessary in order to find any given control on Page. I was opposed to this for performance reasons.
- In order to call Page.FindControl all of my classes in question need to know of Page. This seemed like a large amount of coupling and I was trying to mitigate that with a middle-man class.
As such, I created the class shown below. I am just now seeing how poor my implementation is. I’ve removed methods which do the same work but on different objects to simplify this example:
/// <summary>
/// This class manages controls which are present on the page.
/// Whenever a control is created it notifies this manager that it has been
/// created (inside of its constructor). At that point, you can use this
/// manager to find controls on the dashboard.
/// The only other option is to use Page.FindControl, but its pretty broken and slow.
/// </summary>
public class LayoutManager
{
private static readonly ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly LayoutManager _instance = new LayoutManager();
private LayoutManager() { }
public static LayoutManager Instance
{
get { return _instance; }
}
//HashSets for efficiency. As long as order doesn't matter, use these.
private HashSet<CormantRadDock> _registeredDocks;
public RadMultiPage MultiPage { get; set; }
public CormantTimer Timer { get; set; }
public DashboardUpdatePanel MultiPageUpdatePanel { get; set; }
public CormantRadTabStrip TabStrip { get; set; }
public RadListBox TabsListBox { get; set; }
public UpdatePanel GlobalSettingsUpdatePanel { get; set; }
public HashSet<CormantRadDock> RegisteredDocks
{
get
{
if (Equals(_registeredDocks, null))
_registeredDocks = new HashSet<CormantRadDock>();
return _registeredDocks;
}
}
public CormantRadDock GetDockByID(string dockID)
{
CormantRadDock dock = RegisteredDocks.FirstOrDefault(registeredZone => dockID.Contains(registeredZone.ID));
if (Equals(dock, null))
_logger.ErrorFormat("Did not find dock: {0}", dockID);
else
_logger.DebugFormat("Found dock: {0}", dockID);
return dock;
}
}
So, a couple of things:
- I messed up big time. This class can’t be static. Multiple users are able to place expectations on LayoutManager to return their RegisteredDocks — but this is causing data collisons.
- I’m exposing my RegisteredDocks collection to the outside world instead of providing methods to interact with a privately-owned collection. As such, I call RegisteredDocks.Add(dock) throughout code… I am currently in the process of adding methods such as LayoutManager.AddDock(dock) which will allow me to keep the collections private and only expose methods to interact with the collections.
- I started throwing into the class any other ‘1-of’ objects which I needed references to.
This all stems from the fact that I did not want to use Page.FindControl…. so I am feeling pretty silly now.
My questions:
- Should I be afraid of using a recursive implementation of Page.FindControl? This link highlights my concern — the response given is the route I chose to follow.
- What’s a solid solution if I should not be using Page.FindControl? The only simple solution which comes to my mind is storing my collections in Session and having LayoutManager continue to be static. In this implementation it would go out to Session (which changes based on user) and return the correct collection… but I am wary of saving too much data in Session as well as the cost of constantly writing back to Session.
EDIT:
Final thoughts:
public static Dashboard GetInstance()
{
var dashboard = HttpContext.Current.Handler as Dashboard;
//TODO: Handle case where dashboard is null.
return dashboard;
}
public Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
return root;
foreach (Control control in root.Controls)
{
Control foundControl = FindControlRecursive(control, id);
if (foundControl != null)
return foundControl;
}
return null;
}
//Usage:
Dashboard dashboard = Dashboard.GetInstance();
CormantRadDock dock = (CormantRadDock)dashboard.FindControlRecursive(dashboard, dockID);
I don’t think you should be afraid of using a recursive
FindControl()method at all. Unless the page goes countless levels deep (which it shouldn’t), a recursive function should be able to traverse your control hierarchy without breaking a sweat.In short, build a recursive
FindControl()method. You have nothing to worry about; just don’t make it static.EDIT
To get a handle on the current page from an outside class, you should be able to do this: