1

In an MVC application

Where a field is nullable bool type and is presented as a radiobutton set

How can client side validation be enforced by setting of a data attribute

Client side validation is being used (jquery unobtrusive)

As it stands, I have templated nullable bools to be presented to the user as radio buttons. There are 3 radio buttons, one for True, one for False and one for null.

The null radio button is hidden, so the user is to be forced to chose from the true or false option (null is not a valid option, null just means that the user has not made a selection yet - true or false must be chosen)

I have tried the following that I found as the answer to the same question elsewhere

[Range(typeof(bool), "false", "true", ErrorMessage = "You must make a selection")]

but this never passes validation

I have also simply tried

[Required]

but this does not force the user to make a selection

Further information:-

The problem appears to exist when the bool? is presented as radiobuttons. If using the MVC default template for bool? (which is a drop down list with 3 options, "Not Set", "True", "False" then validation works as expected.

When presented as radio butttons, the radio buttons are functional and save the correct state to the database, but validation does not work.

Example code

Model :-

public class SomeModel
{
    [Required]
    public string Name { get; set; }

    [Required]
    public bool? SomeBool { get; set; }
}

View :-

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <fieldset>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.SomeBool)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.SomeBool)
            @Html.ValidationMessageFor(model => model.SomeBool)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>

    </fieldset>
}

The above works as expected with no editor template set for bool, but not for radio buttons.

Radio button template :-

@model bool?
<div>
    @{
        Dictionary<string, object> yesAttrs = new Dictionary<string, object>();
        Dictionary<string, object> noAttrs = new Dictionary<string, object>();
        Dictionary<string, object> nullAttrs = new Dictionary<string, object>();

        yesAttrs.Add("id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "Yes");
        noAttrs.Add("id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "No");
        nullAttrs.Add("id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "NA");

        if (Model.HasValue && Model.Value)
        {
            yesAttrs.Add("checked", "checked");
        }
        else if (Model.HasValue && !Model.Value)
        {
            noAttrs.Add("checked", "checked");
        }
        else
        {
            nullAttrs.Add("checked", "checked");
        }
    }

    @Html.RadioButtonFor(x => x, "true", yesAttrs) <label for="@(ViewData.TemplateInfo.GetFullHtmlFieldId(""))Yes">Yes</label>
    @Html.RadioButtonFor(x => x, "false", noAttrs) <label for="@(ViewData.TemplateInfo.GetFullHtmlFieldId(""))No">No</label>
    <span style="display: none">
        @Html.RadioButtonFor(x => x, "null", nullAttrs) <label for="@(ViewData.TemplateInfo.GetFullHtmlFieldId(""))NA">N/A</label>
    </span>
</div>

The problem is going to be related to the client side validation data attributes not being created for the radio buttons

user2437066
  • 41
  • 2
  • 10
  • Just add the `[Required]` attribute - it will required the user to select `true` or `false`. If its not working for you, then you have other issues with the code you have not shown. –  Aug 04 '16 at 09:13
  • A bool should only have 2 values, true or false. If you want 3 values, may be use an enum or a string. I personally think bool is wrong data type for situations like this. just my opinion – Preet Singh Aug 04 '16 at 09:15
  • I don't want 3 values. I want true or false only. The bool is nullable so that there is a difference between false, and the user not having selected anything yet. The user must make a selection of true or false only. Without being nullable, false would be the default, and thus it is not possible to force the user to make a selection – user2437066 Aug 04 '16 at 09:20
  • [Try this solution](http://stackoverflow.com/a/2245387/3814721) – mmushtaq Aug 04 '16 at 09:20
  • Why in the world do you have that template - all you need is 3 lines of code `@Html.RadioButtonFor(x => x, true, new { id = "" })`, `@Html.RadioButtonFor(x => x, false, new { id = "" })` and `@Html.RadioButtonFor(x => x, "", new { id = "" })` Note the last radio button has a `null` value, not a string `"null"` (which is not `null`). And then wrap them in ` –  Aug 04 '16 at 09:45
  • And I have just noticed your hiding the 3rd radio button. What is the point - just delete it and render only the `true` and `false` buttons. (or use a checkbox) –  Aug 04 '16 at 09:58
  • @StephenMuecke In order to predictably name the radio buttons by Id, which is required elsewhere, as well as to select the correct radio button, based on the value contained in the model at the time of page load. Regardless, it's not my template, so I can't risk breaking an application by removing someone elses functionality. I just need to be able to selectively enforce user selection of true or false, when a data attribute has been set to require so – user2437066 Aug 04 '16 at 10:18
  • @StephenMuecke Again, there are 3 states. Null means that the user has not made a selection, true means the user has selected the true radio button, and false means the user has selected the false radio button. This cannot be implemented with a checkbox. I can see that many of the pages in the app have jquery functions that are reading if the null value radiobutton is set. I cannot remove a radiobutton from an application wide template and break other peoples functionality – user2437066 Aug 04 '16 at 10:23
  • You need to change the template - the 3rd button has a value - its a string with 4 characters and its not `null`! And you should not be giving you buttons `id` values in a template. If there are more that one `bool?` proeprty then your generating invalid html and your javascript would not work anyway (which is the only reason to give the element an `id` anyway). And your using `RadioButtonFor()` so its the value of the property which determines what is selected - adding the `checked` attribute is pointles –  Aug 04 '16 at 10:23
  • You have stated you any want only 2 possible values - `true` or `false` so you can use a checkbox with a `bool` property. Sorry to be harsh but your code makes no sense. –  Aug 04 '16 at 10:25
  • @StephenMuecke I don't know any other way to explain this. null means there is no user selection, true means the user has pressed True, false means the user has pressed False. The user has to press True or False, they cannot press null, it is hidden and indicates that the user has not yet pressed True or False. You cannot implement this with a checkbox, a checkbox is only true or false, if the user does not press anything, then a checkbox is false, and therefor you cannot enforce the user to do anything with a checkbox, because a checkbox has a (false) value even without user input – user2437066 Aug 04 '16 at 10:33
  • @StephenMuecke and yes, the third value does represent null (see the radio button template code) it is set when the model value is null. Again this is not my template, it is working perfectly fine (except it does not have the required validation, which is what I have been asked to look at) and I cannot just flippantly remove someone elses functionality from an application and risk breaking an entire application because you don't like the way that someone has implemented something – user2437066 Aug 04 '16 at 10:38
  • The 3rd value does NOT represent null - its renders `value="null"` which is a 4 character string - it HAS a value and is not `null`. And refer [this DotNetFiddle](https://dotnetfiddle.net/XOK5Lx). But since you clearly have no intention of fixing bad code which wont work, why bother asking a question. –  Aug 04 '16 at 10:48

2 Answers2

2

You can write your small attribute for this purpose:

public class TrueFalseBoolAttribute: ValidationAttribute
{
    public override bool IsValid(Object value)
    {
        return value is bool;
    }
}

this should work for you. But I also think that if you want to have 3 options the best way would be Enum.

0

Model:

public bool? boolObj { get; set; }
public int intObj { get; set; }

View:

@Html.TextBoxFor(p => p.boolObj )
@Html.TextBoxFor(p => p.intObj )

You can see from a view source what happens on the page:

<input data-val="true" data-val-required="The boolObj field is required." id="boolObj " name="boolObj " type="text" value="False">
<input data-val="true" data-val-number="The field intObj must be a number." data-val-required="The intObj field is required." id="intObj " name="intObj " type="text" value="0">

The nullable bool has no validation attributes, whereas the bool has a data-val-required tag. The int has a data-val-required tag and a data-val-number attribute

Of course, on a checkbox this is all pretty redundant as it can only be checked (true) or not checked (false) so a required tag isn't much use.

shady youssery
  • 430
  • 2
  • 17