Как я могу определить свойство элемента управления пользователя или сервера ASP.NET, чтобы разрешить несколько значений строк в виде вложенных тегов с пользовательским именем тега?

Мне нужно написать пользовательский элемент управления, который можно использовать со следующим синтаксисом:

<quiz:Question runat="server">
  <Answer>Foo</Answer>
  <Answer>Bar</Answer>
</quiz:Question>

Я попробовал следующее объявление свойства:

[ParseChildren(true, "Answer")]
public class Question : UserControl
{
  [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  public string[] Answer { get; set; }
}

Но тогда редактор Visual Studio настаивает на том, что <Answers > должен быть самозакрывающимся, и я получаю это исключение, если я решаю иначе:

Литеральное содержимое ('Foo') недопустимо в пределах 'System.String []'.

Я смотрел на <asp:DropDownList> в Reflector, который наследует от ListControl, который объявляет свойство Items следующим образом:

ParseChildren(true, "Items")
public abstract class ListControl
{
  [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  public virtual ListItemCollection Items { get; }
}

Это не то же самое, что я хочу, потому что в DropDownList вы должны добавить <asp:ListItem> в качестве детей. И есть некоторые вещи, которые я не понимаю о дизайне управления, который в настоящее время мешает мне найти решение:

  • Почему тег <asp:ListItem> не требует атрибута runat="server"?
  • Могу ли я объявить такой "контроль"?
  • Что такого особенного в ListItemCollection, что он переводит на этот конкретный синтаксис?
  • Какой код я могу написать, который переведёт в синтаксис, приведенный в первом примере кода выше?

Ответ 1

Я смешанное поведение элементов управления, у которых могут быть дочерние элементы (например, ListControl), с элементами управления, такими как Panel (ParseChildren = false):

    [ParseChildren(true, "Answers")]
    public class Question : WebControl, INamingContainer
    {
        private AnswerCollection answers;

        public virtual AnswerCollection Answers
        {
            get
            {
                if (this.answers == null)
                {
                    this.answers = new AnswerCollection();
                }
                return this.answers;
            }
        }


        public override void RenderControl(HtmlTextWriter writer)
        {
            //access to each answer value
            foreach (var a in this.Answers)
                writer.WriteLine(((LiteralControl)a.Controls[0]).Text + "<br/>");
        }
    }

    [ParseChildren(false), PersistChildren(true)]
    public class Answer : Control
    {
    }

    public class AnswerCollection : List<Answer>
    {
    }

Тогда вы сможете иметь что-то вроде:

<cc1:Question runat="server">
    <cc1:Answer>Answer1</cc1:Answer>
    <cc1:Answer>Answer2</cc1:Answer>
</cc1:Question>

Надеюсь, что это поможет