Warum rendert die CheckBoxFor ein zusätzliches Eingabe-Tag und wie kann ich den Wert mithilfe der FormCollection abrufen?

130

In meiner ASP.NET MVC-App rendere ich ein Kontrollkästchen mit dem folgenden Code:

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

Jetzt sehe ich, dass dies sowohl das Checkbox-Eingabe-Tag als auch ein verstecktes Eingabe-Tag rendert . Das Problem, das ich habe, ist, wenn ich versuche, den Wert mithilfe der FormCollection aus dem Kontrollkästchen abzurufen:

FormValues["ReceiveRSVPNotifications"]

Ich bekomme den Wert "wahr, falsch". Wenn ich mir das gerenderte HTML ansehe, sehe ich Folgendes:

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

Die FormValues-Auflistung scheint diese beiden Werte zu verbinden, da sie denselben Namen haben.

Irgendwelche Ideen?

Webcognoscere
quelle

Antworten:

168

Schauen Sie hier:

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

Dies ist kein Fehler und in der Tat der gleiche Ansatz, den sowohl Ruby on Rails als auch MonoRail verwenden.

Wenn Sie ein Formular mit einem Kontrollkästchen senden, wird der Wert nur gebucht, wenn das Kontrollkästchen aktiviert ist. Wenn Sie das Kontrollkästchen nicht aktivieren, wird nichts an den Server gesendet, wenn in vielen Situationen stattdessen false gesendet werden soll. Da die versteckte Eingabe denselben Namen wie das Kontrollkästchen hat, wird beim Deaktivieren des Kontrollkästchens immer noch ein "Falsch" an den Server gesendet.

Wenn das Kontrollkästchen aktiviert ist, übernimmt der ModelBinder automatisch das Extrahieren von 'true' aus 'true, false'.

Robert Harvey
quelle
10
Das ist eine gute Erklärung, aber in einigen Fällen möchten Sie möglicherweise 'nichts' in der Abfragezeichenfolge erhalten, wenn die ckecbox nicht aktiviert ist - also das Standardverhalten. Ist dies mit dem HTML-Helfer möglich oder muss ich einfach das <input>Tag verwenden.
Maksymilian Majer
13
Ich mag das nicht ... Ich habe eine Suchseite, die GET verwendet und jetzt ist meine URL voll von wahren / falschen Parametern ...
Shawn Mclean
1
Wow, das ist unerwartet. Bedeutet dies, dass das HTML-Kontrollkästchen tatsächlich "defekt" ist?
Isantipov
1
@Isantipov: Nein, dies bedeutet nur, dass diese Web-Frameworks nicht auf das Fehlen eines veröffentlichten Werts angewiesen sind, den ein Kontrollkästchen anzeigen kann false. Schauen Sie sich die Antwort von RyanJMcGowan unten an: "Durch das Senden einer versteckten Eingabe können Sie
Robert Harvey
1
Nun @ Robert, das funktioniert bei mir nicht. Wenn meine chbox MyCheckbox nicht aktiviert ist, wird false angezeigt. Wenn die MyCheckbox aktiviert ist , wird ein Array mit dem Wert [true, false] als Wert angezeigt . Ich serialisiere das gesamte Formular und übergebe es über Ajax an die Controller-Aktion, z. AddToOrder (MyModel-Modell), wo ich model.MyCheckbox = false statt true
erhalte
18

Ich hatte das gleiche Problem wie Shawn (oben). Dieser Ansatz mag für POST großartig sein, aber für GET ist er wirklich schlecht. Deshalb habe ich eine einfache HTML-Erweiterung implementiert, die nur das versteckte Feld auspeitscht.

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

Das Problem, das ich jetzt habe, ist, dass ich nicht möchte, dass eine Änderung am MVC-Framework meinen Code beschädigt. Ich muss also sicherstellen, dass ich eine Testabdeckung habe, die diesen neuen Vertrag erklärt.

Chris Kemp
quelle
5
Fühlen Sie sich nicht ein bisschen schmutzig, dass Sie Regex verwendet haben, um die versteckte Eingabe abzugleichen / zu entfernen, anstatt nur eine Checkbox-Eingabe aufzubauen? :)
Tim Hobbs
2
Nein, ich habe es getan, um das bestehende Verhalten zu ändern, anstatt mein eigenes neu zu implementieren. Auf diese Weise würde meine Änderung mit allen Änderungen Schritt halten, die MS eingeführt hat.
Chris Kemp
10
Nur eine kleine Beobachtung. Ihre Implementierung rendert keine benutzerdefinierten Attribute, die übergeben werden. Der Parameter "htmlAttributes" wird nicht an die ursprüngliche "CheckBoxFor" -Methode weitergegeben.
Emil Lundin
16

Ich verwende diese alternative Methode, um die Kontrollkästchen für GET-Formulare zu rendern:

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

Es ähnelt der Methode von Chris Kemp , die gut funktioniert, außer dass diese nicht das zugrunde liegende CheckBoxForund verwendet Regex.Replace. Es basiert auf der Quelle der ursprünglichen Html.CheckBoxForMethode.

Tom Pažourek
quelle
Dies ist absolut die beste Lösung für dieses Problem und sollte Teil des MVC-Frameworks sein ... es funktioniert perfekt. Ich bin mir nicht sicher, warum Ihre Antwort nicht mehr Beachtung gefunden hat ... danke!
user2315985
@ user2315985: Ich habe es etwas zu spät gepostet;) das ist der Grund.
Tom Pažourek
Wie gehen Sie mit Sammlungseigenschaften um? Der Standardmodellbinder beendet die Bindungswerte, wenn er auf eine Lücke in gebundenen Indizes stößt. Es scheint also, als würden Sie Ihre Erweiterung verwenden. Wenn beispielsweise der erste und der letzte Wert überprüft wurden, wissen Sie nie, welchen zweiten Wert Sie erhalten sollten . Oder fehlt mir etwas?
Tieson T.
@ TiesonT. Wenn Sie keinen eigenen Modellordner schreiben möchten, besteht die einzige Lösung darin, die Lücken zu schließen. Diese Methode sendet nicht false für nicht aktivierte Kontrollkästchen. Daher möchten Sie möglicherweise die ursprüngliche Html.CheckBoxFor-Methode verwenden, die dies tut.
Tom Pažourek
10

Hier ist der Quellcode für das zusätzliche Eingabe-Tag. Microsoft war so freundlich, Kommentare aufzunehmen, die genau darauf eingehen.

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

Ich denke, dass die einfachste Lösung darin besteht, das INPUT-Element direkt wie folgt zu rendern:

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

In der Razor-Syntax ist dies sogar noch einfacher, da das Attribut "geprüft" direkt mit einem "geprüften" Wert gerendert wird, wenn ein "wahrer" serverseitiger Wert angegeben wird.

Grammophon
quelle