Manually binding HTML checkboxes to an MVC model

ASP.NET MVC makes it easy to bind your HTML form’s inputs to a strongly typed model. It does this by applying type/naming convention that is mostly sensible and understandable. If you use the existing HtmlHelper class, which is the standard way to do forms if you follow most Razor tutorials et.c., you might not even be aware of this being done for you.

This conversion from the HTTP requests parameters to the .NET MVC model class that ends up as your controller’s action arguments, is done by classes called ModelBinders. If you don’t like how it’s normally done, you can writer your own binders.

One very useful part of the standard binding mechanism is the ability to have a set of HTML form inputs bind to an array in your .NET model:

@foreach (var checkbox in ViewBag.Checkboxes) {
    <label for="Checkboxes@checkbox.Id">
        <input type="checkbox" 
               name="Checkboxes[@checkbox.Id]"
               class="measure-control" checked />
            @checkbox.Name
    </label>
}

With this corresponding model on the server side:

public class ActionModel {
    public string[] Checkboxes { get; set; }
}

This will almost take care of automatically binding the checkbox values to the array of strings, putting the string “on” into the array elements where the checkbox has been selected.

The almost part is this: the default model binder assumes that all elements are present, meaning you must provide Checkboxes[0], Checkboxes[1],… up to Checkboxes[n], you can’t have missing elements in the array, or the binder will cut the array short – if you leave out Checkboxes[0], it will even leave the model property as null.

In my case, the indices of the array can’t be guaranteed to start at zero, and there will be gaps in the array. What I ended up doing, to make the model binder happy, was to place this before my checkbox inputs:

@{
    var checkboxes = ((IEnumerable<Checkbox>) ViewBag.Checkboxes);
    var maxCheckboxId = checkboxes.Aggregate(0, (a, m) => Math.Max(m.Id, a));
    for (var i = 0; i < maxCheckboxId; i++) {
        if (checkboxes.All(m => m.Id != i)) {
            <input type="hidden" name="Checkboxes[@i]" value=""/>
        }
    }
}

This will provide a hidden input for all the missing elements of the array, which makes model binder work as expected. Adding a custom model binder or refactoring the view model might make more sense to some (I would prefer the latter in this case). In this particular case, I had no choice and I also got the chance to learn a little more about how the model binder actually works.

One thought on “Manually binding HTML checkboxes to an MVC model

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">