可空布尔值的复选框

113

我的模型有一个布尔变量必须可为空

public bool? Foo
{
   get;
   set;
}

所以在我的Razor cshtml文件中,我有以下代码:

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

除了那个方法不起作用之外,使用(bool)强制转换也不行。如果我这样做

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

这不会产生错误,但在提交时它不会绑定到我的模型,而foo被设置为null。最好的方法是在页面上显示Foo并使其在提交时绑定到我的模型?


请参考:https://dev59.com/tnE95IYBdhLWcg3wCJRf - Michael Maddox
2
那个线程忽略了一个问题,即我必须能够将null检测为第三个值。复选框未选中的表单提交和未显示复选框的表单提交是两种不同的情况,必须加以考虑。 - DMulligan
19个回答

127

我已经让它工作了

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

然后在 Views/Shared/EditorTemplates 文件夹下新建一个名为 Boolean.cshtml 的文件,并添加以下内容:

@model bool?

@Html.CheckBox("", Model.GetValueOrDefault())

4
在使用可空布尔类型时,我使用了这个'''@Html.CheckBoxFor(m => m.Foo.HasValue)'''。 - Gaurav Arora
13
你正在创建一个针对可空属性 HasValue 的复选框,但没有针对实际布尔值创建复选框。这将创建一个选中的复选框,无论底层值是什么。如果可为空的值具有值,则始终为真。 - Siewers
1
这绝对是该页面最清晰、最灵活的解决方案。+1 - T-moty
这将创建两个输入项(当然是一个复选框和一个隐藏表单元素),它们都具有相同的名称,DOM 树中后面的那个隐藏元素将序列化为复选框的实际值。因此,在选中/取消选中时似乎必须使用 JavaScript 更改隐藏元素的值。 - zanderwar

59

在类似问题中找到答案 - 将可空布尔渲染为复选框。 这非常简单,而且只需要起作用:

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

和@afinkelstein的被采纳答案类似,只是我们不需要特殊的“编辑器模板”


1
与被接受的答案相比,这种方法的另一个优点是您可以根据应用程序的逻辑决定 null 是否应该按案例平衡为 true 或 false,而不是在整个应用程序中使用单一默认值(或创建两个编辑器模板!)。 - ChrisFox
1
干净的解决方案非常有效。第一行是关键。 - NINtender
1
这是我最终采取的做法,因为意图非常明确。唯一的改变是使用 nameof 或类似的方式来围绕 Model.RFP.DatesFlexible,而不是硬编码字符串,以从静态类型/智能感知/ReSharper重构等方面受益。 - Lovethenakedgun

25

我在Model中有一个bool?IsDisabled{get;set;}。 在View中插入if语句。

<div class="inputClass" id="disabled">
    <div>
    @if(Model.IsDisabled==null)
    {
        Model.IsDisabled = false;
    }           
    @Html.CheckBoxFor(model => model.IsDisabled.Value)         
    </div>
</div> 

1
翻译:不是很好,因为您在模型中更改了值,在许多情况下,false不等于null。我给你一个点,因为这是一个不错的尝试。请注意,我也同意Darin的看法 :) - Samuel
9
不建议因为“尝试很好”而给出分数,因为这可能会错误地向其他读者表明答案是“有用的”,而实际上存在问题。 - Chris
一个更好的选择是在控制器中将布尔值设置为false,如果在呈现页面之前为空,但仍然使用.Value的想法。 - Graham Laight

15
我的模型必须具有可为空的布尔值。
为什么?这没有意义。复选框只有两种状态:选中/未选中,或者说True/False。没有第三种状态。
或者说,你正在视图中使用领域模型,而不是视图模型?那就是你的问题。我的解决方案是使用视图模型,在其中定义一个简单的布尔属性。
public class MyViewModel
{
    public bool Foo { get; set; }
}

现在你需要将此视图模型传递给视图并生成相应的复选框。


30
有许多不同的原因可能导致页面上根本没有出现"foo"输入框。如果它确实出现了,我可以假设有一个值,但我需要能够区分模型发布时"foo"未被选中和"foo"未显示的情况。 - DMulligan
@KMulligan,你觉得在你的视图模型中再加入一个布尔属性如何?这个属性可以指示是否应该为Foo属性呈现复选框,并且可以存储在隐藏字段中,以便在提交后你会知道是否应该考虑Foo值。 - Darin Dimitrov
4
@KMulligan,你可以编写帮助程序、编辑模板等,以便您不必为每个具有此类属性的属性重复此过程。 - Darin Dimitrov
29
@DarinDimitrov,您明显忽略了或忽视了原帖作者说他必须能够表示布尔值的“null”方面这一事实。 您陷入了他想要使用CheckBoxFor的事实中。 而不是告诉他因为他使用了领域模型(他可能已经这样做了)而编写了“糟糕的代码”,并告诉他复选框要么被选中要么未被选中,您应该尝试回答他的问题或者不予回复。 - Andrew Steitz
在我的情况下,我有一个完全不同的问题,需要将我的viewModel属性更改为bool而不是可空bool。我使用ValueInjecter从我的viewModel传输数据到我的模型,反之亦然,并且我必须添加一些自定义约定来告诉ValueInjecter将bool值转换为可空值,但我不想这样做。无论如何,对于你的答案Darin,我给你点赞。 :) - Samuel
显示剩余2条评论

8

不建议使用隐藏字段来复杂化原始代码以澄清False或Null。

复选框并不是你应该使用的东西——它实际上只有一种状态:已选中。否则,它可以是任何东西。

当你的数据库字段是可空布尔型 (bool?) 时,用户体验 (UX) 应该使用3个单选按钮,其中第一个按钮表示 "已选中",第二个按钮表示 "未选中",第三个按钮表示你的 null,无论 null 的语义是什么。你可以使用 <select><option> 下拉列表来节省空间,但用户需要点击两次,而且选择的选项不够即时清晰。

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

RadioButtonList是一个扩展,名称为RadioButtonForSelectList,它可以为您构建单选按钮,包括选中/已选值,并设置<div class="RBxxxx">,因此您可以使用CSS使单选按钮水平(display: inline-block),垂直或以表格方式显示(display: inline-block; width:100px;)。
在模型中(我正在使用字符串,字符串作为示例来说明字典定义。您可以使用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());
}
}

5

对于我来说,解决方法是更改视图模型。考虑您正在搜索发票。这些发票可以支付或未支付。因此,您的搜索有三个选项:已付款,未付款或“不关心”。

我最初将其设置为bool?字段:

public bool? PaidInvoices { get; set; }

这让我遇到了这个问题。最终,我创建了一个Enum类型,并按如下处理:
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.NotSpecified, new { @checked = true })
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.Yes) 
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.No)

当然,我已经给它们贴上了标签并指定了文本,我只是想说这里还有另一个值得考虑的选项。

这是一个有用的表示法。如果你的空间有限,你也可以使用下拉菜单/选择器来实现,但需要两次点击才能选择一个选项。与选择器一样,单选按钮在不同浏览器之间可能看起来非常不同,这可能会让设计师感到困扰。 - Savage

4
我会为此创建一个模板,并将其与EditorFor()一起使用。
以下是我的做法:
  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")

模板在MVC中非常强大,一定要使用它们。你可以将整个页面创建为一个模板,在使用@Html.EditorFor()时,只需通过lambda表达式传递视图模型即可。

Thats all.


4
我能想到的最简洁的方法是扩展HtmlHelper的可用扩展,同时仍然重用框架提供的功能。
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);
}

我尝试了“塑形”表达式以允许直接通过原生CheckBoxFor<Expression<Func<T, bool>>>,但我认为这是不可能的。


1
我不得不更改方法的签名才能让它对我起作用:我使用了 public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes),它非常有效。 - Pierre-Loup Pagniez

4

复选框只提供了两个值(true,false)。可空布尔类型有3个值(true,false,null),因此使用复选框无法实现。

一个好的选择是使用下拉菜单。

模型

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

控制器

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" });

查看

@Html.DropDownListFor(m => m.myValue, valueList)

1
如果您不关心用户能否设置所有3个可能的值,那么仅使用复选框无法表示所有3个值也无关紧要。 - Dave
@Dave 我觉得你完全错了点。OP有一个bool?,意味着他需要选择3个可能的值之一。如果他只想要true或false,那么他应该使用bool而不是bool?。 - Gudradain
1
实际上,OP的评论表明相反的情况 - 如果表单中有一个复选框,他根本不希望null成为一个选项,因为当没有复选框的不同表单时,该选项已经被覆盖了。 - Dave
我在搜索页面中使用了相同的策略。您可以搜索Yes、No或忽略搜索中的布尔值。这确实有用例! :D - Jess

1

我以前也遇到过类似的问题。

在HTML中创建一个复选框输入,并设置属性name="Foo",这样应该可以正确提交。

<input type="checkbox" name="Foo" checked="@model.Foo.Value" /> Foo Checkbox<br />

这似乎没有发布,我仍然得到null。 - DMulligan
2
这可能会在模型绑定方面出现问题。 - yoel halb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接