So, this post should clear up (and serve as a reminder for me) how and why these attributes are important. Let’s explore what these two attributes are used for. Let’s start with ParseChildren.
The [ParseChildrenAttribute]
The ParseChildren Attribute is probably, the most important attribute you should pay attention to when developing web controls. It’s actually used by the ASP.NET Parser and ControlBuilder object to figure out how to parse the ASP.NET code you write. Visual Studio also uses this attribute to figure out what valid sub-controls and components are allowed within the contents of a server control.
Let’s say, I want to create an AggregateFeeds control that displays an aggregate list of RSS feeds.
A Basic and Boring Control Syntax
You’ll notice that the RssResource is the only available option that is allowed as a child from the AggregateFeeds control. Here’s the code behind theAggregateFeeds control:
113 [
114 ParseChildren(
115 typeof(RssResource),
116 DefaultProperty = "Feeds",
117 ChildrenAsProperties = true
118 )
119 ]
120 public class AggregateFeeds : Control
121 {
122 public AggregateFeeds()
123 {
124 this.Feeds = new RssFeedCollection();
125 }
126 public RssFeedCollection Feeds
127 {
128 get;
129 private set;
130 }
131 protected override void Render(HtmlTextWriterwriter)
132 {
133 this.Feeds
134 .ForEach( rssRes => writer.Write( rssRes.Url ) );
135 }
136 }
137
138 public class RssFeedCollection : List<RssResource>
139 {
140
141 }
142
143 public class RssResource
144 {
145 public string Url { get; set; }
146 }
The ParseChildren attribute on AggregateFeeds tells the ASP.NET, that any children within the AggregateFeeds control should betypeof(RssResource). ChildrenAsProperties=true let’s ASP.NET know that it should STOP parsing server controls with "runat=server", and switch to instantiating objects into the properties of the ArggregateFeeds control. DefaultProperty says, that the results of the parsed objects should go into the default property Feeds.
Syntax Goodness With InnerProperty
The previous example was great, it’s simple and get’s the job done. But let’s say, the requirements have changed, our control is growing, and we need to allow more customization, and extensibility for the consumers of ourAggregateFeeds control.
Let’s clean up the markup and allow our developers to create markup like this:
To get this type of syntactical behavior, check out the code below:
113 [
114 ParseChildren(
115 ChildrenAsProperties = true
116 )
117 ]
118 public class AggregateFeeds : Control
119 {
120 public AggregateFeeds()
121 {
122 this.Feeds = new RssFeedCollection();
123 }
124
125 [PersistenceMode(PersistenceMode.InnerProperty)]
126 public RssFeedCollection Feeds
127 {
128 get;
129 private set;
130 }
131 [PersistenceMode(PersistenceMode.InnerProperty)]
132 public AggregateSettings Settings
133 {
134 get;
135 private set;
136 }
137 protected override void Render(HtmlTextWriterwriter)
138 {
139 this.Feeds
140 .ForEach( rssRes => writer.Write( rssRes.Url ) );
141 }
142
143 }
144
145 public class AggregateSettings
146 {
147 public int TimeOut { get; set; }
148 public bool CacheResults { get; set; }
149 }
150
151 public class RssFeedCollection : List<RssResource>
152 {
153
154 }
155
156 public class RssResource
157 {
158 public string Url { get; set; }
159 }
Notice, we’ve removed DefaultProperty and typeof(RssResource) fromParseChildren attribute. We’re no longer working with a simple control that has simple children objects that need to be parsed, we’re now working with a complex control with more than one property that we’re setting in the markup, so we’ve removed the "default" stuff. The syntactical magic happens withPersitanceMode attribute on the properties. PersistanceMode.InnerProperty allows us to specify our cool <Feeds>and <Settings> tags. How does Visual Studio know what members are available? It does so by Reflection.
Get fancy, more than one child type
Also, I want to point out, suppose, we want to support multiple types of Feed objects. We could use an enum in RssResource, or we could use inheritance to achieve the following:
All we would have to do is simply mark RssResource as an abstract class. Then, subclass for each type.
156 public abstract class RssResource
157 {
158 public string Url { get; set; }
159 }
160 public class MediaRss : RssResource
161 {
162
163 }
164 public class ITunesRss : RssResource
165 {
166
167 }
Again, I’m just showing that it’s possible, but following my mantra of "less code, less maintenance," I’d use an enum to describe the type of rss feed onRssResource.
Where is [PersistChildren]?
Nowhere! Is PersistChildren attribute needed? No, it’s not a required attribute to create your custom control. The PersistChildrenAttribute only provides designer support for your control with Visual Studio and has no "processing" affect in ASP.NET, but remember ParseChildren does.
I’m a
source-view only guy. I really don’t remember the last time I’ve used the Visual Studio "Design View", it’s a waste, crashes all the time, so I’ve pretty much given up on it. Besides, "Design View" is for n00bs anyway. Just kidding! If you plan on using the Design View, then you’ll probably need your
PersistChildren attribute…
In general, PersistChildren and ParseChildren are exclusive complementary attributes to describe the same semantic operation. The rule of thumb goes:
If ParseChildren(true), then PersistChildren(false).
If ParseChildren(false), then PersistChildren(true).
Following the PersistChildren guideline above should keep your code out of trouble. But again, I wouldn’t use PersistChildren only until you actually need it. Less code, less maintenance.
Here’s a nice list of attributes you should consider when writing your custom controls:
Hope that helps! Happy coding!
Brian Chavez
My Testing Codes:
<ParseChildren(True)> _
Partial Class MyControl_MyFrame_01
Inherits System.Web.UI.UserControl
Private _ContentTemplate As ITemplate = Nothing
<PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(GetType(TemplateControl))> _
Public Property ContentTemplate() As ITemplate
Get
Return _ContentTemplate
End Get
Set(ByVal value As ITemplate)
_ContentTemplate = value
End Set
End Property
Public Property Width() As String
Get
Return tb.Width
End Get
Set(ByVal value As String)
tb.Width = value
End Set
End Property
Public Property Title() As String
Get
Return TheTitle.Text
End Get
Set(ByVal value As String)
TheTitle.Text = value
End Set
End Property
Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
MyBase.OnInit(e)
If Not IsNothing(_ContentTemplate) Then _ContentTemplate.InstantiateIn(ContentPH)
End Sub
End Class
如此在web引用時, 可用以下的作法
<MyUC:MyFrame_01 ID="test" runat="server" Width="100%" Title="123123" >
<ContentTemplate>
<asp:label ID="DD" runat="server" Text="this is a label control"></asp:label>
</ContentTemplate>
</MyUC:MyFrame_01>
ContentTemplate裏的控制項會被包進uc裏的PlaceHolder控制項
<%@ Control Language="VB" AutoEventWireup="false" CodeFile="MyFrame_01.ascx.vb" Inherits="MyControl_MyFrame_01" %>
<table id="tb" runat="server" width="" border="0" cellpadding="0" cellspacing="0">
<tr>
<td height="30" background="../images/frame_images/bkg.jpg" align="center">
<asp:Label ID="TheTitle" runat="server" Text="" CssClass="FrameTitleStyle"></asp:Label>
</td>
</tr>
<tr>
<td class="Border_01">
<asp:PlaceHolder ID="ContentPH" runat="server" EnableViewState="true"></asp:PlaceHolder>
</td>
</tr>
</table>