ViewState и динамически удаляемые элементы управления

ViewState страницы ASP.NET, похоже, не справляется с динамически удаляемыми элементами управления и значениями в них.

В качестве примера возьмем следующий код:

ASPX:

<form id="form1" runat="server">
<div>
    <asp:Panel runat="server" ID="controls" />
</div>
</form>

CS:

protected void Page_Init(object sender, EventArgs e) {
    Button b = new Button();
    b.Text = "Add";
    b.Click +=new EventHandler(buttonOnClick);
    form1.Controls.Add(b);
    Button postback = new Button();
    postback.Text = "Postback";
    form1.Controls.Add(postback);
}

protected void Page_Load(object sender, EventArgs e) {
    if (ViewState["controls"] != null) {
        for (int i = 0; i < int.Parse(ViewState["controls"].ToString()); i++) {
            controls.Controls.Add(new TextBox());
            Button remove = new Button();
            remove.Text = "Remove";
            remove.Click +=new EventHandler(removeOnClick);
            controls.Controls.Add(remove);
            controls.Controls.Add(new LiteralControl("<br />"));
        }
    }
}

protected void removeOnClick(object sender, EventArgs e) {
    Control s = sender as Control;
    //A hacky way to remove the components around the button and the button itself
    s.Parent.Controls.Remove(s.Parent.Controls[s.Parent.Controls.IndexOf(s) + 1]);
    s.Parent.Controls.Remove(s.Parent.Controls[s.Parent.Controls.IndexOf(s) - 1]);
    s.Parent.Controls.Remove(s.Parent.Controls[s.Parent.Controls.IndexOf(s)]);
    ViewState["controls"] = (int.Parse(ViewState["controls"].ToString()) - 1).ToString();
}

protected void buttonOnClick(object sender, EventArgs e) {
    if (ViewState["controls"] == null)
        ViewState["controls"] = "1";
    else
        ViewState["controls"] = (int.Parse(ViewState["controls"].ToString()) + 1).ToString();
    controls.Controls.Add(new TextBox());
}

Затем, допустим, вы создали 4 элемента управления и вставили следующие значения:

[ 1 ] [ 2 ] [ 3 ] [ 4 ]

Мы хотим удалить второй элемент управления; после удаления второго элемента управления вывод будет:

[ 1 ] [ 3 ] [ 4 ]

чего мы и хотим. К сожалению, при следующем PostBack список становится следующим:

[ 1 ] [ ] [ 3 ]

Итак, мой вопрос: почему это происходит? Насколько я читал, ViewState должен сохранять свойства элементов управления по отношению к их индексам, а не фактические элементы управления.


person vipirtti    schedule 13.03.2009    source источник


Ответы (2)


Пара вещей. Загружаются ли элементы управления по их идентификатору или индексу, зависит от атрибута ViewStateModeById. По умолчанию это false (имеется в виду загрузка по индексу).

Однако текстовые поля обрабатываются по-другому. Их состояние просмотра не содержит входного значения, если они не отключены или невидимы. Свойство text перезаписывается опубликованными значениями с использованием их идентификаторов. Вот что происходит, поскольку вы не управляете идентификаторами текстовых полей.

После добавления четырех элементов управления у вас есть четыре текстовых поля: ctrl0, ctrl1, ctrl2 и ctrl3 со значениями 1, 2, 3 и 4 соответственно.

Затем вы удаляете поле ctrl1, и клиент получает три поля: ctrl0, ctrl2 и ctrl3 с соответствующими значениями. Теперь, когда вы выполняете обратную передачу, эти три значения передаются в форме ctrl0 = 1 & ctrl2 = 3 & ctrl3 = 4.

Затем в Page_Load вы создаете три элемента управления, на этот раз: ctrl0, ctrl1, ctrl2 без значений.

Платформа вызывает LoadRecursive для загрузки состояний представления, а затем ProcessPostData для назначения входных значений. Он видит отправленные ctrl0 и ctrl2, находит элементы управления с одинаковым идентификатором и присваивает им значения 1 и 3. Он не находит ctrl3, поэтому пропускает его. Оставшийся ctrl1 просто продолжает работать без какого-либо значения.

В качестве примера рассмотрим это решение (не самое лучшее):

protected void Page_Init(object sender, EventArgs e)
{
    Button b = new Button();
    b.Text = "Add";
    b.Click += new EventHandler(buttonOnClick);
    form1.Controls.Add(b);

    Button postback = new Button();
    postback.Text = "Postback";
    form1.Controls.Add(postback);
}

protected void Page_Load(object sender, EventArgs e)
{
    if (ViewState["controls"] != null)
    {
        List<string> ids = (List<string>)ViewState["controls"];

        for (int i = 0; i < ids.Count; i++)
        {
            TextBox textbox = new TextBox();
            textbox.ID = string.Format("txt_{0}", ids[i]);
            textbox.Text = textbox.ID;
            controls.Controls.Add(textbox);

            Button remove = new Button();
            remove.Text = "Remove";
            remove.Click += new EventHandler(removeOnClick);
            remove.ID = ids[i];
            controls.Controls.Add(remove);

            controls.Controls.Add(new LiteralControl("<br />"));
        }
    }
}

protected void removeOnClick(object sender, EventArgs e)
{
    Control btn = sender as Control;

    List<string> ids = (List<string>)ViewState["controls"];
    ids.Remove(btn.ID);

    //A hacky way to remove the components around the button and the button itself
    btn.Parent.Controls.Remove(btn.Parent.Controls[btn.Parent.Controls.IndexOf(btn) + 1]);
    btn.Parent.Controls.Remove(btn.Parent.Controls[btn.Parent.Controls.IndexOf(btn) - 1]);
    btn.Parent.Controls.Remove(btn);

    ViewState["controls"] = ids;
}

protected void buttonOnClick(object sender, EventArgs e)
{
    List<string> ids;

    if (ViewState["controls"] == null)
        ids = new List<string>();
    else
        ids = (List<string>)ViewState["controls"];

    string id = Guid.NewGuid().ToString();
    TextBox textbox = new TextBox();
    textbox.ID = string.Format("txt_{0}", id);
    textbox.Text = textbox.ID;
    controls.Controls.Add(textbox);

    Button remove = new Button();
    remove.Text = "Remove";
    remove.Click += new EventHandler(removeOnClick);
    remove.ID = id;
    controls.Controls.Add(remove);

    controls.Controls.Add(new LiteralControl("<br />"));

    ids.Add(id);

    ViewState["controls"] = ids;
}
person Ruslan    schedule 13.03.2009
comment
У меня есть связанная проблема - у меня есть сетка, зависящая от обратной передачи комбо. Когда вы меняете комбо, сетка обновляется данными для нового значения комбо. Но если вы нажмете «Обновить», сетка вернется к данным по умолчанию для первого элемента в комбинации, но комбинация будет сброшена на элемент, который был выбран при обновлении, поэтому сетка и комбинация не синхронизированы. Я пробовал привязать и установить комбинацию для первого элемента на Page_Load if !postback, и html показывает, что верхний элемент украшен selected="selected", но какой-то javascript затем меняет его на элемент, отличный от значения по умолчанию! Это связано? - person Chris; 07.08.2017

Половину кода вы выполняете в Page_Load, а половину - в событиях обратной передачи. Где-то в этом беспорядке вы сталкиваетесь с конфликтами. Тот факт, что они не появляются до второй обратной передачи, говорит мне, что в вашей логике есть некоторые отклонения.

Я не уверен, где именно возникла проблема, но обработка ViewState - не самая интересная вещь в мире, когда вы выполняете нестандартную работу ViewState. Мне пришлось бы перестроить приложение и установить условия наблюдения, чтобы увидеть, что происходит, но я почти уверен, что это несоответствие импеданса между вашим кодом Page_Load и обработчиками событий.

person Gregory A Beamer    schedule 13.03.2009