Por que o CheckBoxFor processa uma marca de entrada adicional e como posso obter o valor usando o FormCollection?

130

No meu aplicativo ASP.NET MVC, estou processando uma caixa de seleção usando o seguinte código:

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

Agora, vejo que isso torna tanto a tag de entrada caixa de seleção e uma marca de entrada escondida. O problema que estou tendo é quando tento recuperar o valor da caixa de seleção usando o FormCollection:

FormValues["ReceiveRSVPNotifications"]

Eu recebo o valor "verdadeiro, falso". Ao analisar o HTML renderizado, posso ver o seguinte:

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

Portanto, a coleção FormValues ​​parece unir esses dois valores, pois eles têm o mesmo nome.

Alguma ideia?

Webcognoscere
fonte

Respostas:

168

Dê uma olhada aqui:

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

Isso não é um bug, e é de fato a mesma abordagem usada pelo Ruby on Rails e MonoRail.

Quando você envia um formulário com uma caixa de seleção, o valor é lançado somente se a caixa de seleção estiver marcada. Portanto, se você deixar a caixa de seleção desmarcada, nada será enviado ao servidor quando, em muitas situações, você desejar que um falso seja enviado. Como a entrada oculta tem o mesmo nome que a caixa de seleção, se a caixa de seleção estiver desmarcada, você ainda receberá um 'falso' enviado ao servidor.

Quando a caixa de seleção estiver marcada, o ModelBinder cuidará automaticamente de extrair o 'true' do 'true, false'

Robert Harvey
fonte
10
Essa é uma boa explicação, mas em alguns casos você pode querer obter 'nada' na string de consulta quando a ckecbox não estiver marcada - portanto, o comportamento padrão. Isso é possível usando o auxiliar Html ou preciso simplesmente usar a <input>tag.
Maksymilian Majer
13
Eu não como este .... Eu tenho uma página de pesquisa que usa GET e agora a minha url está cheio de verdadeiro / falso params ...
Shawn Mclean
1
Uau, isso é inesperado. Isso significa que a caixa de seleção HTML está realmente "quebrada"?
Isantipov
1
@ Isantipov: Não, isso significa apenas que essas estruturas da Web não dependem da ausência de um valor postado para uma caixa de seleção indicar false. Veja a resposta de RyanJMcGowan abaixo: "O envio de uma entrada oculta torna possível saber que a caixa de seleção estava presente na página quando a solicitação foi enviada."
Robert Harvey
1
Bem, Robert, isso não funciona para mim. Se meu chbox MyCheckbox não estiver marcado, fico falso, quando o MyCheckbox está marcado, recebo uma matriz de [true, false] como seu valor. Estou serializando o formulário inteiro e passá-lo através do ajax para a ação do controlador, por exemplo. AddToOrder (modelo MyModel) onde eu recebo model.MyCheckbox = false em vez de verdade
nickornotto
18

Eu tive o mesmo problema que Shawn (acima). Essa abordagem pode ser ótima para o POST, mas realmente é péssima para o GET. Portanto, implementei uma extensão Html simples que apenas destaca o campo oculto.

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);
}

O problema que tenho agora é que não quero uma alteração na estrutura do MVC para quebrar meu código. Portanto, tenho que garantir que tenho cobertura de teste explicando este novo contrato.

Chris Kemp
fonte
5
Você não se sente um pouco sujo por ter usado regex para corresponder / remover a entrada oculta em vez de apenas criar apenas uma entrada da caixa de seleção? :)
Tim Hobbs
2
Não, eu fiz isso para corrigir o comportamento existente, em vez de reimplementar o meu. Dessa forma, minha alteração acompanharia todas as alterações lançadas pela MS.
Chris Kemp
10
Apenas uma pequena observação. Sua implementação não renderizará nenhum atributo personalizado passado. O parâmetro "htmlAttributes" não é passado para o método "CheckBoxFor" original.
Emil Lundin
16

Eu uso esse método alternativo para renderizar as caixas de seleção dos formulários 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);
}

É semelhante ao método de Chris Kemp , que está funcionando bem, exceto que este não usa o subjacente CheckBoxFore Regex.Replace. É baseado na fonte do Html.CheckBoxFormétodo original .

Tom Pažourek
fonte
Esta é absolutamente a melhor solução para esse problema e deve fazer parte da estrutura do MVC ... funciona perfeitamente. Não sei por que sua resposta não recebeu mais atenção ... obrigado!
user2315985
@ user2315985: Postei um pouco tarde demais;) esse é o motivo.
Tom Pažourek
Como você está lidando com as propriedades da coleção? O fichário do modelo padrão interromperá os valores de associação quando encontrar uma lacuna nos índices associados, portanto, parece que você usa sua extensão. Se, digamos, o primeiro e o último valor foram verificados, você nunca saberia sobre o segundo valor que deveria estar recebendo. . Ou eu estou esquecendo de alguma coisa?
Tieson T.
@TiesonT. A menos que você queira escrever seu próprio fichário de modelo, a única solução é evitar as lacunas. Esse método não envia false para caixas de seleção desmarcadas; portanto, convém usar o método Html.CheckBoxFor original que faz isso.
Tom Pažourek
10

Aqui está o código-fonte para a tag de entrada adicional. A Microsoft teve a gentileza de incluir comentários que abordam isso com precisão.

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
fonte
9

Eu acho que a solução mais simples é renderizar o elemento INPUT diretamente da seguinte maneira:

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

Na sintaxe do Razor, é ainda mais fácil, porque o atributo 'marcado' é renderizado diretamente com um valor "marcado" quando recebe um valor "verdadeiro" do lado do servidor.

grammophone
fonte