113

My model has a boolean that has to be nullable

public bool? Foo
{
   get;
   set;
}

so in my Razor cshtml I have

@Html.CheckBoxFor(m => m.Foo)

except that doesn't work. Neither does casting it with (bool). If I do

@Html.CheckBoxFor(m => m.Foo.Value)

that doesn't create an error, but it doesn't bind to my model when posted and foo is set to null. Whats the best way to display Foo on the page and make it bind to my model on a post?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
DMulligan
  • 8,993
  • 6
  • 33
  • 34
  • See: http://stackoverflow.com/questions/2490790/why-is-checkboxfor-producing-runtime-error – Michael Maddox Sep 01 '11 at 12:34
  • 2
    That thread ignores the issue, that I have to be able to detect null as a 3rd value. A form submission with the checkbox unchecked and a form submission where the checkbox wasn't displayed are two different scenarios that have to be accounted for. – DMulligan Sep 01 '11 at 19:11

19 Answers19

127

I got it to work with

@Html.EditorFor(model => model.Foo) 

and then making a file at Views/Shared/EditorTemplates/Boolean.cshtml with the following:

@model bool?

@Html.CheckBox("", Model.GetValueOrDefault())
James Skemp
  • 8,018
  • 9
  • 64
  • 107
DMulligan
  • 8,993
  • 6
  • 33
  • 34
  • 4
    While working with nullable boolean types, I used this '''@Html.CheckBoxFor(m => m.Foo.HasValue)''' – Gaurav Arora Oct 08 '14 at 19:29
  • 12
    @GauravKumarArora You are creating a checkbox for the nullable `HasValue` property, but not the actual boolean value. This will create a checked checkbox if regardless of what the underlying value. If the nullable has a value, it will always be true. – Siewers Apr 08 '15 at 11:13
  • 1
    This is definitively the much clean and flexibile solution of this page. +1 – T-moty May 13 '15 at 16:27
  • This creates two inputs (a checkbox of course, and a hidden form element) both of which have the same name, the hidden one being the latter in the DOM tree will serialize into being the actual value of the checkbox, therefore it would seem you would have to use javascript to change the value of the hidden element on checked/unchecked – zanderwar Jul 01 '16 at 04:30
58

Found answer in similar question - Rendering Nullable Bool as CheckBox. It's very straightforward and just works:

@Html.CheckBox("RFP.DatesFlexible", Model.RFP.DatesFlexible ?? false)
@Html.Label("RFP.DatesFlexible", "My Dates are Flexible")

It's like accepted answer from @afinkelstein except we don't need special 'editor template'

Community
  • 1
  • 1
resnyanskiy
  • 1,607
  • 1
  • 24
  • 24
  • 1
    The other advantage of this over the accepted answer is that you can decide if null should equate to true or false on a case by case basis as the logic of your application dictates, instead of having a single default throughout the application (or making two editor templates!). – ChrisFox Nov 23 '18 at 13:08
  • 1
    Clean solution worked like a charm. First line was the key – NINtender Jun 17 '21 at 14:12
  • 1
    This is what I ended up doing because it's quite clear in intent. The only change I'd make is using `nameof` or similar around `Model.RFP.DatesFlexible` instead of the hardcoded string, to benefit from static typing / intellisense / ReSharper refactoring / etc. – Lovethenakedgun Jun 21 '21 at 12:37
25

I have bool? IsDisabled { get; set; } in Model. Inserted if in View.

<div class="inputClass" id="disabled">
    <div>
    @if(Model.IsDisabled==null)
    {
        Model.IsDisabled = false;
    }           
    @Html.CheckBoxFor(model => model.IsDisabled.Value)         
    </div>
</div> 
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
VitalyB
  • 291
  • 3
  • 2
  • 1
    Not very good because you change the value in the model and in many cases, false is not equal to null. I gave you a point because it's a nice try. Note that I also agree with Darin :) – Samuel Aug 10 '12 at 20:54
  • 9
    Recommend against *giving a point because it's a nice try* as it might incorrectly indicate to other readers that the answer is *useful* when it has problems. – Chris Apr 18 '13 at 17:15
  • A better-looking alternative is to set the Boolean to false in the controller if null before rendering the page, but still use the .Value idea. – Graham Laight Jun 07 '18 at 07:56
15

My model has a boolean that has to be nullable

Why? This doesn't make sense. A checkbox has two states: checked/unchecked, or True/False if you will. There is no third state.

Or wait you are using your domain models in your views instead of view models? That's your problem. So the solution for me is to use a view model in which you will define a simple boolean property:

public class MyViewModel
{
    public bool Foo { get; set; }
}

and now you will have your controller action pass this view model to the view and generate the proper checkbox.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 30
    There are a myriad of different reasons why the foo input might not appear on the page at all. If it does appear I can assume there's a value, but I need to be able to differentiate between when the model is posted with foo as unchecked and when foo isn't shown. – DMulligan Jul 27 '11 at 19:24
  • @KMulligan, how about including another boolean property on your view model indicating whether you should render a checkbox for the Foo property and which could be stored in a hidden field so that when you postback you would know whether you should take into account the Foo value or not. – Darin Dimitrov Jul 27 '11 at 19:27
  • I did that originally, but I got tons of these primitive value types that may or may not be included in my forms for various reasons. Adding booleans for all of them started feeling silly. When I googled nullables I figured that was the way to go. – DMulligan Jul 27 '11 at 20:38
  • 4
    @KMulligan, you could write helpers, editor templates, ... so that you don't have to repeat this for every property that have such properties. – Darin Dimitrov Jul 27 '11 at 20:42
  • 29
    @DarinDimitrov, you distinctly missed or ignored the fact that the OP said he had to be able to represent the "null" aspect of the bool. You got hung up on the fact that he wanted to use the CheckBoxFor. Instead of telling him he wrote "bad code" because he used his domain model (which he may or may NOT have done) and telling him that checkboxes are either checked or unchecked, you should have attempted to answer his question or not responded. – Andrew Steitz Jan 23 '13 at 21:04
  • In my case, I have a totally different problem changing my viewModel property to a bool instead of nullable bool. I use ValueInjecter to transfer data from my viewModel to my model and vice versa and I must add some custom conventions to tells ValueInjecter to transfer bool values in nullables values which I don't want to do. +1 in anyways for your answer Darin. :) – Samuel Mar 08 '13 at 16:57
  • I don't think that this is a solution. I need to use the nullable "version" of a boolean because if the user didn't checked that checkbox, I don't save anything in the db. Examples can be thousands. – Edi Sep 05 '17 at 12:38
8

Complicating a primitive with hidden fields to clarify whether False or Null is not recommended.

Checkbox isn't what you should be using -- it really only has one state: Checked. Otherwise, it could be anything.

When your database field is a nullable boolean (bool?), the UX should use 3-Radio Buttons, where the first button represents your "Checked", the second button represents "Not Checked" and the third button represents your null, whatever the semantics of null means. You could use a <select><option> drop down list to save real estate, but the user has to click twice and the choices aren't nearly as instantaneously clear.

  1     0      null 
True  False  Not Set
Yes   No     Undecided
Male  Female Unknown
On    Off    Not Detected

The RadioButtonList, defined as an extension named RadioButtonForSelectList, builds the radio buttons for you, including the selected/checked value, and sets the <div class="RBxxxx"> so you can use css to make your radio buttons go horizontal (display: inline-block), vertical, or in a table fashion (display: inline-block; width:100px;)

In the model (I'm using string, string for the dictionary definition as a pedagogical example. You can use bool?, string)

public IEnumerable<SelectListItem> Sexsli { get; set; }
       SexDict = new Dictionary<string, string>()
        {
                { "M", "Male"},
                { "F", "Female" },
                { "U", "Undecided" },

        };

        //Convert the Dictionary Type into a SelectListItem Type
        Sexsli = SexDict.Select(k =>
              new SelectListItem
              {
                  Selected = (k.Key == "U"),
                  Text = k.Value,
                  Value = k.Key.ToString()
              });

<fieldset id="Gender">
<legend id="GenderLegend" title="Gender - Sex">I am a</legend>
    @Html.RadioButtonForSelectList(m => m.Sexsli, Model.Sexsli, "Sex") 
        @Html.ValidationMessageFor(m => m.Sexsli)
</fieldset>

public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression,
    IEnumerable<SelectListItem> listOfValues,
    String rbClassName = "Horizontal")
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();

if (listOfValues != null)
{
    // Create a radio button for each item in the list 
    foreach (SelectListItem item in listOfValues)
    {
        // Generate an id to be given to the radio button field 
        var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);

        // Create and populate a radio button using the existing html helpers 
        var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));

        var radio = String.Empty;

        if (item.Selected == true)
        {
            radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id, @checked = "checked" }).ToHtmlString();
        }
        else
        {
            radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();

        }// Create the html string to return to client browser
        // e.g. <input data-val="true" data-val-required="You must select an option" id="RB_1" name="RB" type="radio" value="1" /><label for="RB_1">Choice 1</label> 

        sb.AppendFormat("<div class=\"RB{2}\">{0}{1}</div>", radio, label, rbClassName);
    }
}

return MvcHtmlString.Create(sb.ToString());
}
}
lukiffer
  • 11,025
  • 8
  • 46
  • 70
Jules Bartow
  • 956
  • 14
  • 14
5

For me the solution was to change the view model. Consider you are searching for invoices. These invoices can be paid or not. So your search has three options: Paid, Unpaid, or "I don't Care".

I had this originally set as a bool? field:

public bool? PaidInvoices { get; set; }

This made me stumble onto this question. I ended up created an Enum type and I handled this as follows:

@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.NotSpecified, new { @checked = true })
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.Yes) 
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.No)

Of course I had them wrapped in labels and had text specified, I just mean here's another option to consider.

Paul
  • 5,700
  • 5
  • 43
  • 67
  • This is a useful representation. If you have limited space, you can also achieve this with drop-down/select, except that it takes two clicks to select an option. As with selects, radio buttons can look very different between browsers, which can be designer-hell. – Savage Oct 17 '19 at 14:52
4

Checkbox only offer you 2 values (true, false). Nullable boolean has 3 values (true, false, null) so it's impossible to do it with a checkbox.

A good option is to use a drop down instead.

Model

public bool? myValue;
public List<SelectListItem> valueList;

Controller

model.valueList = new List<SelectListItem>();
model.valueList.Add(new SelectListItem() { Text = "", Value = "" });
model.valueList.Add(new SelectListItem() { Text = "Yes", Value = "true" });
model.valueList.Add(new SelectListItem() { Text = "No", Value = "false" });

View

@Html.DropDownListFor(m => m.myValue, valueList)
Steve
  • 50,173
  • 4
  • 32
  • 41
Gudradain
  • 4,653
  • 2
  • 31
  • 40
  • 1
    If you don't care about the user being able to set all 3 possible values, it doesn't matter that you can't represent all 3 using just a checkbox. – Dave Mar 14 '14 at 20:07
  • @Dave I think you completely missed the point. The OP has a bool? meaning that he needs to select one of the 3 possible values. If he wanted to only have true or false, he should use a bool and not a bool? – Gudradain Mar 14 '14 at 20:40
  • 1
    Actually, the OP's comment indicates the opposite - that if there is a checkbox in the form, he doesn't want null to be an option at all, because that option is covered when there is a different form with no checkbox. – Dave Mar 17 '14 at 13:45
  • I used this same strategy in a search page. You can search for Yes, No, or ignore the boolean in your search. There _is_ a use case! :D – Jess Aug 09 '19 at 13:14
4

I would actually create a template for it and use that template with an EditorFor().

Here is how I did it:

  1. Create My template, which is basically a partial view I created in the EditorTemplates directory, under Shared, under Views name it as (for example): CheckboxTemplate:

    @using System.Globalization    
    @using System.Web.Mvc.Html    
    @model bool?
    
    @{
        bool? myModel = false;
        if (Model.HasValue)
        {
            myModel = Model.Value;
        }
    }
    
    <input type="checkbox" checked="@(myModel)" name="@ViewData.TemplateInfo.HtmlFieldPrefix" value="True" style="width:20px;" />
    
  2. Use it like this (in any view):

    @Html.EditorFor(x => x.MyNullableBooleanCheckboxToBe, "CheckboxTemplate")

Thats all.

Templates are so powerful in MVC, use them.. You can create an entire page as a template, which you would use with the @Html.EditorFor(); provided that you pass its view model in the lambda expression..

d.popov
  • 4,175
  • 1
  • 36
  • 47
t_plusplus
  • 4,079
  • 5
  • 45
  • 60
4

The cleanest approach I could come up with is to expand the extensions available to HtmlHelper while still reusing functionality provided by the framework.

public static MvcHtmlString CheckBoxFor<T>(this HtmlHelper<T> htmlHelper, Expression<Func<T, bool?>> expression, IDictionary<string, object> htmlAttributes) {

    ModelMetadata modelMeta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    bool? value = (modelMeta.Model as bool?);

    string name = ExpressionHelper.GetExpressionText(expression);

    return htmlHelper.CheckBox(name, value ?? false, htmlAttributes);
}

I experimented with 'shaping' the expression to allow a straight pass through to the native CheckBoxFor<Expression<Func<T, bool>>> but I don't think it's possible.

Red Taz
  • 4,159
  • 4
  • 38
  • 60
  • 1
    I had to change the method's signature for this to work for me: I used `public static MvcHtmlString CheckBoxFor(this HtmlHelper htmlHelper, Expression> expression, object htmlAttributes)` and it worked wonderfully. – Pierre-Loup Pagniez Feb 03 '17 at 10:56
1

Radio buttons are useful, but they don't allow you to deselect. If you want this behaviour, then consider using a drop-down/select. The following code will generate the SelectList and this binds successfully to a nullable boolean:

public static SelectList GetBoolTriState()
{
    var items = new List<SelectListItem>
    {
        new SelectListItem {Value = "", Text = ""},
        new SelectListItem {Value = "True", Text = "Yes"},
        new SelectListItem {Value = "False", Text = "No"},
    };

    return new SelectList(items, "Value", "Text");
}
Savage
  • 2,296
  • 2
  • 30
  • 40
1

If checked = True and not checked = null

Model

public NotNullFoo
{
    get { return this.Foo?? false; }
    set { this.Foo= (value == false ? null : true as bool?); }
}


public bool? Foo
{
   get;
   set;
}

View

  @Html.CheckBoxFor(x => Model.NotNullFoo })
Rui
  • 11
  • 1
1

I had a similar issue in the past.

Create a Checkbox input in HTML, and set the attribute name="Foo" This should still post properly.

<input type="checkbox" name="Foo" checked="@model.Foo.Value" /> Foo Checkbox<br />
Sean
  • 7,562
  • 10
  • 27
  • 29
Chris Lucian
  • 1,013
  • 6
  • 15
0

Just check for the null value and return false to it:

@{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
@Html.CheckBoxFor(model => nullableValue)
Rodrigo Boratto
  • 1,053
  • 13
  • 13
  • You know that your property metadata will be lost, right? Client side validation, field name and id, etc. – T-moty May 13 '15 at 16:24
0

I also faced the same issue. I tried the following approach to solve the issue because i don't want to change the DB and again generate the EDMX.

@{

   bool testVar = (Model.MYVar ? true : false);

 }

<label>@Html.CheckBoxFor(m => testVar)testVar</label><br />
AbcAeffchen
  • 14,400
  • 15
  • 47
  • 66
Amit Kumar
  • 79
  • 1
  • 10
0

When making an EditorTemplate for a model which contains a nullable bool...

  • Split the nullable bool into 2 booleans:

    // Foo is still a nullable boolean.
    public bool? Foo 
    {
        get 
        { 
            if (FooIsNull)
                return null;
    
            return FooCheckbox;  
        }
        set
        {
            FooIsNull   = (value == null);
            FooCheckbox = (value ?? false);
        }
    }
    
    // These contain the value of Foo. Public only so they are visible in Razor views.
    public bool FooIsNull { get; set; }
    public bool FooCheckbox { get; set; }
    
  • Within the editor template:

    @Html.HiddenFor(m => m.FooIsNull)
    
    @if (Model.FooIsNull)
    {
        // Null means "checkbox is hidden"
        @Html.HiddenFor(m => m.FooCheckbox)
    }
    else
    {
        @Html.CheckBoxFor(m => m.FooCheckbox)
    }
    
  • Do not postback the original property Foo, because that is now calculated from FooIsNull and FooCheckbox.

0

All the answers above came with it's own issues. Easiest/cleanest way IMO is to create a helper

MVC5 Razor

App_Code/Helpers.cshtml

@helper CheckBoxFor(WebViewPage page, string propertyName, bool? value, string htmlAttributes = null)
{
    if (value == null)
    {
        <div class="checkbox-nullable">
            <input type="checkbox" @page.Html.Raw(htmlAttributes)>
        </div>
    }
    else if (value == true)
    {
        <input type="checkbox" value="true" @page.Html.Raw(htmlAttributes) checked>
    }
    else
    {
        <input type="checkbox" value="false" @page.Html.Raw(htmlAttributes)>
    }
}

Usage

@Helpers.CheckBoxFor(this, "IsPaymentRecordOk", Model.IsPaymentRecordOk)

In my scenario, a nullable checkbox means that a staff member had not yet asked the question to the client, so it's wrapped in a .checkbox-nullable so that you may style appropriately and help the end-user identify that it is neither true nor false

CSS

.checkbox-nullable {
    border: 1px solid red;
    padding: 3px;
    display: inline-block;
}
zanderwar
  • 3,440
  • 3
  • 28
  • 46
0

Extension methods:

        public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression)
        {
            return htmlHelper.CheckBoxFor<TModel>(expression, null);
        }
        public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            bool? isChecked = null;
            if (metadata.Model != null)
            {
                bool modelChecked;
                if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
                {
                    isChecked = modelChecked;
                }
            }
            return htmlHelper.CheckBox(ExpressionHelper.GetExpressionText(expression), isChecked??false ,  htmlAttributes);
        }
Jim
  • 479
  • 2
  • 8
-1
@{  bool testVar = ((bool)item.testVar ? true : false); }
  @Html.DisplayFor(modelItem => testVar)
-4

This is an old question, and the existing answers describe most of the alternatives. But there's one simple option, if you have bool? in your viewmodel, and you don't care about null in your UI:

@Html.CheckBoxFor(m => m.boolValue ?? false);
Jeff Dege
  • 11,190
  • 22
  • 96
  • 165
  • 3
    Did you try this? Since the xxxFor methods expect a member expression I'd bet good money that this will generate a runtime failure. It's trying to determine the target property or field, not evaluating anything at this point. – JRoughan Apr 11 '14 at 03:13
  • 2
    run time error "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions." – ePezhman Apr 17 '14 at 11:00