Perché CheckBoxFor esegue il rendering di un tag di input aggiuntivo e come posso ottenere il valore utilizzando FormCollection?


130

Nella mia app ASP.NET MVC, sto visualizzando una casella di controllo utilizzando il seguente codice:

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

Ora, vedo che questo rende sia il tag di input della casella di controllo che un tag di input nascosto. Il problema che sto riscontrando è quando provo a recuperare il valore dalla casella di controllo utilizzando FormCollection:

FormValues["ReceiveRSVPNotifications"]

Ottengo il valore "vero, falso". Quando guardo l'HTML renderizzato, posso vedere quanto segue:

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

Quindi la raccolta FormValues ​​sembra unire questi due valori poiché hanno lo stesso nome.

Qualche idea?

Risposte:


168

Dai un'occhiata qui:

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

Questo non è un bug ed è in effetti lo stesso approccio utilizzato sia da Ruby on Rails che da MonoRail.

Quando si invia un modulo con una casella di controllo, il valore viene registrato solo se la casella di controllo è selezionata. Quindi, se si lascia la casella deselezionata, nulla verrà inviato al server quando in molte situazioni si vorrebbe invece inviare false. Poiché l'input nascosto ha lo stesso nome della casella di controllo, quindi se la casella di controllo è deselezionata, verrà comunque inviato un "falso" al server.

Quando la casella è selezionata, ModelBinder si occuperà automaticamente di estrarre il "vero" dal "vero, falso"


10
Questa è una buona spiegazione, ma in alcuni casi potresti voler ottenere "niente" nella stringa di query quando la ckecbox non è selezionata, quindi il comportamento predefinito. È possibile utilizzando l'helper HTML o devo semplicemente usare il <input>tag.
Maksymilian Majer,

13
Non mi piace .... Ho una pagina di ricerca che utilizza GET e ora il mio URL è pieno di parametri vero / falso ...
Shawn Mclean,

1
Wow, è inaspettato. Questo significa che la casella di controllo HTML è effettivamente "non funzionante"?
Isantipov,

1
@Isantipov: No, significa solo che questi framework Web non si basano sull'assenza di un valore pubblicato per una casella di controllo da indicare false. Guarda la risposta di RyanJMcGowan di seguito: "L'invio di un input nascosto consente di sapere che la casella di controllo era presente sulla pagina al momento
Robert Harvey,

1
Bene @Robert, questo non funziona per me. Se il mio chbox MyCheckbox non è selezionato, allora ottengo false, quando MyCheckbox è selezionato, ottengo un array di [true, false] come valore. Sto serializzando l'intero modulo e lo passo attraverso Ajax all'azione del controller, ad es. AddToOrder (modello MyModel) dove ottengo model.MyCheckbox = false anziché true
nickornotto

18

Ho avuto lo stesso problema di Shawn (sopra). Questo approccio potrebbe essere ottimo per POST, ma fa davvero schifo per GET. Pertanto ho implementato una semplice estensione Html che genera semplicemente il campo nascosto.

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

Il problema che ho ora è che non voglio che una modifica al framework MVC rompa il mio codice. Quindi devo assicurarmi di avere una copertura di prova che spieghi questo nuovo contratto.


5
Non ti senti un po 'sporco quando hai usato regex per abbinare / rimuovere l'input nascosto invece di creare solo un input checkbox? :)
Tim Hobbs,

2
No, l'ho fatto in modo da modificare il comportamento esistente, piuttosto che implementare nuovamente il mio. In questo modo il mio cambiamento starebbe al passo con eventuali cambiamenti introdotti dalla SM.
Chris Kemp,

10
Solo una piccola osservazione. L'implementazione non renderà alcun attributo personalizzato che viene passato. Il parametro "htmlAttributes" non viene passato al metodo "CheckBoxFor" originale.
Emil Lundin,

16

Uso questo metodo alternativo per visualizzare le caselle di controllo per i moduli 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);
}

È simile al metodo di Chris Kemp , che funziona benissimo, tranne per il fatto che questo non usa il sottostante CheckBoxFore Regex.Replace. Si basa sulla fonte del Html.CheckBoxFormetodo originale .


Questa è assolutamente la migliore soluzione a questo problema e dovrebbe far parte del framework MVC ... funziona perfettamente. Non sono sicuro del motivo per cui la tua risposta non ha ricevuto più attenzione ... grazie!
user2315985

@ user2315985: l'ho pubblicato un po 'troppo tardi;) questo è il motivo.
Tom Pažourek,

Come gestite le proprietà della raccolta? Il raccoglitore del modello predefinito interromperà i valori di associazione quando incontra uno spazio negli indici associati, quindi sembra che tu usi l'estensione, se, diciamo, il primo e l'ultimo valore sono stati controllati, non sapresti mai del secondo valore che dovresti ricevere . Oppure mi sfugge qualcosa?
Tieson T.

@TiesonT. A meno che non si desideri scrivere il proprio raccoglitore di modelli, l'unica soluzione è quella di prevenire le lacune. Questo metodo non invia false per le caselle di controllo non selezionate, quindi potresti voler utilizzare il metodo Html.CheckBoxFor originale che lo fa.
Tom Pažourek,

10

Ecco il codice sorgente per il tag di input aggiuntivo. Microsoft è stata così gentile da includere commenti che affrontano esattamente questo.

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

9

Penso che la soluzione più semplice sia rendere direttamente l'elemento INPUT come segue:

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

Nella sintassi di Razor è ancora più semplice, poiché l'attributo 'verificato' viene reso direttamente con un valore "controllato" quando viene dato un valore "vero" sul lato server.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.