Search code examples
c#asp.netasp.net-corerazor

How to properly display checkbox form controls in ASP.NET Core Views?


I have a model with a boolean property which will be shown as a checkbox in my view:

public class SomeModel
{
    public bool SomeProperty { get; set; }
}

And I am creating the view like this:

<div class="form-check">
    <input asp-for="TestProperty" value="true" class="form-check-input" />
    <label class="form-check-label" asp-for="TestProperty"></label>
</div>

Below is the HTML output in the browser:

<div class="form-check">
    <input value="true" class="form-check-input" type="checkbox" data-val="true" data-val-required="The TestProperty field is required." id="TestProperty" name="TestProperty">
    <label class="form-check-label" for="TestProperty">TestProperty</label>
</div>

And I can see below hidden element is appended at the end of the form:

<input name="TestProperty" type="hidden" value="false">

When I submit the form this is the request body:

TestProperty: true
TestProperty: false

The value in the first line is the value in the check box element and the second line is the value of the hidden element that overrides the first line value when received in the controller. I noticed that when I click on the checkbox the hidden input's value does not change.

The first solution that comes to mind is to update the hidden element via JavaScript when the input's value is changed, which this is unnecessary. Is there any way that I can get rid of these hidden elements? or I am not displaying the form control properly. I am not sure.

UPDATE

The hidden input elements are added automatically by the razor view itself. Though I am using controllers/views and not razor views, I tested Enrico's solution and the result is the same. It still appends a hidden input element for the checkbox:

enter image description here


Solution

  • You don't need to manually add an hidden input field, or to interact with it via client side Javascript code. The way to go with ASP.NET core and Razor is using the tag helpers and let the actual HTML generation to be done by Razor itself.

    This is an example using Razor Pages and a single boolen value in the page input model. I have used ASP.NET core 3.1 and the usual ASP.NET core web application template in Visual Studio 2019.

    This is the page model:

    public class TestFormModel : PageModel
      {
        private readonly ILogger _logger;
    
        public TestFormModel(ILogger<TestFormModel> logger)
        {
          _logger = logger;
        }
    
        [BindProperty]
        public InputModel Input { get; set; }
    
        public IActionResult OnGet()
        {
          this.Input = new TestFormModel.InputModel
          {
            ShowUsername = false
          };
    
          return this.Page();
        }
    
        public IActionResult OnPost() 
        {
          if (!this.ModelState.IsValid) 
          {
            return this.Page();
          }
    
          _logger.LogInformation("The posted value is: {Value}", this.Input.ShowUsername);
    
          return this.RedirectToPage("Index");
        }
    
        public class InputModel 
        {
          [Display(Name = "Show User Name")]
          public bool ShowUsername { get; set; }
        }
      }
    

    This is the associated Razor view:

    @page
    @model StackoverflowTest.Pages.TestFormModel
    @{
      ViewData["Title"] = "How to build a form with a checkbox";
    }
    
    <h1>@ViewData["Title"]</h1>
    
    <form asp-page="TestForm">
      <div class="form-check">
        <input class="form-check-input" asp-for="Input.ShowUsername">
        <label class="form-check-label" asp-for="Input.ShowUsername"></label>
      </div>
      <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    

    That's it. You do not need to manually add an hidden input in your form. You just have to use the proper tag helps and ASP.NET core will take care of generating the right form and form inputs for you.

    UPDATE 2 August 2021

    See this github repository for a working sample.

    As a sidenote, please consider that the hidden input field is automatically added by ASP.NET core and it is required in order to post to the server the right value for Input.ShowUsername when the checkbox in the form is unchecked.

    Basically in the generated HTML page source there are two form inputs for Input.ShowUsername. One of them is an HTML input having type="checkbox", the other one is an HTML input having type="hidden". Both of them have the same value for the name attribute (name="Input.ShowUsername"), while the value attribute is true for the checkbox input and false for the hidden input.

    This is the HTML page source for the form (exactly the way it is generated by ASP.NET core 3.1):

    <form action="/TestForm" method="post">
      <div class="form-check">
        <input class="form-check-input" type="checkbox" data-val="true" data-val-required="The Show User Name field is required." id="Input_ShowUsername" name="Input.ShowUsername" value="true">
        <label class="form-check-label" for="Input_ShowUsername">Show User Name</label>
      </div>
      <button type="submit" class="btn btn-primary">Submit</button>
      <input name="__RequestVerificationToken" type="hidden" value="CfDJ8AHG4SVs1BBFruMHNiza3QNKvHNf96d_oiz5J6n3eYuncnlFKqvL4x3G6jbAOmNfJjrwk_crqFN99kjZiBVoboFGd0VQIBlrvmp9fL6p8CMbiZ4f6Cf4rm3OpGZEZ_04UQQ5iuUi3nVjfIgfb5-vctw" />
      <input name="Input.ShowUsername" type="hidden" value="false">
    </form>
    

    When the checkbox in the form is checked, the value true is posted to the server for the name Input.ShowUsername, when the checkbox is unchecked the value false is posted to the server for the name Input.ShowUsername: this works because the value of unchecked checkboxes is not posted to the server.

    Adding an input with type="hidden" is a common pattern to post a default value for the checkbox when it is unchecked, see here for an explanation about the way HTML checkboxes work.

    Again, all of this is automatically generated for you by ASP.NET core: you simply have to define the page input model and to use the proper Razor tag helpers.