Como lidar com caixas de seleção em formulários ASP.NET MVC?

246

Cuidado: Esta pergunta tem mais de nove anos!

Sua melhor opção é procurar por perguntas mais recentes ou procurar as respostas abaixo procurando sua versão específica do MVC, pois muitas respostas aqui estão obsoletas agora.

Se você encontrar uma resposta que funcione para sua versão, verifique se a resposta contém a versão do MVC que você está usando.
(A pergunta original começa abaixo)


Isso me parece um pouco bizarro, mas até onde eu sei, é assim que você faz.

Eu tenho uma coleção de objetos e quero que os usuários selecionem um ou mais deles. Isso me diz "formulário com caixas de seleção". Meus objetos não têm nenhum conceito de "selecionado" (são POCOs rudimentares formados pela desserialização de uma chamada wcf). Então, eu faço o seguinte:

public class SampleObject{
  public Guid Id {get;set;}
  public string Name {get;set;}
}

Na visualização:

<%
    using (Html.BeginForm())
    {
%>
  <%foreach (var o in ViewData.Model) {%>
    <%=Html.CheckBox(o.Id)%>&nbsp;<%= o.Name %>
  <%}%>
  <input type="submit" value="Submit" />
<%}%>

E, no controlador, é a única maneira que posso ver para descobrir quais objetos o usuário verificou:

public ActionResult ThisLooksWeird(FormCollection result)
{
  var winnars = from x in result.AllKeys
          where result[x] != "false"
          select x;
  // yadda
}

É esquisito em primeiro lugar e, em segundo lugar, para os itens que o usuário verificou, o FormCollection lista seu valor como "verdadeiro falso" e não apenas verdadeiro.

Obviamente, estou perdendo alguma coisa. Eu acho que isso é construído com a idéia de que os objetos na coleção que são UpdateModel()acionados no formulário html são atualizados usando ou através de um ModelBinder.

Mas meus objetos não estão configurados para isso; isso significa que este é o único caminho? tem outro jeito de fazer isto?

Uwe Keim
fonte
2
Outros podem achar esta solução útil: stackoverflow.com/questions/3291501/…
Aaron

Respostas:

262

Html.CheckBox está fazendo algo estranho - se você visualizar a fonte na página resultante, verá que está <input type="hidden" />sendo gerado ao lado de cada caixa de seleção, o que explica os valores "true false" que você está vendo para cada elemento do formulário.

Tente isso, que definitivamente funciona no ASP.NET MVC Beta, porque eu apenas tentei.

Coloque isso na exibição em vez de usar Html.CheckBox ():

<% using (Html.BeginForm("ShowData", "Home")) {  %>
  <% foreach (var o in ViewData.Model) { %>
    <input type="checkbox" name="selectedObjects" value="<%=o.Id%>">
    <%= o.Name %>
  <%}%>
  <input type="submit" value="Submit" />
<%}%>

Suas caixas de seleção são todas chamadas selectedObjectse asvalue de cada caixa de seleção é o GUID do objeto correspondente.

Poste na ação do controlador a seguir (ou algo semelhante que faça algo útil em vez de Response.Write ())

public ActionResult ShowData(Guid[] selectedObjects) {
    foreach (Guid guid in selectedObjects) {
        Response.Write(guid.ToString());
    }
    Response.End();
    return (new EmptyResult());
}

Este exemplo escreverá apenas os GUIDs das caixas marcadas; O ASP.NET MVC mapeia os valores GUID das caixas de seleção selecionadas no Guid[] selectedObjectsparâmetro para você e até analisa as seqüências de caracteres da coleção Request.Form em objetos GUID instantâneos, o que eu acho bastante agradável.

Dylan Beattie
fonte
Sim! Isso é o que eu tive que fazer pelos meus aplicativos também!
Adhip Gupta 21/10/08
70
wtf. campos de entrada ocultos com o mesmo nome como o controle. seu ViewState 2.0!
Simon_Weaver
3
Isso também está presente na versão 1.0. Veja a resposta que @ andrea-balducci enviou que oferece uma maneira inteligente de lidar com isso. Se a caixa de seleção não estiver marcada, o texto resultante recuperado deve ser 'falso falso' - é uma boa solução alternativa para explicar os navegadores com morte cerebral ...
Bryan Rehbein
77
Surpresa? Wtf? A entrada oculta tem o mesmo nome da caixa de seleção - se a caixa de seleção com o mesmo nome não estiver marcada, seu valor não será publicado, enquanto o valor da oculta será publicado. Na primeira vez em que o navegador encontrar um elemento nomeado, ele usará esse valor e ignorará todos os outros elementos com o mesmo nome. Isso garante que um valor seja enviado: true se a caixa de seleção estiver marcada (supondo que ela seja encontrada acima do elemento oculto) e false se a caixa de seleção estiver desmarcada (a caixa de seleção vazia é ignorada e o oculto se torna o retorno). O verdadeiro wtf é por que ninguém mais apontou isso.
Nerraga 17/08/09
19
@nerraga: Você está errado: é válido html ter vários elementos de formulário com o mesmo nome; Nesse caso, todos os elementos são publicados e não tenho certeza de que o pedido esteja realmente definido (embora seja provável que esteja na ordem das páginas na prática). Usar a palavra "false" como o valor é um pouco enganador, então sim, é um WTF - uma opção melhor e menos enganosa seria algo como "existe" ou algum sinal sem sentido como um traço.
Eamon Nerbonne
95

O HtmlHelper adiciona uma entrada oculta para notificar o controlador sobre o status Não verificado. Portanto, para ter o status verificado correto:

bool bChecked = form[key].Contains("true");
Andrea Balducci
fonte
1
Essa é a abordagem mais fácil para esse problema e é o que eu sempre uso. Ele permite que você use os métodos auxiliares e considere o comportamento dos MVCs do ASP.NET.
Nick Daniels
8
.. ou no caso de não marcado: bool bChecked = form [key] .Equals ("false"); A execução de um .Contains ("false") falha, pois o valor verdadeiro é "true, false".
Mark Robinson
54

Caso você esteja se perguntando POR QUE eles colocam um campo oculto com o mesmo nome da caixa de seleção, o motivo é o seguinte:

Comentário do código-fonte MVCBetaSource \ MVC \ src \ MvcFutures \ Mvc \ ButtonsAndLinkExtensions.cs

Renderize um adicional <input type="hidden".../>para caixas de seleção. Isso aborda cenários em que caixas de seleção desmarcadas não são enviadas na solicitação. 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.

Eu acho que nos bastidores eles precisam saber disso para vincular parâmetros nos métodos de ação do controlador. Suponho que você possa ter um booleano de três estados (vinculado a um parâmetro bool anulável). Eu não tentei, mas espero que seja o que eles fizeram.

Simon_Weaver
fonte
4
Sim, isso é útil em cenários em que você tem grades paginadas etc. e deseja desmarcar o item que foi selecionado anteriormente em algum objeto de negócios.
31411 RichardOD
49

Você também deve usar, <label for="checkbox1">Checkbox 1</label>pois as pessoas podem clicar no texto do rótulo e na própria caixa de seleção. Também é mais fácil de estilizar e, pelo menos no IE, será destacado quando você guia os controles da página.

<%= Html.CheckBox("cbNewColors", true) %><label for="cbNewColors">New colors</label>

Isso não é apenas uma coisa do tipo 'oh, eu poderia fazer'. É uma melhoria significativa na experiência do usuário. Mesmo que nem todos os usuários saibam que podem clicar no rótulo, muitos o farão.

Simon_Weaver
fonte
27

Estou surpreso que nenhuma dessas respostas tenha usado os recursos MVC integrados para isso.

Escrevi um post sobre isso aqui , que até vincula os rótulos à caixa de seleção. Usei a pasta EditorTemplate para fazer isso de maneira limpa e modular.

Você simplesmente terminará com um novo arquivo na pasta EditorTemplate que se parece com isso:

@model SampleObject

@Html.CheckBoxFor(m => m.IsChecked)
@Html.HiddenFor(m => m.Id)
@Html.LabelFor(m => m.IsChecked, Model.Id)

na sua visão atual, não haverá necessidade de fazer um loop, simplesmente uma linha de código:

@Html.EditorFor(x => ViewData.Model)

Visite meu blog para obter mais detalhes.

Shawn Mclean
fonte
1
Obrigado! Everboyd ainda está usando as formas antigas. E quando você envia, pode acessar o Model sem toda essa coleção [] e request.form garbage - Era o que eu estava procurando. +1e + cerveja
Piotr Kula
2
Essa deve ser a melhor resposta para a pergunta. Muito simples e fácil de implementar.
Johan Gunawan
Lutou por algum tempo para encontrar uma solução elegante para isso antes de encontrar esta resposta. Ainda tem alguns anos, mas ainda é perfeitamente válido em 2016! Se estiver usando em várias instâncias, use o SelectListItem interno como um tipo genérico que é perfeitamente adequado para isso
Phil
SampleObject é uma lista? Por exemplo: @ModelType List (Of Type)
JoshYates1980
25

Aqui está o que eu tenho feito.

Visão:


<input type="checkbox" name="applyChanges" />

Controlador:


var checkBox = Request.Form["applyChanges"];

if (checkBox == "on")
{
...
}

Eu achei os métodos auxiliares Html. * Não tão úteis em alguns casos, e era melhor fazê-lo em HTML simples e antigo. Sendo um deles, o outro que vem à mente são os botões de opção.

Edit: isso está na visualização 5, obviamente YMMV entre as versões.

mmacaulay
fonte
1
Eu só quero adicionar um exemplo alternativo: Object.Property =! String.IsNullOrEmpty (Request.Form ["NAME"]);
Gup3rSuR4c 5/05
se não for controlada, em seguida, ele vai para exceção
Xulfee
10

Eles parecem optar por ler apenas o primeiro valor, portanto, isso é "verdadeiro" quando a caixa de seleção está marcada e "falso" quando apenas o valor oculto é incluído. Isso é facilmente buscado com código como este:

model.Property = collection["ElementId"].ToLower().StartsWith("true");
Fofo
fonte
8

@Dylan Beattie Great Find !!! Eu agradeço muito. Para expandir ainda mais, essa técnica também funciona perfeitamente com a abordagem Exibir modelo. O MVC é tão legal que é inteligente o suficiente vincular uma matriz de Guids a uma propriedade com o mesmo nome do objeto Model vinculado à View. Exemplo:

ViewModel:

public class SampleViewModel
{
    public IList<SampleObject> SampleObjectList { get; set; }
    public Guid[] SelectedObjectIds { get; set; }

    public class SampleObject
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

Visão:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Sample View</h2>
<table>
    <thead> 
        <tr>
            <th>Checked</th>
            <th>Object Name</th>
        </tr>
    </thead> 
<% using (Html.BeginForm()) %>
<%{%>                    
    <tbody>
        <% foreach (var item in Model.SampleObjectList)
           { %>
            <tr>
                <td><input type="checkbox" name="SelectedObjectIds" value="<%= item.Id%>" /></td>
                <td><%= Html.Encode(item.Name)%></td>
            </tr>
        <% } %>
    </tbody>
</table>
<input type="submit" value="Submit" />
<%}%>                    

Controlador:

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult SampleView(Guid id)
    {
        //Object to pass any input objects to the View Model Builder 
        BuilderIO viewModelBuilderInput = new BuilderIO();

        //The View Model Builder is a conglomerate of repositories and methods used to Construct a View Model out of Business Objects
        SampleViewModel viewModel = sampleViewModelBuilder.Build(viewModelBuilderInput);

        return View("SampleView", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SampleView(SampleViewModel viewModel)
    {
        // The array of Guids successfully bound to the SelectedObjectIds property of the View Model!
        return View();
    }

Qualquer pessoa familiarizada com a filosofia do View Model se alegra, isso funciona como um campeão!

nautic20
fonte
Obrigado, seu SampleViewModel esclareceu algumas confusões na resposta original.
parlamento
6

Gostaria também de ressaltar que você pode nomear cada caixa de seleção com um nome diferente e fazer com que esse nome faça parte dos parâmetros de resultados da ação.

Exemplo,

Visão:

 <%= Html.CheckBox("Rs232CheckBox", false, new { @id = "rs232" })%>RS-232

 <%= Html.CheckBox("Rs422CheckBox", false, new { @id = "rs422" })%>RS-422

Controlador:

public ActionResults MyAction(bool Rs232CheckBox, bool Rs422CheckBox) {
    ...
}

Os valores da visualização são transmitidos para a ação, pois os nomes são os mesmos.

Sei que essa solução não é ideal para o seu projeto, mas pensei em lançar a idéia lá fora.

Darcy
fonte
5
<input type = "checkbox" name = "checkbox1" /> <label> Check to say hi.</label>

Do controlador:

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Index(FormCollection fc)
    {

         var s = fc["checkbox1"];

         if (s == "on")
         {
             string x = "Hi";
         }
    }
bluwater2001
fonte
4

Esse problema também está acontecendo no release 1.0. Html.Checkbox () faz com que outro campo oculto seja adicionado com o mesmo nome / ID da sua caixa de seleção original. E enquanto eu tentava carregar uma matriz de caixas de seleção usando document.GetElemtentsByName (), você pode adivinhar como as coisas estavam ficando complicadas. Isso é bizarro.

Farhan Zia
fonte
4

Pelo que pude entender, o modelo não quer adivinhar se marcado = verdadeiro ou falso, resolvi isso definindo um atributo de valor no elemento checkbox com o jQuery antes de enviar o formulário da seguinte maneira:

 $('input[type="checkbox"]').each(function () {
       $(this).attr('value', $(this).is(':checked'));
 }); 

Dessa forma, você não precisa de um elemento oculto apenas para armazenar o valor da caixa de seleção.

BraveNewMath
fonte
4

Sei que essa pergunta foi escrita quando o MVC3 não estava disponível, mas para quem chega a essa pergunta e está usando o MVC3, convém a maneira "correta" de fazer isso.

Enquanto eu acho que fazer o todo

Contains("true");

Se tudo é ótimo e limpo, e funciona em todas as versões do MVC, o problema é que não leva em consideração a cultura (como se realmente importasse no caso de um bool).

A maneira "correta" de descobrir o valor de um bool, pelo menos no MVC3, é usar o ValueProvider.

var value = (bool)ValueProvider.GetValue("key").ConvertTo(typeof(bool));

Faço isso em um dos sites do meu cliente quando edito as permissões:

var allPermissionsBase = Request.Params.AllKeys.Where(x => x.Contains("permission_")).ToList();
var allPermissions = new List<KeyValuePair<int, bool>>();

foreach (var key in allPermissionsBase)
{
     // Try to parse the key as int
     int keyAsInt;
     int.TryParse(key.Replace("permission_", ""), out keyAsInt);

     // Try to get the value as bool
     var value = (bool)ValueProvider.GetValue(key).ConvertTo(typeof(bool));
}

Agora, a beleza disso é que você pode usá-lo com praticamente qualquer tipo simples, e até estará correto com base na Cultura (pense em dinheiro, decimais, etc.).

O ValueProvider é o que é usado quando você forma suas ações assim:

public ActionResult UpdatePermissions(bool permission_1, bool permission_2)

mas quando você estiver tentando criar dinamicamente essas listas e verificar os valores, nunca conhecerá o ID em tempo de compilação; portanto, é necessário processá-los rapidamente.

Dan VanWinkle
fonte
existe um motivo para você usar Request.Params em vez de adicionar um parâmetro FormCollection ao método?
21312 SwissCoder
Não há razão, realmente não vejo nenhum benefício de uma maneira sobre a outra.
Dan VanWinkle
1
Desculpe, existe realmente uma razão. Apenas olhei para o meu código e estou usando esse método para 5 chamadas diferentes e não queria passar FormCollection de cada método. Essa era a maneira mais fácil de obter as chaves sem precisar passar nada.
Dan VanWinkle
Sim, eu sou novo no MVC. E li em algum lugar que, em geral, é melhor usar o FormCollection em vez de Request.Params, o mesmo que usar um Model digitado em vez de FormCollection (então, na verdade, seria melhor usar um modelo e evitar o Request.Params em geral). Notei que outras coisas do código não são usadas, como a variável allPermissions e o keyAsInt. Obrigado por responder.
SwissCoder 24/10/12
3

A maneira mais fácil de fazer é ...

Você define o nome e o valor.

<input type="checkbox" name="selectedProducts" value="@item.ProductId" />@item.Name

Em seguida, ao enviar, pegue os valores das caixas de seleção e salve em uma matriz int. então a função LinQ apropriada. É isso aí..

[HttpPost]
        public ActionResult Checkbox(int[] selectedObjects)
        {
            var selected = from x in selectedObjects
                           from y in db
                           where y.ObjectId == x
                           select y;                   

            return View(selected);
        }
kk-dev11
fonte
3

Igual à resposta de nautic20, basta usar a lista de caixas de seleção de ligação de modelo padrão do MVC com o mesmo nome que uma propriedade de coleção de string / int / enum no ViewModel. É isso.

Mas uma questão precisa apontar. Em cada componente da caixa de seleção, você não deve colocar "Id" nele, o que afetará a ligação do modelo MVC.

O código a seguir funcionará para a ligação de modelo:

 <% foreach (var item in Model.SampleObjectList)
       { %>
        <tr>
            <td><input type="checkbox" name="SelectedObjectIds" value="<%= item.Id%>" /></td>
            <td><%= Html.Encode(item.Name)%></td>
        </tr>
 <% } %>

Os códigos a seguir não serão vinculativos ao modelo (diferença aqui é o ID atribuído para cada caixa de seleção)

<% foreach (var item in Model.SampleObjectList)
       { %>
        <tr>
            <td><input type="checkbox" name="SelectedObjectIds" id="[some unique key]" value="<%= item.Id%>" /></td>
            <td><%= Html.Encode(item.Name)%></td>
        </tr>
<% } %>
ChinaHelloWorld
fonte
Obrigado por responder, mas esta pergunta é muito antiga. Seis anos, aproximadamente. Você pode editar e especificar qual versão do MVC você está usando. Isso ajudará qualquer pessoa que utilize uma versão mais recente a localizar esta resposta.
Eu concordo. Removendo a identificação única resolveu este problema após muita dor de cabeça e ranger de dentes
Ody
A versão do MVC que estou usando é .net MVC 4.0 para esse problema.
ChinaHelloWorld 10/06
2

isto é o que eu fiz para perder os valores duplos ao usar o Html.CheckBox (...

Replace("true,false","true").Split(',')

com 4 caixas marcadas, desmarcada, desmarcada, marcada, torna-se verdadeiro, falso, falso, falso, falso, verdadeiro, falso em verdadeiro, falso, falso, verdadeiro. exatamente o que eu precisava

Jeroen
fonte
0

Que tal algo como isso?

bool isChecked = false;
if (Boolean.TryParse(Request.Form.GetValues(”chkHuman”)[0], out isChecked) == false)
    ModelState.AddModelError(”chkHuman”, Nice try.”);
Skadoosh
fonte
0

Ao usar a caixa de seleção HtmlHelper, prefiro trabalhar com os dados do formulário da caixa de seleção postada como uma matriz. Eu realmente não sei por que, sei que os outros métodos funcionam, mas acho que prefiro tratar as seqüências separadas por vírgula como uma matriz, tanto quanto possível.

Portanto, fazer um teste 'marcado' ou verdadeiro seria:

//looking for [true],[false]
bool isChecked = form.GetValues(key).Contains("true"); 

Fazer uma verificação falsa seria:

//looking for [false],[false] or [false]
bool isNotChecked = !form.GetValues(key).Contains("true"); 

A principal diferença é usar, GetValuespois isso retorna como uma matriz.

eyesnz
fonte
0

Basta fazer isso em $(document).ready:

$('input:hidden').each(function(el) {
    var that = $(this)[0];
    if(that.id.length < 1 ) {

        console.log(that.id);
        that.parentElement.removeChild(that);

    }
});
doronAv
fonte
2
Infelizmente, você pode obter resultados estranhos no lado do servidor para pessoas com JavaScript desligado (NoScript plug-in, os telefones móveis velhos, Lynx ...)
Arick
0

Minha solução é:

<input type="checkbox"  id="IsNew-checkbox"  checked="checked" />     
<input type="hidden"  id="IsNew" name="IsNew" value="true"  />      
<script language="javascript" type="text/javascript" >     
  $('#IsNew-checkbox').click(function () {    
      if ($('#IsNew-checkbox').is(':checked')) {    
          $('#IsNew').val('true');    
      } else {    
          $('#IsNew').val('false');    
       }    
  });   
</script>  

Você pode encontrar mais aqui: http://www.blog.mieten.pl/2010/12/asp-net-mvc-custom-checkbox-as-solution-of-string-was-not-recognized-as-a- valid-boolean /

pawlom84
fonte
Você não precisa usar Javascript para isso ... var isChecked = formData [key] .Contains ("true");
Jammer
0

Eu tive quase o mesmo problema, mas o valor de retorno do meu controlador foi bloqueado com outros valores.

Encontrei uma solução simples, mas parece um pouco difícil.

Tente digitar o Viewbag.seu Controller e agora dê um nome comoViewbag.Checkbool

Agora mude para o modo de exibição e tente isso @Viewbag.Checkbool com isso, você obterá o valor do controlador.

Meus parâmetros de controlador são assim:

public ActionResult Anzeigen(int productid = 90, bool islive = true)

e minha caixa de seleção será atualizada assim:

<input id="isLive" type="checkbox" checked="@ViewBag.Value" ONCLICK="window.location.href = '/MixCategory/Anzeigen?isLive=' + isLive.checked.toString()" />
treboriano
fonte
0

Usando @mmacaulay, eu vim com isso para bool:

// MVC Work around for checkboxes.
bool active = (Request.Form["active"] == "on");

Se marcado ativo = verdadeiro

Se desmarcado, ativo = falso

Ravi Ram
fonte