Web Control Collection Property Example

This example shows how to create a control named QuickContacts that implements persistence in a page for a collection property. The example control is a control that allows a page developer to store a list of address book contacts. The QuickContacts control exposes a Contacts collection property that contains Contact objects. The Contact class has Name, Email, and Phone properties.

The Contact items of the Contacts collection property are persisted within the control's tags as shown in the following example:

<aspSample:QuickContacts ID="QuickContacts1" Runat="server">
  <aspSample:Contact Name="someone" Email="someone@example.com"     Phone="(555) 555-5555"/><aspSample:Contact Name="jae" Email="jae@fourthcoffee.com"     Phone="(555) 555-5555"/>
</aspSample:QuickContacts>

For clarity, the QuickContacts control does not implement state management for the collection property. The collection items are assumed to be added declaratively in the page or, if created in code, they must be recreated on postback. In a production-quality control, you would implement state management. For details, see Server Control Custom State Management.

A Visual Studio Web site project with source code is available to accompany this topic: Download. Note that the project contains references to .NET Framework 4 assemblies, but the code is the same for both .NET Framework 3.5 and .NET Framework 4.

Code Listing for the QuickContacts Control

' QuickContacts.vbOption Strict OnImports System
Imports System.ComponentModel
Imports System.Collections
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace Samples.AspNet.VB.Controls
    < _
    DefaultProperty("Contacts"), _
    ParseChildren(True, "Contacts"), _
    ToolboxData( _
        "<{0}:QuickContacts runat=""server""> </{0}:QuickContacts>") _
    > _
    PublicClass QuickContacts
        Inherits WebControl

        Private contactsList As ArrayList

        < _
        Category("Behavior"), _
        Description("The contacts collection"), _
        DesignerSerializationVisibility( _
            DesignerSerializationVisibility.Content), _
        Editor(GetType(ContactCollectionEditor), _
            GetType(ContactCollectionEditor)), _
        PersistenceMode(PersistenceMode.InnerDefaultProperty) _
        > _
        PublicReadOnlyProperty Contacts() As ArrayList
            GetIf contactsList IsNothingThen
                    contactsList = New ArrayList
                EndIfReturn contactsList
            EndGetEndProperty
        ' The contacts are rendered in an HTML table.ProtectedOverridesSub RenderContents( _
        ByVal writer As HtmlTextWriter)
            Dim t As Table = CreateContactsTable()
            If t IsNotNothingThen
                t.RenderControl(writer)
            EndIfEndSubPrivateFunction CreateContactsTable() As Table
            Dim t As Table = NothingIf (contactsList IsNotNothing) AndAlso _
                (contactsList.Count > 0) Then
                t = New Table
                ForEach item As Contact In contactsList
                    Dim aContact As Contact = TryCast(item, Contact)
                    If aContact IsNotNothingThenDim r AsNew TableRow
                        Dim c1 AsNew TableCell
                        c1.Text = aContact.Name
                        r.Controls.Add(c1)

                        Dim c2 AsNew TableCell
                        c2.Text = aContact.Email
                        r.Controls.Add(c2)

                        Dim c3 AsNew TableCell
                        c2.Text = aContact.Phone
                        r.Controls.Add(c3)

                        t.Controls.Add(r)
                    EndIfNextEndIfReturn t
        EndFunctionEndClassEndNamespace
// QuickContacts.csusing System;
using System.ComponentModel;
using System.Collections;
using System.Drawing.Design;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Samples.AspNet.CS.Controls
{
    [
    AspNetHostingPermission(SecurityAction.Demand,
        Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, 
        Level=AspNetHostingPermissionLevel.Minimal),
    DefaultProperty("Contacts"),
    ParseChildren(true, "Contacts"),
    ToolboxData(
        "<{0}:QuickContacts runat=\"server\"> </{0}:QuickContacts>")
    ]
    publicclass QuickContacts : WebControl
    {
        private ArrayList contactsList;

        [
        Category("Behavior"),
        Description("The contacts collection"),
        DesignerSerializationVisibility(
            DesignerSerializationVisibility.Content),
        Editor(typeof(ContactCollectionEditor), typeof(UITypeEditor)),
        PersistenceMode(PersistenceMode.InnerDefaultProperty)
        ]
        public ArrayList Contacts
        {
            get
            {
                if (contactsList == null)
                {
                    contactsList = new ArrayList();
                }
                return contactsList;
            }
        }


        // The contacts are rendered in an HTML table.protectedoverridevoid RenderContents(
            HtmlTextWriter writer)
        {
            Table t = CreateContactsTable();
            if (t != null)
            {
                t.RenderControl(writer);
            }
        }

        private Table CreateContactsTable()
        {
            Table t = null;

            if (contactsList != null && contactsList.Count > 0)
            {
                t = new Table();

                foreach (Contact item in contactsList)
                {
                    Contact aContact = item as Contact;

                    if (aContact != null)
                    {
                        TableRow r = new TableRow();

                        TableCell c1 = new TableCell();
                        c1.Text = aContact.Name;
                        r.Controls.Add(c1);

                        TableCell c2 = new TableCell();
                        c2.Text = aContact.Email;
                        r.Controls.Add(c2);


                        TableCell c3 = new TableCell();
                        c3.Text = aContact.Phone;
                        r.Controls.Add(c3);

                        t.Controls.Add(r);
                    }
                }
            }
            return t;
        }
    }
}

Code Discussion

To enable parsing of the collection items within a control's tags, the QuickContacts control adds the ParseChildren(true, "Contacts") attribute to the control. The first argument (true) of ParseChildrenAttribute specifies that the page parser should interpret nested content within the control's tags as properties and not as child controls. The second argument ("Contacts") provides the name of the inner default property. When you specify the second argument, content within the control's tags must correspond to the default inner property (Contact objects) and to nothing else.

The QuickContacts control also includes the following design-time attributes that you must apply to a collection property for design-time serialization and persistence:

  • DesignerSerializationVisibilityAttribute   Setting the Content parameter specifies that a visual designer should serialize the contents of the property. In the example, the property contains Contact objects.

  • PersistenceModeAttribute   Passing the InnerDefaultProperty parameter specifies that a visual designer should persist the property to which the attribute is applied as an inner default property. This means that a visual designer persists the property within the control's tags. The attribute can be applied to only one property, because only one property can be persisted within the control's tags. The property value is not wrapped in a special tag.

The QuickContacts control associates a collection editor with the Contacts collection property using the EditorAttribute, as in the following example:

Editor(typeof(ContactCollectionEditor), typeof(UITypeEditor))
Editor(GetType(ContactCollectionEditor), GetType(UITypeEditor))

Associating a collection editor with the property enables the property browser in a visual designer to open a collection editor for adding Contact items. This is similar to the user interface (UI) for editing the Items property of the DropDownList or ListBox controls. The custom collection editor used by QuickContacts, ContactCollectionEditor, is described in Collection Editor Example.

For clarity, the QuickContacts control does not define a strongly typed collection and instead uses an ArrayList for its collection type. In general, you should use a strongly typed collection as the type of the collection property so that an application developer cannot add arbitrary types to the collection.

Code Listing for the Contact Class

The design-time attributes in the code of the Contact class are needed for property editing and design-time serialization. The ExpandableObjectConverter type converter associated with the Contact class (using the TypeConverterAttribute) enables the collection editor to provide an expand/collapse user interface for editing subproperties (Name, Email, Phone). This is similar to the UI you see when editing the Font property of a Web control in the property browser of a visual designer. The NotifyParentPropertyAttribute (with the constructor argument equal to true) applied to the Name, Email, and Phone properties causes the editor to serialize changes in these properties into their parent property, an instance of the Contact class.

' Contact.vb' The type of the items in the Contacts collection property ' in QuickContacts.Option Strict OnImports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Web.UI

Namespace Samples.AspNet.VB.Controls
    < _
    TypeConverter(GetType(ExpandableObjectConverter)) _
    > _
    PublicClass Contact
        Private _name AsStringPrivate _email AsStringPrivate _phone AsStringPublicSubNew()
            Me.New(String.Empty, String.Empty, String.Empty)
        EndSubPublicSubNew(ByVal name AsString, _
        ByVal email AsString, ByVal phone AsString)
            _name = name
            _email = email
            _phone = phone
        EndSub

        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Name of contact"), _
        NotifyParentProperty(True) _
        > _
        PublicProperty Name() AsStringGetReturn _name
            EndGetSet(ByVal value AsString)
                _name = value
            EndSetEndProperty

        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Email address of contact"), _
        NotifyParentProperty(True) _
        > _
        PublicProperty Email() AsStringGetReturn _email
            EndGetSet(ByVal value AsString)
                _email = value
            EndSetEndProperty


        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Phone number of contact"), _
        NotifyParentProperty(True) _
        > _
        PublicProperty Phone() AsStringGetReturn _phone
            EndGetSet(ByVal value AsString)
                _phone = value
            EndSetEndPropertyEndClassEndNamespace
// Contact.cs// The type of the items in the Contacts collection property //in QuickContacts.using System;
using System.Collections;
using System.ComponentModel;
using System.Web.UI;

namespace Samples.AspNet.CS.Controls
{
    [
    TypeConverter(typeof(ExpandableObjectConverter))
    ]
    publicclass Contact
    {
        privatestring nameValue;
        privatestring emailValue;
        privatestring phoneValue;


        public Contact()
            : this(String.Empty, String.Empty, String.Empty)
        {
        }

        public Contact(string name, string email, string phone)
        {
            nameValue = name;
            emailValue = email;
            phoneValue = phone;
        }

        [
        Category("Behavior"),
        DefaultValue(""),
        Description("Name of contact"),
        NotifyParentProperty(true),
        ]
        public String Name
        {
            get
            {
                return nameValue;
            }
            set
            {
                nameValue = value;
            }
        }

        [
        Category("Behavior"),
        DefaultValue(""),
        Description("Email address of contact"),
        NotifyParentProperty(true)
        ]
        public String Email
        {
            get
            {
                return emailValue;
            }
            set
            {
                emailValue = value;
            }
        }

        [
        Category("Behavior"),
        DefaultValue(""),
        Description("Phone number of contact"),
        NotifyParentProperty(true)
        ]
        public String Phone
        {
            get
            {
                return phoneValue;
            }
            set
            {
                phoneValue = value;
            }
        }
    }
}

Test Page for the QuickContacts Control

The following example shows an .aspx page that uses the QuickContacts control.

<%@ Page Language="VB"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>
      QuickContacts test page
    </title>
  </head>
  <body>
    <form id="Form1" runat="server">
      <aspSample:QuickContacts ID="QuickContacts1" Runat="server" 
        BorderStyle="Solid" BorderWidth="1px">
        <aspSample:Contact Name="someone" Email="someone@example.com" 
          Phone="(555) 555-0100"/>
        <aspSample:Contact Name="jae" Email="jae@fourthcoffee.com" 
          Phone="(555) 555-0101"/>
        <aspSample:Contact Name="lene" Email="lene@contoso.com" 
          Phone="(555) 555-0102"/>        
      </aspSample:QuickContacts>
    </form>
  </body>
</html>
<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>
      QuickContacts test page
    </title>
  </head>
  <body>
    <form id="Form1" runat="server">
      <aspSample:QuickContacts ID="QuickContacts1" Runat="server" 
        BorderStyle="Solid" BorderWidth="1px">
        <aspSample:Contact Name="someone" Email="someone@example.com" 
          Phone="(555) 555-0100"/>
        <aspSample:Contact Name="jae" Email="jae@fourthcoffee.com" 
          Phone="(555) 555-0101"/>
        <aspSample:Contact Name="lene" Email="lene@contoso.com" 
          Phone="(555) 555-0102"/>        
      </aspSample:QuickContacts>
    </form>
  </body>
</html>

Building and Using the Example

Compile the QuickContacts control and the Contacts class with the ContactCollectionEditor editor listed in Collection Editor Example. You must add a reference to the System.Design assembly for compilation.

For more information about compiling and using the custom control examples, see Building the Custom Server Control Examples.

See Also

Concepts

Collection Editor Example

Building the Custom Server Control Examples

Other Resources

Developing Custom ASP.NET Server Controls

Change History

Date

History

Reason

February 2010

Added link to downloadable project.

Customer feedback.