views:

1761

answers:

2

Hi,

I'm trying to model bind a set of dynamically generated checkboxes so as to process them in the controller action but can't get the model binding to occur. This is the scenario:

My ViewModel class (DocumentAddEditModel) contains a dictionary (Dictionary<string,bool>) with the string of each entry being the name/label for each checkbox and the boolean indicating whether the checkbox is checked:

    public class DocumentAddEditModel
    {
     ...
        private Dictionary<string, bool> _categoryCheckboxes = new Dictionary<string,bool>();
     ...

     ...
        public Dictionary<string, bool> CategoryCheckboxes
        {
            get { return _categoryCheckboxes; }
            set { _categoryCheckboxes = value; }
        }
     ...
    }
}

Within my controller the action that handles the GET request for the form populates the dictonary as follows:

public class DocumentsController : Controller
{
    [RequiresAuthentication]
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Add()
    {
     DocumentAddEditModel documentAddEditModel = new DocumentAddEditModel();
     ...
     Dictionary<string, bool> categoryCheckboxes = new Dictionary<string, bool>();
     ...
     string[] categories = Enum.GetNames(typeof(Category));

     foreach (string category in categories)
      categoryCheckboxes.Add(category, false);

     documentAddEditModel.CategoryCheckboxes = categoryCheckboxes;

     return View(documentAddEditModel);
    }
}

Within the view i have the following to generate the checkboxes:

<% foreach (KeyValuePair<string, bool> categoryCheckbox in ViewData.Model.CategoryCheckboxes)
    {%>
    <input class="checkbox" type="checkbox" name="CategoryCheckboxes[0].Key" id="<%= categoryCheckbox.Key %>" />
    <label class="categoryLabel" for="<%= categoryCheckbox.Key %>"><%= categoryCheckbox.Key %></label>
<% } %>

but i think this is where the problem must be. Not sure what needs to be going in the name attribute. The problem is that once posted back to the following action method in the DocumentsController:

[RequiresAuthentication]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(DocumentAddEditModel documentAddEditModel)
{
    ...
}

documentAddEdit.Model.CategoryCheckboxes is always null. How do i set this up so that the CategoryCheckboxes dictionary is correctly populated with name and checked/unchecked bool value for the checkboxes?

Thanks

+6  A: 

If you are binding your checkboxes to a Dictionary<string, bool> try this:

<% var i = 0; %>
<% foreach (KeyValuePair<string, bool> categoryCheckbox in Model.CategoryCheckboxes) {%>

    <input type="hidden" name="<%= String.Format("CategoryCheckboxes[{0}].Key", i) %>" value="<%= categoryCheckbox.Key %>" />
    <%= Html.CheckBox(String.Format("CategoryCheckboxes[{0}].Value", i), categoryCheckbox.Value) %>

    <label class="categoryLabel" for="<%= categoryCheckbox.Key %>"><%= categoryCheckbox.Key %></label>

    <% i++; %>
<% } %>

Hope this helps

UPDATED:

For binding to IDictionary<T1, T2> your form must contain inputs with "CategoryCheckboxes[n].Key" and "CategoryCheckboxes[n].Value" Ids/Names, where n must be zero-based and unbroken.

eu-ge-ne
Thanks eu-ge-ne, that seems to do the trick although, could you possibly clarify what's going on here?I understand that since the checkbox control (<input type="checkbox"...) does not have a 'name' attribute which model binding uses to perform the mapping between controls and viewmodel properties it is necessary(?) to use a hidden field. Is there however no way to avoid such a hidden field that holds/transmits state?What i'm subsequently not clear about is how the true/false status of the checkbox is transmitted.
Matthew
Again i realise that the Html.CheckBox() htmlhelper generates a second hidden field with a name attribute equal to that of the visible checkbox but when i compare the generated html for a checkbox when not checked:
Matthew
<input type="hidden" name="CategoryCheckboxes[0].Key" value="Article" /> <input id="CategoryCheckboxes[0]_Value" name="CategoryCheckboxes[0].Value" type="checkbox" value="true" /><input name="CategoryCheckboxes[0].Value" type="hidden" value="false" /> <label class="categoryLabel" for="Article">Article</label>
Matthew
with what it is when checked: <input type="hidden" name="CategoryCheckboxes[0].Key" value="Article" /> <input checked="checked" id="CategoryCheckboxes[0]_Value" name="CategoryCheckboxes[0].Value" type="checkbox" value="true" /><input name="CategoryCheckboxes[0].Value" type="hidden" value="false" /> <label class="categoryLabel" for="Article">Article</label>
Matthew
the only difference is that in the checked state the 'checked' attribute is set to "checked". The 'value' attributes that hold the actual data do not change. Also, is it not possible to achieve what i want without having to again resort to a hidden element (i.e. without using the Html.Checkbox() helper?)Thanks
Matthew
Unchecked checkboxes do not provide any data during serializing. In that case your Model is binded to the second hidden field with the same name and false value.
eu-ge-ne
Using only Html.Checkbox() helper I think it is impossible to bind to IDictionary<>. You will be able to bind only to bool[]
eu-ge-ne
A: 

hidden elements are the modus operandi of asp.net. in the webforms version it was the ViewState, which was a massive hidden field, in the MVC its lighter weight more straightforward hidden fields per control. I'm not sure what's wrong or disturbing about that. Somehow you have to keep state, you can either do it client side or server side. I suggest client side for non-sensitive information that way it will still be there if the server is restarted between postbacks etc.

dave