5
votes

Background

I have a model containing three bools

public class PageDataModel
{
    public bool setting1 { get; set; }
    public bool setting2 { get; set; }
    public bool setting3 { get; set; }
}

If one value is true, the others must be false.

In my view, I am displaying three radio buttons:

Enter image description here

The Razor code for my view:

<div class="row">
        <div class="radio">
            @if (Model.Setting1)
            {
                @Html.RadioButtonFor(m => m.Setting1,  "Setting1", new { Checked = "checked", Name = "Group"})
            }
            else
            {
                @Html.RadioButtonFor(m => m.Setting1, "Setting1", new { Name = "Group"})
            }

            @Html.Label("Setting1")
        </div>
        <div class="radio">
            @if (Model.Setting2)
            {
                @Html.RadioButtonFor(m => m.Setting2, "Setting2", new { Checked = "checked", Name = "Group" })
            }
            else
            {
                @Html.RadioButtonFor(m => m.Setting2, "Setting2", new { Name = "Group"})
            }
            @Html.Label("Setting2")
        </div>
        <div class="radio">
            @if (Model.Setting3)
            {
                @Html.RadioButtonFor(m => m.Setting3, "Setting3", new { Checked = "checked", Name = "Group" })
            }
            else
            {
                @Html.RadioButtonFor(m => m.Setting3, "Setting3", new { Name = "Group"})
            }
            @Html.Label("Setting3")
        </div>
        <button type="submit" class="btn btn-default">Save</button>
    </div>

In my controller, I am returning a model with setting1 set to true:

var model = new PageDataModel
        {
            Setting1 = true,
            Setting2 = false,
            Setting3 = false
        };
        return View(model);

This works correctly, the page is loaded with the 'setting 1' radio button checked.

The problem

When I submit the form, the posted model contains all false values, regardless of which radio button was checked.

I've tried changing the value from "setting1" to true/false, but that had no impact on the returned data.

I'm fairly sure I'm not setting up the binding properly, but I'm not sure where I'm going wrong.

All other examples I have found are binding two radio buttons to one bool value (for example, a yes/no pair of radio buttons bound to one bool). I can change the code to use one int property, but I'd like to solve this using bools.

What am I doing wrong? Am I misusing radio buttons here?

1
Having only one radio button for a property does not make sense. You need to 2 radio button for each property, one with value="true" and one with value="false" (and remove all your if blocks) - @Html.RadioButtonFor(m => m.Setting1, true) @Html.RadioButtonFor(m => m.Setting1, false) - user3559349
I'm fairly sure I'm using the wrong control here, was just interested to see if it was possible to solve it this way. A radio button is either checked or not (true/false), it made sense to me at the time. If blocks are a bit messy, but this is still very much at the PoC phase. Once I get it working I'll tidy them up. - Jay
And the reason all your properties are false is that your radio buttons have value="Setting1", value="Setting2" etc which cannot be bound to a bool so the values are their defaults (false) - user3559349
that makes sense. Is there a way to bind the value to if checked? - Jay
The RadioButtonFor() method does that automatically. Based on the code in my previous comments, if the value is true the 1st button will be checked, otherwise the 2nd one will be. - user3559349

1 Answers

6
votes

Technically, this would work fine as long as you have the value set to true. For example:

@Html.RadioButtonFor(m => m.setting1, true)

With that, if the radio is selected, it will post true.

However, the insurmountable problem here is that this is not how radios are designed to function. A radio is part of a group, determined by the value of the name attribute. This causes selecting one radio to deselect all radios with the same name attribute. Using RadioButtonFor, though, will give each radio a different name, based on the property name. In other words, you'd end up with:

<input type="radio" id="setting1" name="setting1" value="true" />
<input type="radio" id="setting2" name="setting2" value="true" />
<input type="radio" id="setting3" name="setting3" value="true" />

Each radio, then, would be part of a different group, consisting of only one radio each (meaning each will be selected by default). You could override the name attribute, but then the posted value would no longer bind to the actual property. For example, if you had:

<input type="radio" id="setting1" name="setting" value="true" />
<input type="radio" id="setting2" name="setting" value="true" />
<input type="radio" id="setting3" name="setting" value="true" />

Such that they're all now part of the same radio group, the value would be posted to setting and it would always be true.

Long and short, there's just no way to make this work like this. What you can do is proxy the value using another property.

public class PageDataModel
{
    public bool setting1 { get; set; }
    public bool setting2 { get; set; }
    public bool setting3 { get; set; }

    public string SelectedSetting
    {
        get
        {
            if (setting1) return "setting1";
            if (setting2) return "setting2";
            if (setting3) return "setting3";
            return null; // or you can set a default here
        }
        set
        {
            switch (value)
            {
                case "setting1":
                    setting1 = true;
                    break;
                case "setting2":
                    setting2 = true;
                    break;
                case "setting3":
                    setting3 = true;
                    break;
            }
        }
    }
}

Then, in your view:

<div class="radio">
    <label>
        @Html.RadioButtonFor(m => m.SelectedSetting, "setting1")
        @Html.DisplayNameFor(m => m.setting1)
    </label>
</div>
<div class="radio">
    <label>
        @Html.RadioButtonFor(m => m.SelectedSetting, "setting2")
        @Html.DisplayNameFor(m => m.setting2)
    </label>
</div>
<div class="radio">
    <label>
        @Html.RadioButtonFor(m => m.SelectedSetting, "setting3")
        @Html.DisplayNameFor(m => m.setting3)
    </label>
</div>

Now, the radios will all be grouped as "SelectedSetting" and will post to the SelectedSetting property. The custom getter and setter on that propery ensures that the proper boolean values will be set on the actual settingX properties.