Ned
Ned

Reputation: 1207

Binding radio buttons in ASP.NET MVC page with razor

I have a page with 4 radio buttons (Exam question and 4 options to choose from). With the code below, I can read the option values after user clicks the button.

I have two questions:

Thanks!

StudentQuestions.cshtml

@page "{examId:int?}"
@model VerityLearn.WebUI.Pages.Questions.StudentQuestionsModel
@{
    ViewData["Title"] = "StudentQuestions";
}
    <div>
        <form method="post">
            @Html.LabelFor(model => model.QuestionViewModel.QuestionText);

                <p>Select the correct option.</p>

                int optionIndex = -1;

                @foreach (OptionViewModel opt in Model.QuestionViewModel.Options)
                {
                    optionIndex++;

                    @Html.RadioButtonFor(model => model.QuestionViewModel.Options[optionIndex], Model.QuestionViewModel.Options[optionIndex]);
                    @opt.OptionText<br />
                }

            <button type="submit" asp-route-questionIndex="@(Model.QuestionNdx + 1)" class="btn btn-primary @nextDisabled">Next</button>

        </form>
    </div>

StudentQuestions.cshtml.cs

   public class StudentQuestionsModel : PageModel
    {
        //private readonly VerityContext _context;
        private readonly SignInManager<VerityUser> _signInManager;
        private readonly UserManager<VerityUser> _userManager;
        private readonly IStudentQuesionsUOW _studentQuesionsUOW;
        private readonly VerityLearn.DataAccess.VerityContext _context;

        public StudentQuestionsModel(
            VerityContext context,
            SignInManager<VerityUser> signInManager,
            UserManager<VerityUser> userMrg,
            IStudentQuesionsUOW studentQuesionsUOW
        )
        {
            _context = context;
            _signInManager = signInManager;
            _userManager = userMrg;
            _studentQuesionsUOW = studentQuesionsUOW;
        } // end public StudentQuestionsModel(VerityContext context, SignInManager<VerityUser> signInManager, UserManager<VerityUser> userMrg)

        [BindProperty]
        public QuestionViewModel QuestionViewModel { get; set; }

        [BindProperty]
        public Question Question { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            ... Not sure what to do here ...
        }

QuestionViewModel.cs

    public class QuestionViewModel
    {
        public int QuestionId { get; set; }

        public int ExamQuestionOrder { get; set; }

        public string QuestionText { get; set; }

        public bool? IsSingleSelection { get; set; }

        public List<OptionViewModel> Options { get; set; }
    } 

Upvotes: 1

Views: 8897

Answers (3)

Murat Yıldız
Murat Yıldız

Reputation: 12022

I would suggest to use Animated radios & checkboxes (noJS) that is working perfectly fine with its animation effects. You can follow the steps below in order to use it in your Razor pages:

css:

.checkbox label:after, .radio label:after {
    content: '';
    display: table;
    clear: both;
}
 
.checkbox .cr, .radio .cr {
    position: relative;
    display: inline-block;
    border: 1px solid #a9a9a9;
    border-radius: .25em;
    width: 1.3em;
    height: 1.3em;
    float: left;
    margin-right: .5em;
}
 
.radio .cr {
    border-radius: 50%;
}
 
.checkbox .cr .cr-icon, .radio .cr .cr-icon {
    position: absolute;
    font-size: .8em;
    line-height: 0;
    top: 50%;
    left: 20%;
}
 
.radio .cr .cr-icon {
    margin-left: 0.04em;
}
 
.checkbox label input[type="checkbox"], .radio label input[type="radio"] {
    display: none;
}
 
.checkbox label input[type="checkbox"] + .cr > .cr-icon, .radio label input[type="radio"] + .cr > .cr-icon {
    transform: scale(3) rotateZ(-20deg);
    opacity: 0;
    transition: all .3s ease-in;
}
 
.checkbox label input[type="checkbox"]:checked + .cr > .cr-icon, .radio label input[type="radio"]:checked + .cr > .cr-icon {
    transform: scale(1) rotateZ(0deg);
    opacity: 1;
}
 
.checkbox label input[type="checkbox"]:disabled + .cr, .radio label input[type="radio"]:disabled + .cr {
    opacity: .5;
}

controller:

public async Task<ActionResult> Edit(int id)
{
    //code omitted for brevity
    return PartialView("_Edit", model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Update([Bind(Exclude = null)] GroupViewModel model, 
    params string[] selectedRoles)
{
    //code omitted for brevity
    await this.GroupManager.UpdateGroupAsync(group);
    selectedRoles = selectedRoles ?? new string[] { };
    await this.GroupManager.SetGroupRolesAsync(group.Id, selectedRoles);
}

view:

@model Demo.Models.GroupViewModel

<div class="form-group">
    @Html.Label("Roles", new { @class = "col-md-3 control-label" })
    <div class="col-md-6">
 
        <div class="checkbox">
            <label>
                <input type="checkbox" id="checkAll" name="" class="" />
                <span class="cr"><i class="cr-icon glyphicon glyphicon-ok"></i></span>
                <text id= "checkText">Tümü?</text> 
            </label>
        </div>
 
        @foreach (var role in Model.RolesList)
        {
            <div class="checkbox">
                <label>
                    <input type="checkbox" name="selectedRoles" value="@role.Text" checked="@role.Selected" class="" />
                    <span class="cr"><i class="cr-icon glyphicon glyphicon-ok"></i></span>
                    @role.Text
                </label>
            </div>
        }
    </div>
</div>

<script>
    // for selecting All / None
    $("#checkAll").change(function () {
        var checked = $(this).prop("checked");
        $("input:checkbox").prop('checked', checked);
        $('#checkText').text(checked ? 'None?' : 'All?');
    });     
</script>

Upvotes: 1

Mike Brind
Mike Brind

Reputation: 30035

If you want to use radio buttons to represent mutually exclusive options, you should group them together by applying the same name attribute value:

<input type="radio" name="question1" value="answer1" /> Answer 1
<input type="radio" name="question1" value="answer2" /> Answer 2
<input type="radio" name="question1" value="answer3" /> Answer 3

The Html helper you are using generates different id and name attribute values for each radio button. I would avoid using Html helpers in Razor Pages in favour of tag helpers:

@for (var i = 0; i <  Model.QuestionViewModel.Options.Count(); i++)
 {
    <input type="radio" asp-for="QuestionViewModel.QuestionId" value="@Model.QuestionViewModel.Options[i]" /> @Model.QuestionViewModel.Options[i].OptionText<br/>
 }

More info: https://www.learnrazorpages.com/razor-pages/forms/radios

Upvotes: 1

Dave Barnett
Dave Barnett

Reputation: 2206

The thing you are interested in is the value for the radio button that is selected. In my demo to make things simpler I've assumed it's an id. Suppose the radio buttons have name AnswerSelectedId, then a value labelled with AnswerSelectedId is posted back to the server when the form is submitted. Therefore you need to define a property in your model called AnswerSelectedId so that the model binding can match the two.

Here is a quick demo to show what you are trying to achieve borrowing heavily from Mike Brind's answer.

StudentQuestions.cshtml.cs

public class OptionViewModel
{
    public int Id { get; set; }
    public string OptionText { get; set; }
}

public class QuestionViewModel
{
    public int QuestionId { get; set; }
    public int ExamQuestionOrder { get; set; }
    public string QuestionText { get; set; }
    public bool? IsSingleSelection { get; set; }
    public List<OptionViewModel> Options { get; set; }
}

public class StudentQuestionsModelModel : PageModel
{
    public void OnGet()
    {
        QuestionViewModel = new QuestionViewModel
        {
            QuestionId = 1,
            ExamQuestionOrder = 1,
            QuestionText = "Question1",
            IsSingleSelection = false,
            Options = new List<OptionViewModel>()
            {
                new OptionViewModel
                {
                    Id = 1,
                    OptionText = "Option 1"
                },
                new OptionViewModel
                {
                    Id = 2,
                    OptionText = "Option 2"
                }
            }
        };
    }

    [BindProperty]
    public QuestionViewModel QuestionViewModel { get; set; }

    [BindProperty]
    public int AnswerSelectedId { get; set; }   //this is the key bit

    public async Task<IActionResult> OnPostAsync()
    {
        return Content($"The answer selected was {AnswerSelectedId}");
    }
}

StudentQuestions.cshtml

@page "{examId:int?}"
@model StudentQuestions.Pages.StudentQuestionsModel  

<div>
    <form method="post">
        @Html.LabelFor(model => model.QuestionViewModel.QuestionText);

        <p>Select the correct option.</p>    

        @for (var i = 0; i < Model.QuestionViewModel.Options.Count(); i++)
        {
            <input type="radio" asp-for="AnswerSelectedId" 
                   value="@Model.QuestionViewModel.Options[i].Id" /> 
                  @Model.QuestionViewModel.Options[i].OptionText
                  <br>
        }

        <button type="submit" class="btn btn-primary">Next</button>
    </form>
</div>

Note how the asp-for attribute matches the property AnswerSelectedId in your page model.

When you submit is pressed form data will be sent to the server. If you use the developer tools under form data sent to the server it will look like this (depending on the value selected)

AnswerSelectedId: 1

Then the model binding will bind the value to your property called AnswerSelectedId and you will then be able to access it in your onPostAsync method.

Upvotes: 2

Related Questions