Spontaneous Publicity
blogs are the new phone book

Child Collections in Asp.Net Custom Controls

May 23, 2007 12:47 by Luke

I have been developing custom web controls for many years now and I am just now getting comfortable with many of the advanced features afforded by the rich System.Web.UI.WebControls namespace. One feature I have implemented many different times over the years is having a web control which contains a list of sub items. For example, the Asp.Net GridView's collection of Columns or the DropDownList's collection of ListItems.

<asp:GridView ID="grid" runat="server"> 
    <Columns> 
        <asp:BoundField /> 
        <asp:ButtonField /> 
    </Columns> 
</asp:GridView>

There is so many things going on it a control like this that just seem like magic. How are the child items persisted in ViewState? How does the control know how to parse the child items? What if the whole world were a power ranger?

StateManagedItem

I have created a couple of classes that really simplify the process of creating lists of child items. The first of which is the class StateManagedItem. This class is the base class for all the objects which you wish to use as "Child Items" (like grid view columns or list items).

In order to manage state in Asp.Net, you have to implement the IStateManager interface. This interface allows objects to participate in the process of saving and retrieving things from ViewState. In order to generalize the whole process, I needed on other method to set an item as "Dirty" so I created the following interface:

public interface IStateManagedItem : IStateManager 
{ 
    void SetDirty(); 
}


So now we have the base interface for all state managed items. So here is the what the base class definition looks like:

public abstract class StateManagedItem : IStateManagedItem

This class provides it's own ViewState for items to save their state in which essentially allows "Child Items" to save their state just like custom web controls do. To illustrate this concept, lets create an example class called Appointment which will hold information about a calendar appointment.

public class Appointment : StateManagedItem

The definition for this class is very simple. Here is how the properties are implemented:

public DateTime? Start 
{ 
    get 
    { 
        object o = ViewState["Start"]; 
        return o == null ? null : (DateTime?)o; 
    } 
    set { ViewState["Start"] = value; } 
}

If you are familiar with using ViewState to store web control properties, this syntax should look fairly familiar. That that is all there is to our child item.

StateManagedCollection

The next helper class is a base class for the collection of items. In our example, we need a collection of Appointments. For this purpose, I have created a StateManagedCollection<T> class which can be used to hold the child items.

public abstract class StateManagedCollection<T> : IList<T>, ICollection<T>, IEnumerable<T>, IStateManager, 
    IList, ICollection 
    where T : class, IStateManagedItem, new()

If you are not familiar with generics and generic constraints, this is basically a list class which requires the child items to be a class (not a struct), derive from IStateManagedItem, and to have a default constructor.

So to create our list of Appointments, all we need to do is the following:

public class AppointmentCollection : StateManagedCollection<Appointment> 
{ 
}

That is it as far as the AppointmentCollection class goes. It doesn't require any more code.

Sample Control

So lets finish the example by creating a custom control which displays a list of Appointments. First we will derive from Composite Control:

[ParseChildren(true), PersistChildren(false)] 
public class AppointmentControl : CompositeControl

Note the ParseChildren and PersistChildren attributes, these are required to tell Asp.Net how to parse the markup inbetween the start and end tags of your control. This is part of the "Magic" I mentioned earlier.

Next, make a member of the class which is an AppointmentCollection:

private AppointmentCollection appointments = null;


And a public property to access that collection:

[ 
    Mergable(false), 
    PersistenceMode(PersistenceMode.InnerProperty), 
] 
public AppointmentCollection Appointments 
{ 
    get 
    { 
        if (appointments == null) 
        { 
            appointments = new AppointmentCollection(); 
            if (base.IsTrackingViewState) 
            { 
                appointments.TrackViewState(); 
            } 
        } 
        return appointments; 
    } 
}

This property also must have a couple of attributes - Mergable and PersistenceMode. These basically tell Asp.Net and Visual Studio how to handle and persist this property.

To use this control, first place the proper includ directive on your page:

<%@ Register TagPrefix="web" Namespace="Web.Example.Appointment" %>

Now we can declare our control markup as:

<web:AppointmentControl id="appointmentList" runat="server"> 
    <Appointments> 
        <web:Appointment Start="1/6/2007 8:00 AM" End="1/6/2007 11:30 AM" Description="Breakfast" /> 
        <web:Appointment Start="5/12/2007 9:30 AM" End="5/12/2007 9:45 AM" Description="Meeting" /> 
        <web:Appointment Start="5/12/2007 10:00 AM" End="5/12/2007 10:30 AM" Description="Break" /> 
    </Appointments> 
</web:AppointmentControl>

I won't go into the render code (you can download the source code to see all the gory details) but here is how the control renders:

You have 3 appointment(s)

  • Breakfast - (01/06/2007 08:00:00 - 01/06/2007 11:30:00)
  • Meeting - (05/12/2007 09:30:00 - 05/12/2007 09:45:00)
  • Break - (05/12/2007 10:00:00 - 05/12/2007 10:30:00)

You can download the full source code here. Hope this helps dispell some of the "Magic" of Asp.Net controls.

kick it on DotNetKicks.com
Tags:
Categories:
Actions: E-mail | del.icio.us | Permalink | Comments (13) | Comment RSSRSS comment feed

Comments

June 4. 2007 16:41

Will


          Hi Luke, good work! Are you looking for a new job? We're a local firm and hiring, let me know.

          Thanks.
          Will
        

Will

August 21. 2007 11:24

Ian Kulmatycki


          Hi,

          These base classes are a great idea. Well done.

          ian
        

Ian Kulmatycki

October 11. 2007 05:17

Chris

I have been messing with the composite control examples online for days, sigh . . . Downloaded your code and bam done, simple elegant works like a charm, my composite control works great. Now I can sleep. Thanks

Chris

November 7. 2007 04:43

Steve

Just what I was looking for. There doesn't seem to be a lot of info on the net about how this is done, so thanks for this write-up!

Steve

November 29. 2007 05:01

naisioxerloro


          Hi.
          Good design, who make it?
        

naisioxerloro

December 9. 2007 04:46

[...] enums as well as some parsing methods similar to these. I have written about DelimitedList and the state managed classes before. I make heavy use of QueryStringHelper to build the query string which  represents the [...]

Asp.Net Control For Google Charts « Spontaneous Publicity

April 3. 2008 21:13

Nisar

hi guys.. need help here..

i want to use the Appointments statebag at later time, how would i do that?

actually, i have a button on my custom control appointmentControl class and when the user clicks on that i want to read all the appointments and this is what i did

foreach (Appointment appointment in Appointments)
{
   //
}

but i'm getting Appointments count=0 ?
what exactly do i need to modify in order to get the items from viewstate?

any help is greatly appreciate.

thanks

Nisar

April 9. 2008 15:33

Nisar

after i add the following code in the .aspx page

<web:AppointmentControl id="appointmentList" runat="server">
    <Appointments>
        <web:Appointment Start="1/6/2007 8:00 AM" End="1/6/2007 11:30 AM" Description="Breakfast" />
        <web:Appointment Start="5/12/2007 9:30 AM" End="5/12/2007 9:45 AM" Description="Meeting" />
        <web:Appointment Start="5/12/2007 10:00 AM" End="5/12/2007 10:30 AM" Description="Break" />
    </Appointments>
</web:AppointmentControl>

when i switch to design mode i get this error:

Appointments could not initialized. Details: 'Appointments' could not be added to the collection. Details: object does not match target type

any idea why this error pop-up ?

Nisar

April 25. 2008 04:19

aram

How can implements this with webcontrol such as checkbox?

Thanks in advance

aram

June 13. 2008 06:31

Zalak Shah

hi all ... it's a good idea

i have one more question .. what happen if i will use this control as custom control and i will add more then one control to same page .. how view state will manage the different view states for all the controls ???

Zalak Shah

December 7. 2008 10:23

John

The example you provided does not maintain viewstate. For example, remove all of your child Appointment tags from the aspx page. Then add the following code to the Page_Load()

if (this.IsPostBack == false) {
            app = new Appointment();
            app.Start = DateTime.Now;
            app.End = DateTime.Now;
            app.Description = "Testing";
            appointmentList.Appointments.Add(app);
}

Now click a button or do something to cause a postback. The appointment will not be there anymore.

John

December 12. 2008 16:57

DLarsen

John is right.  Anxious for a fix.

DLarsen

December 17. 2008 05:02

Tomas Jansson

Hi,

First of all, great post. It helped me a lot to get rid of all the red marking in Visual Studio and getting the intellisense recognizing all the inner properties and control list.

Although, I spotted one thing that probably should be fixed... I might do it myself when I have time, but the problem is that you can't have different types of appointments in the above example. This is due to the new () constraint that doesn't allow abstract classes. I would prefer to have Appointment abstract and then have different types of Appointment, like training appointment, meeting appointment etc.

But overall, great work and it was a lot of help to almost understand how it works.

Regards,
--Tomas

Tomas Jansson