Managing Checkboxes And Radios In ASP.NET Razor Web Pages

Checkboxes and radio buttons cause more confusion than any other form control in ASP.NET Razor Web Pages. This article takes an in depth look at them and tackles the most frequently asked questions that they generate.

Checkboxes are used to enable users to select zero or more available options. Typical examples of usage might include providing a means for people to indicate which, if any, hobbies they like. Most commonly, you see checkboxes being used to provide the user with a means to signify acceptance of terms of use or similar. Radio buttons are generally used in groups and allow selection of only one of a group of options. You might use them to specify the postage option for shipping, or to choose between yes and no.

Both controls are types of the HTML <input> element. The type attribute dictates the field type. For checkboxes, the type attribute must be "checkbox". For radio buttons, the type is "radio":

<input type="checkbox" />
<input type="radio" />

Alternatively, you might want to use the HTML form helpers that come with Web Pages to render your controls:

@Html.CheckBox("check")
@Html.RadioButton("radio")

Name and Value attributes

A name attribute is required if you want to access the form control's value when the containing form is submitted to the server. If you use the HTML helper, the string that you pass in to the helper method will be used for both the name and id attributes. The default value for both checkboxes and radio buttons is "on". You can provide any other value to the value attribute, but if you omit it altogether, the default value is used. The following code demonstrates that:

<form method="post">
    <input type="checkbox" name="chk1" />
    <input type="checkbox" name="chk2" value="my own value" />
    <input type="submit" />
</form>
<div>
    <p>chk1 value: @Request["chk1"]</p>
    <p>chk2 value: @Request["chk2"]</p>
</div>

If you run this page and check both boxes, the rendered result is as follows:

chk1 value: on

chk2 value: my own value

When you submit a form, most controls within the form are added to the Request collection along with their value or an empty string if none was provided. This is not the case with radios and checkboxes. If they are not selected, they will not be added to the Request collection. That means they will be null in server-side code. They share this behaviour with the input that has a type attribute of 'submit'. You can test this yourself with a simple form like this:

<form method="post">
    <input type="checkbox" name="chk" /><br />
    <input type="radio" name="rad" /><br />
    <input type="submit" name="submit" /><br />
    <input type="text" name="text" /><br />
    <textarea name="textarea"></textarea><br />
    <select name="select"><option></option><option>1</option></select><br />
    <input type="hidden" name="hidden" /><br />
    <input type="password" name="password" /><br />
    <button>Button</button>
</form>

You can use the ObjectInfo helper to examine the values when you submit the empty form using the <button>:

Check: @ObjectInfo.Print(Request["chk"])
Radio: @ObjectInfo.Print(Request["rad"])
Submit: @ObjectInfo.Print(Request["submit"])
Text: @ObjectInfo.Print(Request["text"])
TextArea: @ObjectInfo.Print(Request["textarea"])
Select: @ObjectInfo.Print(Request["select"])
Hidden: @ObjectInfo.Print(Request["hidden"])
Password: @ObjectInfo.Print(Request["password"])

The output confirms that radios, checkboxes and input type="submit" are all null if they are not checked or clicked:

Checkbox

You have to test to see if the checkbox, radio or submit is present in the collection before you attempt any operations on them, otherwise you will receive a NullReferenceException: "Object reference not set to an instance of an object". You can use the IsEmpty() helper to determine if a form value is present as it tests for null before it tests to see if an empty string is present.

Grouping

You can group checkboxes and radios together to add additional behaviour. You do this by supplying multiple elements with the same name attribute value:

@for(var i = 1; i<= 5; i++){
    <input type="checkbox" name="check" value="@i" />
}

This will result in 5 checkboxes, each with the name 'check' but with incrementing values from 1 - 5. When you group checkboxes, the user can select as many within the group as they like. When the containing form is posted, all the values of the checked boxes are received on the server as a comma-separated string. If all boxes are checked in the example above, the value of Request["check"] will be "1,2,3,4,5". Typically, you will want to process each value separately so you use the string.Split method to convert the string into an array that you can then iterate:

@if(!Request["check"].IsEmpty()){
    <ul>

        @foreach(var selection in Request["check"].Split(',')){
            <li>@selection</li>
        }
    </ul>
}

Radio buttons behave differently when you group them by providing them with the same name value. Only one radio within a group can be checked at any time. The only way to uncheck a radio is select another one in the same group. Therefore they are a good way to force a user to make a selection, especially if you set one of the radios as checked by default. You do that by setting the checked attribute value to 'checked', although browsers will also accept the presence of the checked attribute alone - or with any other value applied- as indicating the radio (or checkbox) has been selected. All of the following result in the element being checked - even if you try to apply 'false' as a value to the checked attribute:

<input type="radio" name="rad1" checked /> 
<input type="radio" name="rad2" checked="checked" />
<input type="radio" name="rad3" checked="true" /> <input type="radio" name="rad4" checked="false" />

Both of the last two will result in a checked radio in most browsers, but will not validate as HTML.

If you want to set the checked status of a radio or checkbox dynamically using Razor, you can take advantage of a Razor feature called Conditional Attributes that was introduced in version 2 of the Web Pages framework (MVC 4 onwards). To use this feature, you pass a boolean expression or variable into the checked attribute. If the expression resolves to 'true', Razor will render checked="checked" to the browser. If it resolves to 'false', the checked attribute is not rendered at all. Here's an example that will (rather uselessly) result in a checkbox being checked if it is rendered when the hour is even:

<input type="checkbox" name="chk" checked="@(DateTime.Now.Hour  % 2 == 0 )" />

If you wanted to use the Html Helper, the syntax is as follows:

@Html.CheckBox("chk", DateTime.Now.Hour  % 2 == 0)

HTML Helpers

Having started to look at the Html helper in the previous example, it is worth exploring their use in a bit more detail. The Html helpers offer a more succinct syntax than their HTML counterparts and can help to minimise code in the view area of your page. Most of them also translate across to their MVC counterparts, so if there is a possibility that you may upgrade to MVC at some time, using the helpers will minimise the work required to migrate the View area. Note that the Razor Web Pages Html helpers are NOT the same as the MVC Html helpers. They come from different libraries. The Razor Web Pages helpers come from System.Web.WebPages.Html, whereas the MVC helpers are in System.Web.Mvc.Html.

You have already seen a couple of examples of the helpers' use: the first example at the beginning of the article showed just a string being passed in to the helper method:

@Html.CheckBox("check")

The string passed in will be used for both the name and id attribute. This default behaviour could cause a problem when grouping. It is perfectly acceptable to have multiple elements on the same page share the same name attribute value, but the id attribute value should be unique for each element. If you pass in the same string to multiple checkbox helpers, they will all share the same id as well as the same name. The way to get round that is to override the id value by passing in your own to the htmlAttributes parameter of the helper. You do this either by passing in an attribute and its value using a Dictionary<string, object> or an anonymous type:

[Dictionary approach]
@for(var i = 1; i <= 5; i++){
    @Html.CheckBox("group1", new Dictionary<string, object>{{"id", i}}) @i<br />
}
    
[Anonymous type approach]
@for(var i = 1; i <= 5; i++){
    @Html.CheckBox("group1", new {id = i}) @i<br />
}

Both of these approaches result in 5 checkboxes, all with the same name, but with id values that increment from 1 to 5. So why are there two approaches to achieve the same outcome? Well, the anonymous type approach is by far the shorter syntactically, and would generally be preferred. An anonymous type is a proper C# type that acts as nothing more than a short-lived read-only type where you both declare property names and set their values using the object initialiser syntax. Once you realise that the left hand side of the equation is a property, you realise that it must conform to the rules that apply to naming properties in C#. In particular, you cannot use a hyphen in a property name. Most attributes do not include hyphens in their name, but the HTML5 custom 'data-' attributes do. So if you want to specify the value if a 'data-' attribute, you will have to use the dictionary approach:

@Html.CheckBox("check", new Dictionary<string,object>{{"class", "chk-style"},{"data-custom", "5"}})

The above example sets two attributes, data-custom and class. If you just wanted to set the class attribute value, you can use the anonymous type approach - but you need to prefix the word 'class' with an @ symbol:

@Html.CheckBox("check", new {@class = "chk-style"})

This has nothing to do with Razor syntax. It is a requirement of C# since 'class' is a C# keyword. Generally, you should avoid using keywords for C# identifiers (including property names), but in this case it is unavoidable.

Client-side

While the focus of this article is the use of Checkboxes and radios in Razor programming, it is also worth briefly covering a couple of client-side questions that come up regularly: How do I determine the state of a checkbox or radio in client-side code? And how do I provide an easy way for users to select all checkboxes?

In both cases, jQuery is the library of choice for DOM querying and manipulation, so the solutions will feature its use. The easiest way to determine if a checkbox or radio is checked is to inspect its checked property:

$('#chk').is(':checked')

This will return true if the checkbox belonging to the specified element is checked, and false if not.

The following code illustrates the Select All question and its solution.

<div>
    <input type="checkbox" id="selectAll" /> Select all
</div>
<div>
    <ul>
        @for(var i = 1; i<= 10; i++){
        <li><input type="checkbox" name="chk@i" class="check" /></li>
    }
    </ul>
</div>
<script>
    $(function () {
        // Set 'Select All' to checked if all other boxes are already checked
        $('#selectAll').prop('checked',  $('.check').length == $('.check:checked').length);

        // Add click function to 'Select All' to select all other boxes
        $('#selectAll').click(function () {
            $('.check').prop('checked', this.checked);
        });

        // Add click function to each child checkbox
        $('.check').click(function () {
            $('#selectAll').prop('checked', $('.check').length == $('.check:checked').length);
        });
    })
</script>

One checkbox is labeled 'Select All'. When this is checked, all the checkboxes with a class of 'check' will be checked too. The checkbox labeled 'Select All' is set to checked if all checkboxes with the class 'check' are checked. This is determined by creating two selectors - one that contains all checkboxes with the class 'check' and another that contains a sequence of checkboxes with the class 'check' that have been checked. The length property of both sequences is compared. Then a click event handler is added to 'Select All' which sets all the other checkboxes to the same state as itself. This allows the user to toggle the state of all checkboxes using the Select All option. Finally, a click event handler is added to each target checkbox, which updates the state of the 'Select All' checkbox based on the state of all the checkboxes with the class 'check'.