Search code examples
c#asp.netvisual-studio-2013webformsasp.net-customcontrol

Collections Editor is not persisting entries in custom web control markup in design view in VS 2013?


I am trying to develop a simple custom web control for ASP.Net WebForms that has a collections property called Subscriptions.

I can compile the control project successfully and add it from toolbox to an aspx page without any issues.

The problem is when I add entries for Subscriptions property using the collections editor in design view in Visual Studio 2013.

I can input multiple Subscriptions but when I click on OK button of the collections editor and then I go back to Subscriptions property in design view it's empty even though I had input some entries a moment ago.

Markup of custom control in aspx

<cc1:WebControl1 ID="WebControl1" runat="server"></cc1:WebControl1>

Question : What is not correct with my code that is causing the collections to not show up in control's markup in design view?

Custom web control code

namespace WebControl1
{
    [ToolboxData("<{0}:WebControl1 runat=\"server\"> </{0}:WebControl1>")]
    [ParseChildren(true)]
    [PersistChildren(false)]
    public class WebControl1 : WebControl
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text
        {
            get
            {
                String s = (String)ViewState["Text"];
                return ((s == null) ? "[" + this.ID + "]" : s);
            }

            set
            {
                ViewState["Text"] = value;
            }
        }
        [
        Category("Behavior"),
        Description("The subscriptions collection"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
        Editor(typeof(SubscriptionCollectionEditor), typeof(UITypeEditor)),
        PersistenceMode(PersistenceMode.InnerDefaultProperty)
        ]
        public List<Subscription> Subscriptions { get; set; }

        protected override void RenderContents(HtmlTextWriter output)
        {
            output.Write(Text);
        }
    }
}


[TypeConverter(typeof(ExpandableObjectConverter))]
public class Subscription
{
    private string name;
    private decimal amount;


    public Subscription()
        : this(String.Empty, 0.00m)
    {
    }

    public Subscription(string nm, decimal amt)
    {
        name = nm;
        amount = amt;
    }

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

    [
    Category("Behavior"),
    DefaultValue("0.00"),
    Description("Amount for subscription"),
    NotifyParentProperty(true)
    ]
    public decimal Amount
    {
        get
        {
            return amount;
        }
        set
        {
            amount = value;
        }
    }
   }

public class SubscriptionCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
    public SubscriptionCollectionEditor(Type type)
        : base(type)
    {
    }

    protected override bool CanSelectMultipleInstances()
    {
        return false;
    }

    protected override Type CreateCollectionItemType()
    {
        return typeof(Subscription);
    }
}

Solution

  • I was able to solve the problem by making following 2 changes.

    1. Since for collections like List the .Net framework will automatically display an appropriate editor so we don't need to specify the editor since the collection is of List type. So we don't need this attribute Editor(typeof(SubscriptionCollectionEditor), typeof(UITypeEditor)).
    2. The setter for Subscriptions needs to be removed and only a get should be there as in code below. If a setter is used then it should be used as in second code snippet. But automatic get and set should not be used with collection property in a custom web control.

    The final code for the collections property should look like below and then collections will not disappear when one returns to it later on in design-time VS 2013.

    Code that works without a setter

    private List<Subscription> list = null;
    
    [Category("Behavior"),
     Description("The subscriptions collection"),
     DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
     PersistenceMode(PersistenceMode.InnerDefaultProperty)
    ]
    public List<Subscription> SubscriptionList 
    { 
        get
        {
            if (lists == null)
            {
                lists = new List<Subscription>();
            }
            return lists;
        }
    }
    

    Code that works with a setter

    [Category("Behavior"),
     Description("The subscriptions collection"),            
     DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
     PersistenceMode(PersistenceMode.InnerDefaultProperty)
    ]
    public List<Subscription> SubscriptionList 
    { 
        get
        {
            object s = ViewState["SubscriptionList"];
            if ( s == null)
            {
                ViewState["SubscriptionList"] = new List<Subscription>();
            }
            return (List<Subscription>) ViewState["SubscriptionList"];
        }
        set
        {
            ViewState["SubscriptionList"] = value;
        }
    }