Checkboxes in a Razor Pages Form

Checkboxes are used in a Razor Pages form to enable users to select zero or more predefined options. Checkboxes are a type of input element, and some aspects of their behaviour is unique and needs to be understood when deciding whether, and how to use them.

Checkbox Basics

Checkboxes are rendered in HTML by setting the type attribute in an input element to checkbox:

<input type="checkbox">

This appears in most browsers as a small box: . When it is selected, the box acquires a tick: . This state is indicated in HTML by the presence of a checked attribute on the element:

<input type="checkbox" checked>

You can also provide a value to the checked attribute. Any of the following are considered valid values according to the HTML 5 spec:

<input type="checkbox" checked="">
<input type="checkbox" checked="checked">
<input type="checkbox" checked="CHECKED">

Most browsers will interpret any value as indicating true, so even the following will result in the checkbox being checked:

<input type="checkbox" checked="false">

Note that if you pass a Razor expression to the checked attribute that evaluates to anything other than true, the checked attribute will not be rendered at all. So the following will result in the checked attribute being omitted from the generated HTML:

<input type="checkbox" checked="@(1 == 0)">

As with any form field, the checkbox needs a name attribute specified so that its a value can be submitted.

<input type="checkbox" name="myCheckbox">

The value will only be submitted if the checkbox is checked. If nothing is specified for the value attribute, on will be submitted for a checkbox value:

Checkboxes in a Razor Pages Form

Razor Checkboxes

Razor offers two ways to generate checkboxes. The recommended approach is to use the input tag helper. Any boolean property of the PageModel will render a checkbox if it is passed to the asp-for attribute, so long as the property is not nullable:

public class IndexModel : PageModel
{
    public bool IsChecked { get; set; }
    ...
}
<input asp-for="IsChecked">

You can also use a string property, so long as the value of the string can be parsed as a boolean value (true or false).

The property name passed to the asp-for attribute is used for the values of both the id and name attributes. The rendered HTML will also include two fields for the IsChecked property:

<input type="checkbox" data-val="true" data-val-required="The IsChecked field is required." id="IsChecked" name="IsChecked" value="true">
<input name="IsChecked" type="hidden" value="false">

The second field is a hidden field. It will be submitted regardless whether the checkbox is checked or not. If the checkbox is checked, the posted value will be true,false. The model binder will correctly extract true from the value. Otherwise it will be false. This behaviour is really a feature of MVC, where selection of which particular action to execute on a controller can come down to the parameters that the action method takes. The hidden field will ensure that the checkbox will correspond to a bool parameter of an action method, or a Razor Pages handler method.

If you don't want a hidden field to be rendered, the workaround is to NOT use the tag helper to render your checkbox.

The second mechanism for generating checkboxes is the CheckBox (and CheckBoxFor) Html helper method, which again are features of MVC. Nevertheless, it is worth understanding how these work since they are available from within a PageModel.

The Html.CheckBox helper takes a string that is used to render the name and id attributes of the input element:

@Html.CheckBox("IsChecked")

There are overloads that enable you to set the checkbox as checked, and to provide additional arbitrary attributes:

@Html.CheckBox("IsChecked", new { @class = "form-control" })

The Html.CheckBoxFor helper is similar to the tag helper in that it takes an expression to be evaluated against the current model:

@Html.CheckBoxFor(model => model.IsChecked)

Consequently, it supports working in a strongly typed manner where only properties of the current model will be made available by IntelliSense.

Both of the Html helper methods result in a hidden field being generated along with the checkbox.

Binding to Collections

All of the examples above illustrate rendering a single checkbox to capture a single choice. Checkboxes can also be used to manage multiple selections. Checkboxes can be used with simple collections, such as those that permit the user to select one or more options from a prepared list, or they can be used in conjunction with a collection of complex objects.

Simple Collections

The following Razor code illustrates generating 10 checkboxes, all with the same value for the name attribute, but each with a different value attribute:

<form method="post">
    @for (var i = 1; i <= 10; i++)
    {
        <input name="AreChecked" type="checkbox" value="@i" /> @i<br />
    }
    <button>Click</button>
</form>

This is how the resulting HTML renders:

Checkboxes in Razor Pages Forms

When you select multiple boxes and submit the form, the values are included in the request like this:

AreChecked=1&AreChecked=2&AreChecked=4&AreChecked=6&AreChecked=10

ASP.NET Core processes that into StringValues type, which represents null/zero, one or more strings:

Checkboxes in Razor Pages Forms

The model binder will bind those values to a collection whose property name matches the value of the name attribute of the elements and whose type supports conversion of the submitted values. In this case, a collection of type int will work, as well as a collection of string types:

Checkboxes in Razor Pages Forms

The key to this scenario is to ensure that the name attribute is the same for each input. Also, you should note that if you specify a non-boolean property in the asp-for attribute of an input tag helper, Razor renders an input whose type is set to text, so you must use plain HTML to render checkboxes that enable binding to a simple collection.

Collections of Complex Objects

In the next example, the checkbox is used to represent a boolean property of a complex type - the Dispatched property of an Order entity. You have a list of orders made this week. You have added a property to the PageModel to represent the data and ensured that posted values will be bound to it:

[BindProperty]
public List<Order> OrdersThisWeek { get; set; } = new List<Order>();

You want to display the orders on screen together with an updatable checkbox to indicate whether the order has been dispatched. Therefore the task is to get the model binder to associate each checkbox with a specific order. The following code shows how that is achieved:

<form method="post">
    <table class="table">
        <tr>
            <th>Id</th>
            <th>Customer</th>
            <th>Date</th>
            <th>Dispatched</th>
        </tr>
        @for (var i = 0; i < Model.OrdersThisWeek.Count(); i++)
        {
            <tr>
                <td>
                    <input type="hidden" asp-for="OrdersThisWeek[i].OrderId" />
                    @Model.OrdersThisWeek[i].OrderId
                </td>
                <td>@Model.OrdersThisWeek[i].Customer</td>
                <td>@Model.OrdersThisWeek[i].OrderDate.ToShortDateString()</td>
                <td><input asp-for="OrdersThisWeek[i].Dispatched" /></td>
            </tr>
        }
    </table>
    <button>Update</button>
</form>

Here is how the HTML renders:

Checkboxes in Razor Pages Forms

The key to associating related properties of items in a collection is to use an indexer. In the example above, a sequential index has been used. Sequential indices must start at zero, and there can be no gaps in the sequence.

Although the example above features an edit scenario, sequential indices are more likely to be used when adding collections of new items. When editing collections, you are more likely to use the other type of index supported by the model binder - an explicit index.

An explicit index supports any value that uniquely identifies an item of data. Usually, this is the key value for an item, typically an int, but you can just as validly use GUIDs, strings, date and time values etc. When using this indexing system, you must include a hidden field that explicitly states the index value for an item. The hidden field must be named in the form boundpropertyname.Index, and the value must be the index. Here is the previous form modified to use the order's OrderId value as an explicit index:

<form method="post">
    <table class="table">
        <tr>
            <th>Id</th>
            <th>Customer</th>
            <th>Date</th>
            <th>Dispatched</th>
        </tr>
        @foreach (var order in Model.OrdersThisWeek)
        {
            <tr>
                <td>
                    <input type="hidden" name="OrdersThisWeek.Index" value="@order.OrderId" />
                    <input type="hidden" name="OrdersThisWeek[@order.OrderId].OrderId" value="@order.OrderId" />
                    @order.OrderId
                </td>
                <td>@order.Customer</td>
                <td>@order.OrderDate.ToShortDateString()</td>
                <td><input name="OrdersThisWeek[@order.OrderId].Dispatched" value="true" /></td>
            </tr>
        }
    </table>
    <button>Update</button>
</form>

This time, it is not possible to use tag helpers to generate the checkbox. Therefore you must set the name and value attributes yourself.

Last updated: 11/6/2020 11:17:28 AM

© 2018 - 2024 - Mike Brind.
All rights reserved.
Contact me at Mike dot Brind at Outlook.com