Dlaczego CheckBoxFor renderuje dodatkowy tag wejściowy i jak mogę uzyskać wartość przy użyciu FormCollection?

130

W mojej aplikacji ASP.NET MVC renderuję pole wyboru przy użyciu następującego kodu:

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

Teraz widzę, że renderuje to zarówno znacznik wejściowy pola wyboru, jak i ukryty znacznik wejściowy. Problem, który mam, polega na tym, że próbuję pobrać wartość z pola wyboru przy użyciu FormCollection:

FormValues["ReceiveRSVPNotifications"]

Otrzymuję wartość „prawda, fałsz”. Patrząc na wyrenderowany kod HTML, widzę:

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

Wydaje się więc, że kolekcja FormValues ​​łączy te dwie wartości, ponieważ mają one tę samą nazwę.

Jakieś pomysły?

Webcognoscere
źródło

Odpowiedzi:

168

Zajrzyj tutaj:

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

To nie jest błąd, aw rzeczywistości jest to to samo podejście, którego używają zarówno Ruby on Rails, jak i MonoRail.

Gdy przesyłasz formularz z polem wyboru, wartość jest księgowana tylko wtedy, gdy pole wyboru jest zaznaczone. Tak więc, jeśli pozostawisz to pole wyboru niezaznaczone, nic nie zostanie wysłane na serwer, gdy w wielu sytuacjach chciałbyś zamiast tego wysłać fałsz. Ponieważ ukryte dane wejściowe mają taką samą nazwę jak pole wyboru, to jeśli to pole wyboru nie jest zaznaczone, nadal będzie wysyłane do serwera „fałsz”.

Gdy pole wyboru jest zaznaczone, ModelBinder automatycznie zajmie się wyodrębnieniem „prawda” z „prawda, fałsz”

Robert Harvey
źródło
10
To dobre wyjaśnienie, ale w niektórych przypadkach możesz chcieć uzyskać „nic” w ciągu zapytania, gdy ckecbox nie jest zaznaczony - więc jest to zachowanie domyślne. Czy jest to możliwe przy użyciu pomocnika Html, czy po prostu muszę użyć <input>tagu.
Maksymilian Majer
13
Nie podoba mi się to .... Mam stronę wyszukiwania, która używa GET i teraz mój adres URL jest pełen parametrów prawda / fałsz ...
Shawn Mclean
1
Wow, to nieoczekiwane. Czy to oznacza, że ​​pole wyboru HTML jest faktycznie „zepsute”?
Isantipov
1
@Isantipov: Nie, to po prostu oznacza, że ​​te frameworki internetowe nie opierają się na braku opublikowanej wartości w polu wyboru do wskazania false. Spójrz na odpowiedź RyanJMcGowana poniżej: „Wysłanie ukrytych danych wejściowych pozwala zorientować się, że pole wyboru było obecne na stronie w momencie przesłania żądania”.
Robert Harvey
1
Cóż, @Robert, to nie działa dla mnie. Jeśli moja chbox MyCheckbox nie jest zaznaczona, otrzymuję false, gdy zaznaczone jest MyCheckbox , otrzymuję tablicę [true, false] jako jego wartość. Serializuję cały formularz i przekazuję go przez ajax do akcji kontrolera np. AddToOrder (model MyModel), gdzie otrzymuję model.MyCheckbox = false zamiast true
nickornotto
18

Miałem ten sam problem co Shawn (powyżej). To podejście może być świetne w przypadku POST, ale naprawdę jest do bani dla GET. Dlatego zaimplementowałem proste rozszerzenie Html, które po prostu usuwa ukryte pole.

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

Problem, który mam teraz, polega na tym, że nie chcę, aby zmiana struktury MVC zepsuła mój kod. Muszę więc upewnić się, że mam pokrycie testowe wyjaśniające tę nową umowę.

Chris Kemp
źródło
5
Czy nie czujesz się trochę brudny, że użyłeś wyrażenia regularnego do dopasowania / usunięcia ukrytych danych wejściowych, zamiast po prostu budować tylko pole wyboru? :)
Tim Hobbs
2
Nie, zrobiłem to po to, aby zmieniać istniejące zachowanie, zamiast ponownie wdrażać własne. W ten sposób moja zmiana nadążałaby za wszelkimi zmianami wprowadzonymi przez MS.
Chris Kemp
10
Tylko mała obserwacja. Twoja implementacja nie wyrenderuje żadnych przekazanych atrybutów niestandardowych. Parametr „htmlAttributes” nie jest przekazywany do oryginalnej metody „CheckBoxFor”.
Emil Lundin
16

Używam tej alternatywnej metody do renderowania pól wyboru dla formularzy 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);
}

Jest podobna do metody Chrisa Kempa , która działa dobrze, z wyjątkiem tego, że ta nie wykorzystuje bazowego CheckBoxFori Regex.Replace. Opiera się na źródle oryginalnej Html.CheckBoxFormetody.

Tom Pažourek
źródło
To absolutnie najlepsze rozwiązanie tego problemu i powinno być częścią frameworka MVC ... działa idealnie. Nie jestem pewien, dlaczego twoja odpowiedź nie wzbudziła większego zainteresowania ... dzięki!
user2315985
@ user2315985: Wysłałem to trochę za późno;) to jest powód.
Tom Pažourek
Jak zarządzasz właściwościami kolekcji? Domyślny spinacz modelu przestanie wiązać wartości, gdy napotka lukę w powiązanych indeksach, więc wygląda na to, że używasz twojego rozszerzenia, jeśli, powiedzmy, sprawdzono pierwszą i ostatnią wartość, nigdy nie wiesz o drugiej wartości, którą powinieneś otrzymać . A może coś mi brakuje?
Tieson T.
@TiesonT. Jeśli nie chcesz napisać własnego segregatora modelu, jedynym rozwiązaniem jest uniknięcie luk. Ta metoda nie wysyła wartości false dla niezaznaczonych pól wyboru, więc możesz chcieć użyć oryginalnej metody Html.CheckBoxFor, która to robi.
Tom Pažourek
10

Oto kod źródłowy dodatkowego tagu wejściowego. Microsoft był na tyle uprzejmy, że zamieścił komentarze, które dokładnie dotyczą tego zagadnienia.

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
źródło
9

Myślę, że najprostszym rozwiązaniem jest wyrenderowanie elementu INPUT bezpośrednio w następujący sposób:

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

W składni Razor jest to jeszcze prostsze, ponieważ atrybut „zaznaczony” jest bezpośrednio renderowany z wartością „zaznaczoną”, gdy ma wartość „prawdziwą” po stronie serwera.

gramofon
źródło