Почему CheckBoxFor отображает дополнительный тег ввода и как я могу получить значение с помощью FormCollection?

130

В моем приложении ASP.NET MVC я визуализирую флажок, используя следующий код:

<%= Html.CheckBoxFor(i=>i.ReceiveRSVPNotifications) %>

Теперь я вижу, что это отображает как тег ввода флажка, так и скрытый тег ввода. Проблема, с которой я сталкиваюсь, - это когда я пытаюсь получить значение из флажка с помощью FormCollection:

FormValues["ReceiveRSVPNotifications"]

Я получаю значение «истина, ложь». Глядя на визуализированный HTML-код, я вижу следующее:

 <input id="ReceiveRSVPNotifications" name="ReceiveRSVPNotifications" value="true" type="checkbox">
 <input name="ReceiveRSVPNotifications" value="false" type="hidden">

Таким образом, кажется, что коллекция FormValues ​​объединяет эти два значения, поскольку они имеют одинаковое имя.

Любые идеи?

Webcognoscere
источник

Ответы:

168

Взгляните сюда:

http://forums.asp.net/t/1314753.aspx

Это не ошибка, и фактически это тот же подход, который используют как Ruby on Rails, так и MonoRail.

Когда вы отправляете форму с флажком, значение публикуется только в том случае, если этот флажок установлен. Таким образом, если вы не установите этот флажок, на сервер ничего не будет отправлено, тогда как во многих ситуациях вы бы хотели, чтобы вместо этого отправлялось false. Поскольку скрытый ввод имеет то же имя, что и флажок, то, если флажок не установлен, вы все равно получите сообщение «false», отправленное на сервер.

Когда флажок установлен, ModelBinder автоматически извлекает «истина» из «истина, ложь».

Роберт Харви
источник
10
Это хорошее объяснение, но в некоторых случаях вы можете захотеть получить «ничего» в строке запроса, когда ckecbox не отмечен - так что поведение по умолчанию. Возможно ли это с помощью помощника Html или мне нужно просто использовать <input>тег.
Максимилиан Майер
13
Мне это не нравится .... У меня есть страница поиска, которая использует GET, и теперь мой URL-адрес заполнен истинными / ложными параметрами ...
Шон Маклин
1
Вау, это неожиданно. Означает ли это, что флажок HTML на самом деле "сломан"?
Исантипов
1
@Isantipov: Нет, это просто означает, что эти веб-фреймворки не полагаются на отсутствие опубликованного значения для флажка false. Посмотрите на ответ RyanJMcGowan ниже: «Отправка скрытого ввода позволяет узнать, что флажок присутствовал на странице, когда был отправлен запрос».
Роберт Харви
1
Что ж, @Robert, у меня это не работает. Если мой chbox MyCheckbox не отмечен, я получаю false, когда MyCheckbox отмечен , я получаю массив [true, false] в качестве его значения. Я сериализую всю форму и передаю ее через ajax в действие контроллера, например. AddToOrder (модель MyModel), где я получаю model.MyCheckbox = false вместо true
nickornotto
18

У меня была та же проблема, что и у Шона (см. Выше). Этот подход может быть отличным для POST, но действительно отстой для GET. Поэтому я реализовал простое расширение Html, которое просто выводит скрытое поле.

public static MvcHtmlString BasicCheckBoxFor<T>(this HtmlHelper<T> html, 
                                                Expression<Func<T, bool>> expression,
                                                object htmlAttributes = null)
{
    var result = html.CheckBoxFor(expression).ToString();
    const string pattern = @"<input name=""[^""]+"" type=""hidden"" value=""false"" />";
    var single = Regex.Replace(result, pattern, "");
    return MvcHtmlString.Create(single);
}

Проблема в том, что я не хочу, чтобы изменение инфраструктуры MVC нарушило мой код. Поэтому я должен убедиться, что у меня есть тестовое покрытие, объясняющее этот новый контракт.

Крис Кемп
источник
5
Вам не кажется немного грязным, что вы использовали регулярное выражение для сопоставления / удаления скрытого ввода, а не просто создавали только ввод флажка? :)
Тим Хоббс
2
Нет, я сделал это для того, чтобы изменить существующее поведение, а не заново реализовывать свое собственное. Таким образом, мои изменения будут идти в ногу с любыми изменениями, выпущенными MS.
Крис Кемп
10
Просто небольшое наблюдение. Ваша реализация не будет отображать переданные настраиваемые атрибуты. Параметр «htmlAttributes» не передается в исходный метод «CheckBoxFor».
Эмиль Лундин
16

Я использую этот альтернативный метод для отображения флажков для форм GET:

/// <summary>
/// Renders checkbox as one input (normal Html.CheckBoxFor renders two inputs: checkbox and hidden)
/// </summary>
public static MvcHtmlString BasicCheckBoxFor<T>(this HtmlHelper<T> html, Expression<Func<T, bool>> expression, object htmlAttributes = null)
{
    var tag = new TagBuilder("input");

    tag.Attributes["type"] = "checkbox";
    tag.Attributes["id"] = html.IdFor(expression).ToString();
    tag.Attributes["name"] = html.NameFor(expression).ToString();
    tag.Attributes["value"] = "true";

    // set the "checked" attribute if true
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
    if (metadata.Model != null)
    {
        bool modelChecked;
        if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
        {
            if (modelChecked)
            {
                tag.Attributes["checked"] = "checked";
            }
        }
    }

    // merge custom attributes
    tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));

    var tagString = tag.ToString(TagRenderMode.SelfClosing);
    return MvcHtmlString.Create(tagString);
}

Он похож на метод Криса Кемпа , который работает нормально, за исключением того, что в нем не используются лежащие в основе CheckBoxForи Regex.Replace. Он основан на источнике исходного Html.CheckBoxForметода.

Том Пажоурек
источник
Это абсолютно лучшее решение этой проблемы, и оно должно быть частью структуры MVC ... работает отлично. Я не уверен, почему ваш ответ не получил большего внимания ... спасибо!
user2315985 04
@ user2315985: Я запоздало выложил;) вот в чем причина.
Tom Pažourek
Как вы обрабатываете свойства коллекции? Связыватель модели по умолчанию прекратит привязку значений, когда обнаружит пробел в привязанных индексах, поэтому кажется, что вы используете ваше расширение, если, скажем, первое и последнее значения были проверены, вы никогда не узнаете о втором значении, которое вы должны получить , Или я что-то упускаю?
Tieson T.
@TiesonT. Если вы не хотите писать свою собственную подшивку модели, единственное решение - предотвратить зазоры. Этот метод не отправляет false для снятых флажков, поэтому вы можете использовать исходный метод Html.CheckBoxFor, который делает это.
Tom Pažourek
10

Вот исходный код для дополнительного входного тега. Microsoft любезно предоставила комментарии, которые именно по этому поводу.

if (inputType == InputType.CheckBox)
{
    // Render an additional <input type="hidden".../> for checkboxes. This
    // addresses scenarios where unchecked checkboxes are not sent in the request.
    // Sending a hidden input makes it possible to know that the checkbox was present
    // on the page when the request was submitted.
    StringBuilder inputItemBuilder = new StringBuilder();
    inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

    TagBuilder hiddenInput = new TagBuilder("input");
    hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
    hiddenInput.MergeAttribute("name", fullName);
    hiddenInput.MergeAttribute("value", "false");
    inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
    return MvcHtmlString.Create(inputItemBuilder.ToString());
}
RyanJMcGowan
источник
9

Я думаю, что самое простое решение - визуализировать элемент INPUT напрямую следующим образом:

<input type="checkbox" 
       id="<%=Html.IdFor(i => i.ReceiveRSVPNotifications)%>"
       name="<%=Html.NameFor(i => i.ReceiveRSVPNotifications)%>"
       value="true"
       checked="<%=Model.ReceiveRSVPNotifications ? "checked" : String.Empty %>" />

В синтаксисе Razor это еще проще, потому что атрибут «checked» напрямую отображается со значением «checked», когда ему задано «истинное» значение на стороне сервера.

граммофон
источник