Mengapa CheckBoxFor membuat tag input tambahan, dan bagaimana saya bisa mendapatkan nilai menggunakan FormCollection?

130

Di aplikasi ASP.NET MVC saya, saya merender kotak centang menggunakan kode berikut:

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

Sekarang, saya melihat bahwa ini menjadikan kedua tag masukan kotak centang dan tag masukan tersembunyi. Masalah yang saya alami adalah ketika saya mencoba mengambil nilai dari kotak centang menggunakan FormCollection:

FormValues["ReceiveRSVPNotifications"]

Saya mendapatkan nilai "benar, salah". Saat melihat HTML yang dirender, saya dapat melihat yang berikut:

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

Jadi koleksi FormValues ​​tampaknya menggabungkan kedua nilai ini karena mereka memiliki nama yang sama.

Ada Ide?

Webcognoscere
sumber

Jawaban:

168

Lihat di sini:

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

Ini bukan bug, dan sebenarnya pendekatan yang sama yang digunakan Ruby on Rails dan MonoRail.

Saat Anda mengirimkan formulir dengan kotak centang, nilainya hanya diposkan jika kotak centang dicentang. Jadi, jika Anda meninggalkan kotak centang tidak dicentang maka tidak ada yang akan dikirim ke server ketika dalam banyak situasi Anda ingin false dikirim sebagai gantinya. Karena input tersembunyi memiliki nama yang sama dengan kotak centang, maka jika kotak centang tidak dicentang Anda masih akan mendapatkan 'false' yang dikirim ke server.

Ketika kotak centang dicentang, ModelBinder akan secara otomatis menangani mengekstraksi 'benar' dari 'benar, salah'

Robert Harvey
sumber
10
Itu penjelasan yang baik tetapi dalam beberapa kasus Anda mungkin ingin mendapatkan 'tidak ada' di string kueri ketika kotak centang tidak dicentang - jadi perilaku default. Apakah ini mungkin menggunakan bantuan Html atau apakah saya harus cukup menggunakan <input>tag.
Maksymilian Majer
13
Saya tidak suka ini .... Saya memiliki halaman pencarian yang menggunakan GET dan sekarang url saya penuh dengan params benar / salah ...
Shawn Mclean
1
Wow, itu tidak terduga. Apakah ini berarti bahwa kotak centang HTML sebenarnya "rusak"?
Isantipov
1
@Isantipov: Tidak, itu hanya berarti bahwa kerangka kerja web ini tidak bergantung pada tidak adanya nilai yang diposting untuk dicentang false. Lihatlah jawaban RyanJMcGowan di bawah ini: "Mengirim input tersembunyi memungkinkan untuk mengetahui bahwa kotak centang ada pada halaman ketika permintaan diajukan."
Robert Harvey
1
Nah @Robert, ini tidak berhasil untuk saya. Jika chbox MyCheckbox saya tidak dicentang maka saya mendapatkan false, ketika MyCheckbox dicentang maka saya mendapatkan array [true, false] sebagai nilainya. Saya membuat serial seluruh bentuk dan meneruskannya melalui ajax ke aksi controller misalnya. AddToOrder (model MyModel) tempat saya mendapatkan model.MyCheckbox = false, bukan true
nickornotto
18

Saya memiliki masalah yang sama dengan Shawn (di atas). Pendekatan ini mungkin bagus untuk POST, tetapi sangat menyebalkan untuk GET. Oleh karena itu saya menerapkan ekstensi Html sederhana yang hanya mencabut bidang tersembunyi.

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

Masalah saya sekarang adalah bahwa saya tidak ingin perubahan pada kerangka kerja MVC untuk memecahkan kode saya. Jadi saya harus memastikan saya memiliki cakupan pengujian yang menjelaskan kontrak baru ini.

Chris Kemp
sumber
5
Apakah Anda tidak merasa kotor sedikit yang Anda gunakan regex untuk mencocokkan / menghapus masukan tersembunyi daripada hanya membangun hanya masukan kotak centang? :)
Tim Hobbs
2
Tidak, saya melakukannya sehingga saya mengubah perilaku yang ada, daripada menerapkan kembali perilaku saya sendiri. Dengan begitu perubahan saya akan mengimbangi perubahan apa pun yang diluncurkan MS.
Chris Kemp
10
Hanya pengamatan kecil. Implementasi Anda tidak akan membuat atribut khusus apa pun yang diteruskan. Parameter "htmlAttributes" tidak diteruskan ke metode "CheckBoxFor" asli.
Emil Lundin
16

Saya menggunakan metode alternatif ini untuk merender kotak centang untuk formulir 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);
}

Ini mirip dengan metode Chris Kemp , yang berfungsi dengan baik, kecuali yang ini tidak menggunakan yang mendasarinya CheckBoxFordan Regex.Replace. Ini didasarkan pada sumber Html.CheckBoxFormetode asli .

Tom Pažourek
sumber
Ini benar-benar solusi terbaik untuk masalah ini dan harus menjadi bagian dari kerangka kerja MVC ... ini bekerja dengan sempurna. Saya tidak yakin mengapa jawaban Anda belum mendapat perhatian lebih ... terima kasih!
user2315985
@ user2315985: Saya mempostingnya sedikit terlambat;) itulah alasannya.
Tom Pažourek
Bagaimana Anda menangani properti koleksi? Pengikat model default akan berhenti nilai pengikatan ketika menemukan celah dalam indeks terikat, jadi sepertinya menggunakan ekstensi Anda, jika, katakanlah, nilai pertama dan terakhir diperiksa, Anda tidak akan pernah tahu tentang nilai kedua yang harus Anda terima . Atau, apakah saya melewatkan sesuatu?
Tieson T.
@ TiesonT. Kecuali jika Anda ingin menulis binder model Anda sendiri, satu-satunya solusi adalah untuk mencegah kesenjangan. Metode ini tidak mengirim false untuk kotak centang tidak dicentang, jadi Anda mungkin ingin menggunakan metode Html.CheckBoxFor asli yang melakukan itu.
Tom Pažourek
10

Berikut adalah kode sumber untuk tag input tambahan. Microsoft cukup baik untuk memasukkan komentar yang membahas hal ini dengan tepat.

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

Saya pikir solusi paling sederhana adalah merender elemen INPUT secara langsung sebagai berikut:

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

Dalam sintaks Razor bahkan lebih mudah, karena atribut 'checked' langsung dirender dengan nilai "checked" ketika diberi nilai sisi server 'benar'.

grammophone
sumber