PlaceHolders and UserControls (i.e. Template-Lite UserControls)
Most of you will want to skip this technical addition to my otherwise prolific and interesting personal chronicles. (Ok ok I admit I’ve been way too consumed with professional goals over the last two years to blog. I’m motivated, almost desperately so, to strike while the iron’s hot and thus have had to reallocate my time. Short-terms goals are roughly: finish my programming reading list, crank out a couple learning programming projects, revolutionize digital information, then get back to the business of documenting the incoherent musings of my scrambled brains.)
Anyway, back to my technical article. I’ve been experimenting lately with increasingly complex browser look-and-feel, and encapsulating that into generic UserControls (I know. I’m still using WebForms. Ugh.) I’ve always believed my only recourse was to develop a custom control for this. I prefer to derive from WebControl, and went down the path of building full-on, by-the-books templated datasource controls (exposing ITemplates qualified with PersistenceMode.InnerProperty, and insantiating them in CreateChildControls, and rejiggering DataBind to fire <template>Created and <template>DataBound events so the consumer can FindControls in his content).
Worked fine, but for the consuming code to have to rely on Created and DataBound events to get visibility into his content was totally cumbersome and counterproductive to the original goal of creating something like a Panel that didn’t parse child controls (i.e. was qualified with ParseChildren(false)). Furthermore, I wasn’t doing anything data-related in the controls themselves, since they’re generic. In fact, my TemplateContainers were glorified objects.
So I realized the data templating is beyond what I need, and I refactored back to exposing mere setter PlaceHolder properties, with PersistenceMode.InnerProperty. The setter would merely add the value (which was also a PlaceHolder) to the Controls collection, and keep a handle around for optimized rendering in an overridden RenderContents. This was much nicer because it could be used like a plain old Panel. The drawback, however, was that that complex look and feel that the control encapsulated was still baked into a custom control, without the declarative manageability (and dynamic recompilation) of UserControls. As I pondered this the other night, I wondered if an actual UserControl could expose a setter PlaceHolder property qualified with PersistenceMode.InnerProperty. Such a setter would add the value not to the Controls collection, but to a PlaceHolder persisted in the declarative portion of the control.
I fired up VisualStudio this morning and put finger to keyboard. And the technique worked!
Example UserControl:
1 <%@ Control Language="C#" AutoEventWireup="true" ClassName="WebUserControl" %>
2 <script runat="server">
3 [PersistenceMode(PersistenceMode.InnerProperty)]
4 public PlaceHolder Bah
5 {
6 set { bah.Controls.Clear(); bah.Controls.Add(value); }
7 }
8 </script>
9 <asp:Label runat="server" ID="before">fancy complicated preceeding content</asp:Label>
10 <asp:PlaceHolder runat="server" ID="bah"></asp:PlaceHolder>
11 fancy complicated succeeding content
Example consumer:
1 <%@ Page Language="C#" AutoEventWireup="true" ClassName="Default" %>
2 <%@ Register Src="~/WebUserControl.ascx" TagName="WebUserControl" TagPrefix="test" %>
3 <script type="text/C#" runat="SErver">
4 protected void Page_Load (object sender, EventArgs e)
5 {
6 dynamic.Text = DateTime.Now.Ticks.ToString();
7 }
8 </script><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
9
10 <html xmlns="http://www.w3.org/1999/xhtml">
11 <head runat="server">
12 <title>Persist as InnerProperty on UserControl test</title>
13 </head>
14 <body>
15 <form id="form1" runat="server">
16 <test:WebUserControl runat="Server">
17 <bah>test <asp:Label runat="server" ID="dynamic"></asp:Label></bah>
18 </test:WebUserControl>
19 </form>
20 </body>
21 </html>
Example output:
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head><title>
5 Persist as InnerProperty on UserControl test
6 </title></head>
7 <body>
8 <form name="form1" method="post" action="default.aspx" id="form1">
9 <div>
10 <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE4NzM3NDU2NjcPZBYCAgMPZBYCAgEPZBYCAgIPZBYCZg9kFgICAQ8PFgIeBFRleHQFEjYzMzczMTUyMTk3MDA3NTk0MmRkZIkdLkHRWXYFy657lSmO9z5WqoSr" />
11 </div>
12
13 <span id="ctl02_before">before</span>
14 test <span id="ctl02_dynamic">633731521970075942</span>
15 after
16 </form>
17 </body>
18 </html>
Perfect ID namespacing, content visibility (no need to FindControls) from the consumer, *and* the manageability of UserControls. This is a solution to a problem I’ve faced since I started with WebForms, and I am absolutely stupified that I have not come across this anywhere before. (Maybe in a few days I’ll be back to describe some fatal flaw I found with it.) But so far, it seems like the holy grail of complex UserControls.




