Bind checkboxes to List in partial view

1.3k views Asked by At

I have a CreateViewModel.

public class CreateViewModel
{
  public AttributesViewModel AttributesInfo { get; set; }
}

The AttributesViewModel is sent to a partial view.

public class AttributesViewModel
{
  public AttributesViewModel()
  {
    ChosenAttributes = new List<int>();
  }

  public List<Attributes> Attributes { get; set; }
  public List<int> ChosenAttributes { get; set; }
}

The List of Attributes is outputted in the partial view. Each one has a checkbox.

foreach (var attribute in Model.Attributes)
{
  <input type="checkbox" name="ChosenAttributes" value="@attribute.ID" /> @Attribute.Name
}

When I post CreateViewModel, AttributesInfo.ChosenAttributes is always empty even though I checked some boxes. How do I properly name each checkbox so that it binds to the ChosenAttributes List?

My Solution

I took Stephen Muecke's suggestion to do the two way binding. So, I created a CheckboxInfo class that contained Value, Text, and IsChecked. I created a EditorTemplate for it:

@model Project.CheckboxInfo

@Html.HiddenFor(model => model.Text)
@Html.HiddenFor(model => model.Value)
@Html.CheckBoxFor(model => model.IsChecked)&nbsp;@Model.Text

One GIANT caveat. To get this to work properly, I had to create an EditorTemplate for the AttributesViewModel class. Without it, when CreateViewModel is posted, it cannot link the checkboxes to AttributesInfo.

2

There are 2 answers

5
AudioBubble On

Your naming the checkbox name="ChosenAttributes" but CreateViewModel does not contain a property named ChosenAttributes (only one named AttributesInfo). You may be able make this work using

<input type="checkbox" name="AttributesInfo.ChosenAttributes" value="@attribute.ID" /> @Attribute.Name

but the correct approach is to use a proper view model that would contain a boolean property (say) bool IsSelected and use strongly typed helpers to bind to your properties in a for loop or using a custom EditorTemplate so that your controls are correctly names and you get 2-way model binding.

0
Brendan Vogt On

I had a similar scenario, but this was how I did it. The solution is not perfect so please excuse if I have left something out, but you should be able to relate. I tried to simplify your solution as well :)

I changed the Attribute class name to CustomerAttribute, rename it to whatever you like, use a singular name, not plural. Add a property to your CustomerAttribute class, call it whatever you like, I called mine IsChange.

public class CustomerAttribute
{
     public bool IsChange { get; set; }

     // The rest stays the same as what you have it in your Attributes class

     public string Name { get; set; }  // I'm assuming you have a name property
}

Delete your AttributesViewModel class, you don't really need it, I like simplicity.

Modify your CreateViewModel class to look like this:

public class CreateViewModel
{
     public CreateViewModel()
     {
          CustomerAttributes = new List<CustomerAttribute>();
     }

     public List<CustomerAttribute> CustomerAttributes { get; set; }
}

Your controller will look something like this:

public ActionResult Create()
{
     CreateViewModel model = new CreateViewModel();

     // Populate your customer attributes

     return View(model);
}

Your post controller action method would look something like this:

[HttpPost]
public ActionResult Create(CreateViewModel model)
{
     // Do whatever you need to do
}

In your view, you will have something like this:

<table>
     <tbody>

          @for (int i = 0; i < Model.CustomerAttributes.Count(); i++)
          {
               <tr>
                    <td>@Html.DisplayFor(x => x.CustomerAttributes[i].Name)</td>
                    <td>@Html.CheckBoxFor(x => x.CustomerAttributes[i].IsChange)</td>
               </tr>
          }

     <tbody>
</table>

Create a sample app and try out the code above and see if it works for you.