Case à cocher pour booléen nullable

97

Mon modèle a un booléen qui doit être nullable

public bool? Foo
{
   get;
   set;
}

donc dans mon Razor cshtml j'ai

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

sauf que ça ne marche pas. Ni le cast avec (bool). Si je fais

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

cela ne crée pas d'erreur, mais il ne se lie pas à mon modèle lorsqu'il est publié et foo est défini sur null. Quelle est la meilleure façon d'afficher Foo sur la page et de la lier à mon modèle sur une publication?

DMulligan
la source
2
Ce fil ignore le problème, à savoir que je dois être en mesure de détecter null comme troisième valeur. Une soumission de formulaire avec la case à cocher décochée et une soumission de formulaire où la case à cocher n'était pas affichée sont deux scénarios différents qui doivent être pris en compte.
DMulligan le

Réponses:

107

Je dois travailler avec

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

puis créer un Boolean.cshtml dans mon dossier EditorTemplates et coller

@model bool?

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

à l'intérieur.

DMulligan
la source
2
Travaille pour moi. Merci!
Samuel
1
Tout en travaillant avec des types booléens Nullable, j'ai utilisé ceci '' '@ Html.CheckBoxFor (m => m.Foo.HasValue)' ''
Gaurav Aroraa
9
@GauravKumarArora Vous créez une case à cocher pour la HasValuepropriété Nullable , mais pas la valeur booléenne réelle. Cela créera une case à cocher si quelle que soit la valeur sous-jacente. Si le nullable a une valeur, ce sera toujours vrai.
Siewers
1
C'est définitivement la solution la plus propre et flexible de cette page. +1
T-moty
2
juste une note: 1. vous devez stocker ce fichier dans "Views / Shared / EditorTemplates" 2. vous devez nommer ce fichier "Boolean.cshtml"
Jarrette
46

Réponse trouvée dans une question similaire - Rendu Nullable Bool comme CheckBox . C'est très simple et fonctionne simplement:

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

C'est comme une réponse acceptée de @afinkelstein sauf que nous n'avons pas besoin de 'modèle d'éditeur' spécial

resnyanskiy
la source
1
L'autre avantage de ceci par rapport à la réponse acceptée est que vous pouvez décider si null doit être égal à vrai ou faux au cas par cas selon la logique de votre application, au lieu d'avoir une seule valeur par défaut dans toute l'application (ou de créer deux éditeurs modèles!).
ChrisFox
25

J'ai bool? IsDisabled { get; set; }dans Model. Inséré si dans View.

<div class="inputClass" id="disabled">
    <div>
    @if(Model.IsDisabled==null)
    {
        Model.IsDisabled = false;
    }           
    @Html.CheckBoxFor(model => model.IsDisabled.Value)         
    </div>
</div> 
VitalyB
la source
1
Pas très bon car vous modifiez la valeur dans le modèle et dans de nombreux cas, false n'est pas égal à null. Je vous ai donné un point parce que c'est un bel essai. Notez que je suis également d'accord avec Darin :)
Samuel
7
Il est recommandé de ne pas donner un point car c'est un bon essai car cela pourrait indiquer à tort aux autres lecteurs que la réponse est utile en cas de problème.
Chris
Une meilleure alternative consiste à définir la valeur booléenne sur false dans le contrôleur si elle est nulle avant de rendre la page, tout en utilisant l'idée .Value.
Graham Laight
15

Mon modèle a un booléen qui doit être nullable

Pourquoi? Cela n'a pas de sens. Une case à cocher a deux états: coché / décoché ou Vrai / Faux si vous voulez. Il n'y a pas de troisième état.

Ou attendez que vous utilisiez vos modèles de domaine dans vos vues au lieu de voir des modèles? C'est ton problème. La solution pour moi est donc d'utiliser un modèle de vue dans lequel vous définirez une simple propriété booléenne:

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

et maintenant, l'action de votre contrôleur passera ce modèle de vue à la vue et générera la case à cocher appropriée.

Darin Dimitrov
la source
26
Il existe une myriade de raisons différentes pour lesquelles l'entrée foo peut ne pas apparaître du tout sur la page. Si cela apparaît, je peux supposer qu'il y a une valeur, mais je dois être en mesure de faire la différence entre le moment où le modèle est publié avec foo comme non coché et lorsque foo n'est pas affiché.
DMulligan
@KMulligan, que diriez-vous d'inclure une autre propriété booléenne sur votre modèle de vue indiquant si vous devez rendre une case à cocher pour la propriété Foo et qui pourrait être stockée dans un champ caché afin que lorsque vous publiez, vous sachiez si vous devez prendre en compte la valeur Foo ou pas.
Darin Dimitrov le
Je l'ai fait à l'origine, mais j'ai eu des tonnes de ces types de valeur primitifs qui peuvent ou non être inclus dans mes formulaires pour diverses raisons. Ajouter des booléens pour tous a commencé à sembler idiot. Lorsque j'ai recherché des nullables sur Google, je me suis dit que c'était la voie à suivre.
DMulligan
4
@KMulligan, vous pouvez écrire des helpers, des modèles d'éditeur, ... pour ne pas avoir à répéter cela pour chaque propriété qui a de telles propriétés.
Darin Dimitrov le
25
@DarinDimitrov, vous avez clairement manqué ou ignoré le fait que l'OP a dit qu'il devait être capable de représenter l'aspect "nul" du booléen. Vous vous êtes accroché au fait qu'il voulait utiliser CheckBoxFor. Au lieu de lui dire qu'il a écrit un "mauvais code" parce qu'il a utilisé son modèle de domaine (ce qu'il a peut-être fait ou non) et de lui dire que les cases à cocher sont soit cochées soit décochées, vous auriez dû essayer de répondre à sa question ou ne pas avoir répondu.
Andrew Steitz
7

Compliquer une primitive avec des champs masqués pour clarifier si False ou Null n'est pas recommandé.

La case à cocher n'est pas ce que vous devriez utiliser - elle n'a en réalité qu'un seul état: cochée . Sinon, ça pourrait être n'importe quoi.

Lorsque votre champ de base de données est un booléen Nullable ( bool?), l'UX doit utiliser 3-Radio Buttons, où le premier bouton représente votre "Checked", le deuxième bouton représente "Not Checked" et le troisième bouton représente votre null, quelle que soit la sémantique de null signifie. Vous pouvez utiliser une <select><option>liste déroulante pour enregistrer des biens immobiliers, mais l'utilisateur doit cliquer deux fois et les choix ne sont pas aussi clairs instantanément.

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

La RadioButtonList, définie comme une extension nommée RadioButtonForSelectList, crée les boutons radio pour vous, y compris la valeur sélectionnée / cochée, et définit le <div class="RBxxxx">afin que vous puissiez utiliser css pour rendre vos boutons radio horizontaux (affichage: bloc en ligne), vertical ou sous forme de tableau (affichage: bloc en ligne; largeur: 100px;)

Dans le modèle (j'utilise string, string pour la définition du dictionnaire comme exemple pédagogique. Vous pouvez utiliser 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());
}
}
Jules Bartow
la source
4

Pour moi, la solution était de changer le modèle de vue. Considérez que vous recherchez des factures. Ces factures peuvent être payées ou non. Votre recherche comporte donc trois options: Payé, Non payé ou "Je m'en fiche".

Je l'avais initialement défini comme un booléen? champ:

public bool? PaidInvoices { get; set; }

Cela m'a fait trébucher sur cette question. J'ai fini par créer un type Enum et j'ai géré cela comme suit:

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

Bien sûr, je les avais enveloppés dans des étiquettes et j'avais spécifié du texte, je veux simplement dire que voici une autre option à considérer.

Paul
la source
Ceci est une représentation utile. Si vous disposez d'un espace limité, vous pouvez également y parvenir avec la liste déroulante / sélection, sauf qu'il faut deux clics pour sélectionner une option. Comme pour les sélections, les boutons radio peuvent sembler très différents d'un navigateur à l'autre, ce qui peut être un enfer de concepteur.
Savage
4

La case à cocher ne vous propose que 2 valeurs (vrai, faux). Nullable boolean a 3 valeurs (true, false, null) donc il est impossible de le faire avec une case à cocher.

Une bonne option consiste à utiliser une liste déroulante à la place.

Modèle

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

Manette

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

Vue

@Html.DropDownListFor(m => m.myValue, valueList)
Gudradain
la source
1
Si vous ne vous souciez pas de la possibilité pour l'utilisateur de définir les 3 valeurs possibles, peu importe que vous ne puissiez pas représenter les 3 en utilisant simplement une case à cocher.
Dave
@Dave Je pense que vous avez complètement manqué le point. L'OP a un booléen? ce qui signifie qu'il doit sélectionner l'une des 3 valeurs possibles. S'il voulait seulement avoir vrai ou faux, il devrait utiliser un booléen et non un booléen?
Gudradain
1
En fait, le commentaire du PO indique le contraire - que s'il y a une case à cocher dans le formulaire, il ne veut pas du tout que null soit une option, car cette option est couverte quand il y a un formulaire différent sans case à cocher.
Dave
J'ai utilisé cette même stratégie dans une page de recherche. Vous pouvez rechercher Oui, Non ou ignorer le booléen dans votre recherche. Il y a un cas d'utilisation! : D
Jess
4

Je créerais en fait un modèle pour cela et utiliserais ce modèle avec un EditorFor ().

Voici comment je l'ai fait:

  1. Créer mon modèle, qui est essentiellement une vue partielle que j'ai créée dans le répertoire EditorTemplates, sous Shared, sous Views, nommez-le comme (par exemple) 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. Utilisez-le comme ceci (dans n'importe quelle vue):

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

C'est tout.

Les modèles sont si puissants dans MVC, utilisez-les .. Vous pouvez créer une page entière en tant que modèle, que vous utiliseriez avec le @Html.EditorFor(); à condition que vous passiez son modèle de vue dans l'expression lambda.

t_plusplus
la source
3

L'approche la plus propre que je pourrais proposer est d'étendre les extensions disponibles HtmlHelpertout en réutilisant les fonctionnalités fournies par le 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);
}

J'ai essayé de «façonner» l'expression pour permettre un passage direct au natif CheckBoxFor<Expression<Func<T, bool>>>mais je ne pense pas que ce soit possible.

Taz rouge
la source
J'ai dû changer la signature de la méthode pour que cela fonctionne pour moi: j'ai utilisé public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)et cela a fonctionné à merveille.
Pierre-Loup Pagniez
1

J'ai eu un problème similaire dans le passé.

Créez une entrée de case à cocher en HTML et définissez l'attribut name = "Foo". Cela devrait toujours s'afficher correctement.

<input type="checkbox" name="Foo" checked="@model.Foo.Value" /> Foo Checkbox<br />
Chris Lucian
la source
Cela ne semble pas publier, je reçois toujours la nullité.
DMulligan
2
Cela pourrait avoir des problèmes avec la reliure du modèle
yoel halb
0

Vérifiez simplement la valeur nulle et renvoyez-lui false:

@{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
@Html.CheckBoxFor(model => nullableValue)
Rodrigo Boratto
la source
Vous savez que les métadonnées de votre propriété seront perdues, non? Validation côté client, nom et identifiant du champ, etc.
T-moty
0

J'ai également fait face au même problème. J'ai essayé l'approche suivante pour résoudre le problème car je ne veux pas changer la base de données et générer à nouveau l'EDMX.

@{

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

 }

<label>@Html.CheckBoxFor(m => testVar)testVar</label><br />
Amit Kumar
la source
0

Lors de la création d'un EditorTemplate pour un modèle contenant un bool Nullable ...

  • Divisez le bool nullable en 2 booléens:

    // 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; }
  • Dans le modèle de l'éditeur:

    @Html.HiddenFor(m => m.FooIsNull)
    
    @if (Model.FooIsNull)
    {
        // Null means "checkbox is hidden"
        @Html.HiddenFor(m => m.FooCheckbox)
    }
    else
    {
        @Html.CheckBoxFor(m => m.FooCheckbox)
    }
  • Ne publiez pas la propriété d'origine Foo, car elle est désormais calculée à partir de FooIsNull et FooCheckbox.


la source
0

Toutes les réponses ci-dessus sont venues avec ses propres problèmes. Le moyen le plus simple / le plus propre à l'OMI est de créer un assistant

Rasoir MVC5

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)

Dans mon scénario, une case à cocher nullable signifie qu'un membre du personnel n'avait pas encore posé la question au client, elle est donc enveloppée dans un .checkbox-nullableafin que vous puissiez styler de manière appropriée et aider l'utilisateur final à identifier que ce n'est ni truenifalse

CSS

.checkbox-nullable {
    border: 1px solid red;
    padding: 3px;
    display: inline-block;
}
Zanderwar
la source
0

Méthodes d'extension:

        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
la source
0

Les boutons radio sont utiles, mais ils ne vous permettent pas de désélectionner . Si vous voulez ce comportement, envisagez d'utiliser une liste déroulante / sélectionnez. Le code suivant générera le SelectListet cela se lie avec succès à un booléen Nullable:

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");
}
Sauvage
la source
-1
@{  bool testVar = ((bool)item.testVar ? true : false); }
  @Html.DisplayFor(modelItem => testVar)
edrdesigner
la source
1
cela lèvera une exception si testVar est nul.
danwyand le
-1

tu peux essayer comme ça

@Html.TextBoxFor(m => m.foo, new {   type = "checkbox" })
Yağmur Bilgin
la source
-4

C'est une vieille question, et les réponses existantes décrivent la plupart des alternatives. Mais il y a une option simple, si vous avez booléen? dans votre viewmodel, et vous ne vous souciez pas de null dans votre interface utilisateur:

@Html.CheckBoxFor(m => m.boolValue ?? false);
Jeff Dege
la source
2
Avez-vous essayé cela? Puisque les méthodes xxxFor s'attendent à une expression de membre, je parierais que cela générera un échec d'exécution. Il essaie de déterminer la propriété ou le champ cible, n'évaluant rien à ce stade.
JRoughan
2
erreur d'exécution "Les modèles ne peuvent être utilisés qu'avec l'accès aux champs, l'accès aux propriétés, l'index de tableau à une dimension ou les expressions d'indexation personnalisées à paramètre unique."
ePezhman