Templates are a powerful mechanism that enable page developer's to customize controls and define repeatable content (eg. in data-bound controls) and have been around since ASP.NET v1. However, the templating and closely related INamingContainer infrastructure can get in the way of accessing controls defined within. Top-level controls can be accessed directly via associated member fields. However controls inside templates do not get corresponding fields. Instead, you have to use FindControl passing in the ID as a string argument and then cast the result to the right type to access controls placed within a template, which is sub-optimal. In Whidbey, we've made some incremental improvements in this area. One of the features we've added is the notion of single-instance templates.
Why FindControl?
At runtime, the page is basically a control tree, and each control needs to be identifiable uniquely for various reasons. Unfortunately the ID is not good enough, because templates may be repeated (as ItemTemplate is in DataList). This is the same reason that ID'd controls within templates do not have associated fields. The solution we chose to implement was to add the ability to define name scopes. A name scope is defined by a control that implements INamingContainer (as each item does in DataList). With this in place, controls must have a unique ID within a name scope rather than across the entire page. The actual unique ID is computed by concatenating the IDs of controls along the name scope hierarchy. One other thing this scheme for unique IDs provided was a mechanism to enable on-demand control creation in composite controls. Composite controls don't really know when to build their child controls. They need to be told to do so. So when FindControl is invoked (either by the page developer's code, or by the page when it tries to match controls to incoming post-back data), it gives a chance to each naming container to build its control hierarchy as needed. Hence the two reasons for FindControl: to ensure controls are created so they can be found, and the ability to define a name scope to search within (both of these requirements cannot be solved by direct references.
In reality some controls do know when to build their child controls. An example of this is the new LoginView control that uses the ambient user context to automatically switch views. So it would be nice if such controls could expose their template contents as top-level controls, and simulate direct access. Secondly, a lot of templated controls do not repeat their contents. In these cases, templates are just a customization mechanism, and are not meant to define a snippet of repeatable content. Again LoginView is a good example. The contents of the template are in fact unique across the page, and could be promoted top-level, and be accessed directly.
The Solution
This is a general pattern that would be useful elsewhere. We added the notion of "single instance templates" as a general solution. Control developers can define templates to be single instance using metadata which causes the ID'd controls within the template contents to be promoted to the page level, i.e. have associated fields, and therefore accessible directly without FindControl. Control developers simply need to mark their template properties appropriately. The parser and code-generator together work behind the scenes to add member fields, and initialize them at the right time. For example, LoginView defines its AnonymousTemplate as follows:
[TemplateInstance(TemplateInstance.Single)]
public ITemplate AnonymousTemplate {
get { ... }
set { ... }
}
So if you're a control developer, check to see if any of your control's template properties qualify for this metadata, if you are looking to simplify some usage patterns associated with your control. One thing to keep in mind: the fields associated with the contained control references remain null, until the template is in fact instantiated. Consequently, templated controls using this new mechanism ought to define an event that page developers can handle, and access the controls in that event handler. On the other hand, page developers ought to keep in mind that Page_Load may be too early to access these controls, and they instead should handle this event to be guaranteed that the template has been instantiated, and that it is safe to access the contained controls.
I believe access to controls can be simplified in multi-instance templates as well. I've been discussing some ideas in the context of v-next ideas around data-bound controls. So hopefully, they will completely eliminate the pain of FindControl and INamingContainer in terms of accessing controls with templates.
Posted on Monday, 10/17/2005 @ 6:01 PM
| #
ASP.NET