asp.net mvc: por que o Html.CheckBox está gerando uma entrada oculta adicional

215

Acabei de perceber que Html.CheckBox("foo")gera 2 entradas em vez de uma, alguém sabe por que isso acontece?

<input id="foo" name="foo" type="checkbox" value="true" />
<input name="foo" type="hidden" value="false" /> 
Omu
fonte
1
A resposta de ericvg na possível pergunta duplicada também explica o que o fichário do modelo faz quando a caixa de seleção e os campos ocultos são enviados.
Theophilus
1
Eu odeio essa coisa que estava acumulando com o meu jquery.
Jerry Liang
2
Estranha implementação por mvc. Enviar ambos os valores não faz sentido. Eu verifiquei Request.From ["myCheckBox"] e seu valor era verdadeiro, falso. WTF. Eu tive que escrever o controle manualmente na exibição.
21715 Nick Masao
Se isso for realmente indesejada, então não use Html.Checkbox (...) e entrada apenas o html para uma caixa de seleção
wnbates

Respostas:

197

Se a caixa de seleção não estiver selecionada, o campo do formulário não será enviado. É por isso que sempre há um valor falso no campo oculto. Se você deixar a caixa de seleção desmarcada, o formulário ainda terá valor do campo oculto. É assim que o ASP.NET MVC lida com os valores das caixas de seleção.

Se você quiser confirmar isso, marque a caixa de seleção não com Html.Hidden, mas com <input type="checkbox" name="MyTestCheckboxValue"></input>. Deixe a caixa de seleção desmarcada, envie o formulário e observe os valores de solicitação publicados no lado do servidor. Você verá que não há valor na caixa de seleção. Se você tivesse um campo oculto, ele conteria MyTestCheckboxValueentrada com falsevalor.

LukLed
fonte
52
Se você não marcou a caixa, obviamente a resposta é falsa, não é necessário enviar o item de volta. Design bobo na minha opinião.
O Muffin Man
54
@TheMuffinMan: Esta não é uma opção boba. Digamos que seu modelo de exibição tenha propriedade chamada IsActive, que é iniciada trueno construtor. O usuário desmarca a caixa de seleção, mas como o valor não é enviado ao servidor, o fichário do modelo não a seleciona e o valor da propriedade não é alterado. O fichário do modelo não deve assumir que, se o valor não for enviado, ele foi definido como falso, pois pode ser sua decisão de não enviar esse valor.
LukLed
21
Começa com uma caixa de seleção inofensiva e, antes que você perceba, temos o estado de exibição e depois evolui para o ASP.NET MVCForms.
O homem de muffin
4
@LukLed Respondendo tarde ao seu comentário ... Eu acho que a lógica é falha porque se o seu modelo de exibição tem um tipo de propriedade int e não há entrada no formulário, o valor padrão é 0 porque nenhum valor foi alterado para alterá-lo. O mesmo acontece com qualquer outra propriedade, o valor padrão para uma sequência é nulo. Se você não enviar um valor, é nulo. No seu exemplo de como definir uma propriedade como verdadeira no construtor, isso é honestamente uma péssima decisão de design e agora vem à tona por causa de como as caixas de seleção funcionam no terreno http.
O homem de muffin
8
Essa lógica de sombreamento interrompe as caixas de seleção desabilitadas - elas enviam falsevalor mesmo se estiverem marcadas e isso é confuso. As caixas de seleção desabilitadas não devem enviar nenhum valor, se o ASP.NET quiser ser compatível com o comportamento HTTP padrão.
precisa saber é o seguinte
31

Você pode escrever um auxiliar para evitar adicionar a entrada oculta:

using System.Web.Mvc;
using System.Web.Mvc.Html;

public static class HelperUI
{
    public static MvcHtmlString CheckBoxSimple(this HtmlHelper htmlHelper, string name, object htmlAttributes)
    {
        string checkBoxWithHidden = htmlHelper.CheckBox(name, htmlAttributes).ToHtmlString().Trim();
        string pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1));
        return new MvcHtmlString(pureCheckBox);
    }
}

use-o:

@Html.CheckBoxSimple("foo", new {value = bar.Id})
Alexander Trofimov
fonte
4
Isso me deixou tropeçado por um minuto, por via @using Your.Name.Spacedas dúvidas ... Se o seu ajudante estiver em um espaço para nome, não se esqueça de adicionar no topo do seu arquivo de visualização .cshtml.
ooXei1sh
3
@ ooXei1sh Ou isso, ou colocar o seu ajudante no espaço de nomes System.Web.Mvc.Htmlpara ser acessível em todos os pontos de vista
Lc
1
Se você deseja usar isso para uma postagem de formulário ou para postagem via ajax com os valores do formulário, precisará definir o valor como true ou false por meio do evento onchange na caixa de seleção. @ Html.CheckBoxSimple (x => Model.Foo, novo {value = Model.Foo, onchange = "toggleCheck (this)"}). Em seguida, na função javascript ToggleCompleted (el) {var marcada = $ (el) .is (': marcada'); $ ('# Foo'). Val (marcado); }
John81
12

quando a caixa de seleção estiver marcada e enviada, execute-o

if ($('[name="foo"]:checked').length > 0)
    $('[name="foo"]:hidden').val(true);

Referir

Desmond
fonte
8

A abordagem manual é esta:

bool IsDefault = (Request.Form["IsDefault"] != "false");
Valamas
fonte
1
E isso garante a você o valor do tipo de caixa de seleção e não o valor do tipo oculto?
27912 Brian Sweeney
1
isso é irrelevante. Quando a caixa de seleção não está marcada, ela não é lançada. Portanto, a caixa oculta terá 'false'. Se a caixa de seleção estiver marcada, o valor de 'true, true' é retornado (eu acho), e isso não é igual a 'false'.
Valamas
16
Não, quando a caixa de seleção está marcada, true e false são lançados porque a caixa de seleção e os campos ocultos são controles válidos a serem enviados de volta. O fichário do Mvc verifica se existe um valor verdadeiro para um determinado namve e, se assim for, prefere o valor verdadeiro. Você pode verificar isso visualizando os dados publicados. Ele terá valores verdadeiros e falsos em relação ao nome único.
ZafarYousafi
Isto começou-me uma vez, eu estava verificando se o valor == true
Terry Kernan
8

Esta é a versão fortemente tipada da solução de Alexander Trofimov:

using System.Web.Mvc;
using System.Web.Mvc.Html;

public static class HelperUI
{
    public static MvcHtmlString CheckBoxSimpleFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression, object htmlAttributes)
    {
        string checkBoxWithHidden = htmlHelper.CheckBoxFor(expression, htmlAttributes).ToHtmlString().Trim();
        string pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1));
        return new MvcHtmlString(pureCheckBox);
    }
}
Ciro Corvino
fonte
4

Use Contém, ele funcionará com os dois possíveis valores de postagem: "false" ou "true, false".

bool isChecked = Request.Form["foo"].Contains("true");
Dave D
fonte
1

A entrada oculta estava causando problemas nas caixas de seleção com estilo. Então, criei uma extensão auxiliar de HTML para colocar a entrada oculta fora da div que contém a caixa de seleção.

using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;

namespace YourNameSpace
{
    public static class HtmlHelperExtensions
    {
        public static MvcHtmlString CustomCheckBoxFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, string labelText)
        {
            //get the data from the model binding
            var fieldName = ExpressionHelper.GetExpressionText(expression);
            var fullBindingName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
            var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
            var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            var modelValue = metaData.Model;

            //create the checkbox
            TagBuilder checkbox = new TagBuilder("input");
            checkbox.MergeAttribute("type", "checkbox");
            checkbox.MergeAttribute("value", "true"); //the visible checkbox must always have true
            checkbox.MergeAttribute("name", fullBindingName);
            checkbox.MergeAttribute("id", fieldId);

            //is the checkbox checked
            bool isChecked = false;
            if (modelValue != null)
            {
                bool.TryParse(modelValue.ToString(), out isChecked);
            }
            if (isChecked)
            {
                checkbox.MergeAttribute("checked", "checked");
            }

            //add the validation
            checkbox.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(fieldId, metaData));

            //create the outer div
            var outerDiv = new TagBuilder("div");
            outerDiv.AddCssClass("checkbox-container");

            //create the label in the outer div
            var label = new TagBuilder("label");
            label.MergeAttribute("for", fieldId);
            label.AddCssClass("checkbox");

            //render the control
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(outerDiv.ToString(TagRenderMode.StartTag));
            sb.AppendLine(checkbox.ToString(TagRenderMode.SelfClosing));
            sb.AppendLine(label.ToString(TagRenderMode.StartTag));
            sb.AppendLine(labelText); //the label
            sb.AppendLine("<svg width=\"10\" height=\"10\" class=\"icon-check\"><use xlink:href=\"/icons.svg#check\"></use></svg>"); //optional icon
            sb.AppendLine(label.ToString(TagRenderMode.EndTag));
            sb.AppendLine(outerDiv.ToString(TagRenderMode.EndTag));

            //create the extra hidden input needed by MVC outside the div
            TagBuilder hiddenCheckbox = new TagBuilder("input");
            hiddenCheckbox.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
            hiddenCheckbox.MergeAttribute("name", fullBindingName);
            hiddenCheckbox.MergeAttribute("value", "false");
            sb.Append(hiddenCheckbox.ToString(TagRenderMode.SelfClosing));

            //return the custom checkbox
            return MvcHtmlString.Create(sb.ToString());
        }

Resultado

<div class="checkbox-container">
    <input checked="checked" id="Model_myCheckBox" name="Model.myCheckBox" type="checkbox" value="true">
    <label class="checkbox" for="Model_myCheckBox">
        The checkbox label
        <svg width="10" height="10" class="icon-check"><use xlink:href="/icons.svg#check"></use></svg>
    </label>
</div>
<input name="Model.myCheckBox" type="hidden" value="false">
VDWWD
fonte
0

Isso não é um bug! Acrescenta a possibilidade de sempre ter um valor, depois de postar o formulário no servidor. Se você quiser lidar com os campos de entrada da caixa de seleção com jQuery, use o método prop (passe a propriedade 'marcada' como parâmetro). Exemplo:$('#id').prop('checked')

BlueKore
fonte
0

Você pode tentar inicializar o construtor do seu modelo assim:

public MemberFormModel() {
    foo = true;
}

e na sua opinião:

@html.Checkbox(...)
@html.Hidden(...)
Saïd Mezhoud
fonte
0

Descobri que isso realmente causava problemas quando eu tinha um WebGrid. Os links de classificação no WebGrid girariam pela string de consulta dobrada ou x = true & x = false em x = true, false e causariam um erro de análise na caixa de seleção.

Acabei usando o jQuery para excluir os campos ocultos no lado do cliente:

    <script type="text/javascript">
    $(function () {
        // delete extra hidden fields created by checkboxes as the grid links mess this up by doubling the querystring parameters
        $("input[type='hidden'][name='x']").remove();
    });
    </script>
Matthew Lock
fonte