Search code examples
c#asp.net-mvchtml-helpercheckboxfor

CheckBoxFor not binding to model


My application works like this: A user chooses a project from a dropdown list and it loads a partial view. The controller action that returns the partial view pulls data from a database and populates the checkboxes in the partial view with the values that exist in the database. To do that, obviously the model is returned with the partial view. This is working just fine.

Now, if a user changes the value of the checkbox and submits the form again, I assumed the checkboxes' new values would be bound to the model and thus, I would be able to use it in updating the database with new values. Unforunately, everything is coming back as "false" even if the checkbox is checked. I do understand that if no action is taken on the checkbox it will submit a false value (even if it was pre-populated with a checkmark from an existing value in the db), I will concern myself with fixing that later, but I'm trying to figure out why the boxes that get actively checked are not binding to the model. I'm not sure what the deal is as my CheckBoxFor helpers look correct based on everything I've read.

Model

public class DataSharingModels
{
    public string ReferenceID { get; set; }
    public NBTC NBTCGroup { get; set; }
    public Contractors ContractorsGroup { get; set; }
    public Coordinators CoordinatorsGroup { get; set; }
    public NGO NGOGroup { get; set; }
    public Public PublicGroup { get; set; }
    public SelectList FA_RA_List { get; set; }

}

public class NBTC
{
    public Boolean NBTC_FA_Centroid { get; set; }
    public Boolean NBTC_FA_Bound { get; set; }
    public Boolean NBTC_RA_Centroid { get; set; }
    public Boolean NBTC_RA_Bound { get; set; }
    public Boolean NBTC_Spring_Sum { get; set; }
    public Boolean NBTC_Spring_Analysis { get; set; }
    public Boolean NBTC_Spring_Locate { get; set; }
    public Boolean NBTC_Fall_Sum { get; set; }
    public Boolean NBTC_Fall_Analysis { get; set; }
    public Boolean NBTC_Fall_Locate { get; set; }
    public Boolean NBTC_HabMon_Sum { get; set; }
    public Boolean NBTC_HabMon_Analysis { get; set; }
    public Boolean NBTC_HabMon_Locate { get; set; }
    public Boolean NBTC_HabMgmt_Sum { get; set; }
    public Boolean NBTC_HabMgmt_Analysis { get; set; }
    public Boolean NBTC_HabMgmt_Locate { get; set; }
    public Boolean NBTC_Inventory_Sum { get; set; }
    public Boolean NBTC_OpSvy_Sum { get; set; }
    public Boolean NBTC_OpSvy_Individ { get; set; }
}
//continues...

Partial View

@model xxx.Models.DataSharingModels

@using (Html.BeginForm("SetPermission", "DataSharing", FormMethod.Post, new { id = "dShareForm" }))
{
<table id="data-sharing-table">
    //a whole bunch of table set up
    <tr>
        <td rowspan="2">CIP Focal Area</td>
        <td>Centroid</td>
        <td><input type="checkbox" name="NBCIStaff" checked disabled /></td>
        <td>@Html.CheckBoxFor(m => m.NBTCGroup.NBTC_FA_Centroid, nbtcAttr)</td>
        <td>@Html.CheckBoxFor(m => m.ContractorsGroup.Contractors_FA_Centroid, contractAttr)</td>
        <td>@Html.CheckBoxFor(m => m.CoordinatorsGroup.Coordinators_FA_Centroid, coordAttr)</td>
        <td>@Html.CheckBoxFor(m => m.NGOGroup.NGO_FA_Centroid, ngoAttr)</td>
        <td>@Html.CheckBoxFor(m => m.PublicGroup.Public_FA_Centroid, publicAttr)</td>
    </tr>
    <tr>
        <td>Boundary</td>
        <td><input type="checkbox" name="NBCIStaff" checked disabled /></td>
        <td>@Html.CheckBoxFor(m => m.NBTCGroup.NBTC_FA_Bound, nbtcAttr)</td>
        <td>@Html.CheckBoxFor(m => m.ContractorsGroup.Contractors_FA_Bound, contractAttr)</td>
        <td>@Html.CheckBoxFor(m => m.CoordinatorsGroup.Coordinators_FA_Bound, coordAttr)</td>
        <td>@Html.CheckBoxFor(m => m.NGOGroup.NGO_FA_Bound, ngoAttr)</td>
        <td>@Html.CheckBoxFor(m => m.PublicGroup.Public_FA_Bound, publicAttr)</td>
    </tr>
    //it continues on like this
</table>
<button class="btn btn-default" type="submit">Submit</button>
}

Controller

[HttpPost]
public void SetPermission(DataSharingModels dsm)
{
    //do stuff
}

Solution

  • So it turns out the problem was with the variables I was attaching to add html attributes to my checkboxes. I had something like this in my view:

    nbtcAttr = new {@Name = "NBTC"}
    

    This overrides the name that gets generated using CheckBoxFor so instead of this:

    @Html.CheckBoxFor(m => m.NBTCGroup.NBTC_FA_Centroid) 
    //generates:
    //<input name="NBTCGroup.NBTC_FA_Centroid" data-val:"true" data-val-required:"The NBTC_FA_Centroid field is required" id="NBTCGroup_NBTC_FA_Centroid" type="checkbox" value="true">
    //<input name="NBTCGroup.NBTC_FA_Centroid" type="hidden" value="false">
    

    you get this:

    @Html.CheckBoxFor(m => m.NBTCGroup.NBTC_FA_Centroid, nbtcAttr)
    //generates:
    //<input name="NBTC" data-val="true" data-val-required:"The NBTC_FA_Centroid field is required" id="NBTCGroup_NBTC_FA_Centroid" name="NBTCGroup.NBTC_FA_Centroid" type="checkbox" value="true">
    //<input name="NBTCGroup.NBTC_FA_Centroid" type="hidden" value="false">
    

    Notice how in the last example, the names of the true and false generated checkboxes mismatch. Turns out the process for model binding looks for the name attribute, thus when I changed the name, it took the false value from the hidden input, no matter if the checkbox was checked or not. It just saw the correct name (while the true value had the incorrect name) and bound that value to the model.

    What I did was instead of changing the name, I just added a class and used that instead in to find the elements I wanted in my JS stuff:

    nbtcAttr = new {@class = "NBTC"}