Not able to bind html.checkbox on form post

2019-08-06 15:31发布

问题:

So I have a view model call ProductViewModel which has a list of sites where a product can be produced. I am trying to figure out how to show this on a form using a checkbox for the user to select the sites where the product can be produced. Seems straight forward, right? Well it doesn't work like I want/need. Hoping someone can help guide me to the correct way to do this.

My classes:

 public class ProductViewModel
 {
    public List<Sites> SiteList {get; set;}   
    public string ProductName {get; set;}
    public int ProductId {get; set;}
    public User ProductOwner{get; set;}
 }

 public class Sites
 { 
    public int SiteId {get; set;} 
    public string SiteName {get; set;}
    public bool IsSelected {get; set;}    
 }

Part of my view:

@Html.LabelFor(m=>m.Sites):
@foreach (var site in Model.Sites)
{
    @Html.CheckBox("Sites", site.IsSelected, new { value = site.SiteName })
    @Html.Label(site.SiteName)
} 

When using @Html.Checkbox() I see the following output in the html from the browser:

<input checked="checked" id="Sites" name="Sites" type="checkbox" value="Miami" />
<input name="Sites" type="hidden" value="false" />

I understand the hidden field but what I really need is to get the value for the selected item. So I need to get back the list with Miami in it. I don't need the false/true thing that the html helper seem to want to send (i.e. Miami=true)

So instead I tried this.

@for(int id=0; id < Model.Sites.Count(); id++)
{
    <input type="checkbox" id="@Model.Sites[id].SiteName" name="Sites[@id]" value="@Model.BoxingSites[id].SiteName" @(Model.Sites[id].IsSelected  ? @"checked=""checked""": "") />
    @Html.Label(Model.Sites[id].SiteName)                   
}  

And the output is:

<input type="checkbox" id="Miami" name="Sites[0]" value="Miami" checked=&quot;checked&quot; />
<label for="Miami">Miami</label>

In both of these cases I am not able to get the binder to map the form values to the Product.Sites list when posting to the action.

The action is like this:

[HttpPost]
public ActionResult Edit(ProductViewModel Product)
{
     //Does something with the form data.
}

The other values (ProductName etc...) map fine.

What am I doing wrong? I feel I am missing something as this should be easier due to how MVC simplifies so many other form handling situations.

Thanks in advance...

回答1:

How about using an editor template instead of struggling with loops:

@model ProductViewModel
@using (Html.BeginForm())
{
    ... some other form fields

    @Html.LabelFor(x => x.SiteList)
    @Html.EditorFor(x => x.SiteList)

    <input type="submit" value="Create" />
}

and inside the corresponding editor template ~/Views/Shared/EditorTemplates/Sites.cshtml:

@model Sites
<div>
    @Html.HiddenFor(x => x.SiteId)
    @Html.CheckBoxFor(x => x.IsSelected)
    @Html.LabelFor(x => x.SiteName)
</div>

Now not only that your view code is much more clean but proper names will be generated for the input fields so that the model binder will be able to bind the selected values back in the POST action.

[HttpPost]
public ActionResult Create(ProductViewModel model)
{
    ...
}


回答2:

Here is what is working for me.

// View Model
[Display(Name="Boolean Property")]
[UIHint("booleancheckbox"]
public bool? booleanProperty;

View

// View
@Html.EditorFor(m => m.booleanProperty, new { @onclick = "Toggle(this);" })

Editor Template - add some more code to handle null values

// Editor Template booleancheckbox.cshtml
@model bool?

@{
    labelText = ViewData.ModelMetadata.DisplayName != null ?
                ViewData.ModelMetadata.DisplayName : 
                ViewData.ModelMetadata.PropertyName;
}

<label for="@ViewData.ModelMetadata.PropertyName">@labelText

    @Html.CheckBox(string.Empty, Model.Value, ViewContext.ViewData)

</label>