asp.net-coredata-bindingrazor-pages

Why does my posted razor-page model have zero list-items in it when there should be many?


When I get the View with the same model, it contains 2 questions with 2 answeroptions for each question. So far so good.

The problem is that no matter what answer-options I select I always get zero questions back when I post the form even though my indices look right when I inspect the produced HTML, the texts are rendered properly so the loop and indices should work. I have used asp-for on inputs to bind the properties. Why don't I get any questions back?

I have searched for similar posts but I simply cannot see what I am doing different or wrong, so I must be missing something. Please help!

This is the View to reproduce the issue:

@model JobMediation.Api.Models.Onboarding.ViewModels.StartOnboardingOnlineTestModel

@{
    ViewData["Title"] = "Online-test!";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@using (Html.BeginForm("StartOnboardingOnlineTest", "Onboarding", Model, FormMethod.Post))
{
    <div>
        @for (var i = 0; i < Model.OnboardingTestQuestions.Count; i++)
        {
            <p>
                @(i + 1). @Model.OnboardingTestQuestions[i].QuestionText
            </p>

            @for (var j = 0; j < Model.OnboardingTestQuestions[i].OnboardingTestAnswerOptions.Count; j++)
            {
                <div>
                    <input type="radio" asp-for="@Model.OnboardingTestQuestions[i].SelectedAnswerOptionId" value="@Model.OnboardingTestQuestions[i].OnboardingTestAnswerOptions[j].AnswerOptionId">
                    <label><p>@(j + 1). @Model.OnboardingTestQuestions[i].OnboardingTestAnswerOptions[j].Text</p></label>
                </div>
            }

            <div style="display:none">
                questionId: <input hidden asp-for="@Model.OnboardingTestQuestions[i].QuestionId" type="text" required>
                questionText: <input hidden asp-for="@Model.OnboardingTestQuestions[i].QuestionText" type="text" required>
            </div>
        }

        <button>Send</button>
    </div>
}

My ViewModel:

using System.Collections.Generic;

namespace JobMediation.Api.Models.Onboarding.ViewModels
{
    public class StartOnboardingOnlineTestModel
    {
        public List<OnboardingTestQuestion> OnboardingTestQuestions { get; set; } = new List<OnboardingTestQuestion>();
    }
}

using System.Collections.Generic;

namespace JobMediation.Api.Models.Onboarding.ViewModels
{
    public class OnboardingTestQuestion
    {
        public string QuestionId{ get; set; }
        public string QuestionText { get; set; }
        public List<OnboardingTestAnswerOption> OnboardingTestAnswerOptions { get; set; } = new List<OnboardingTestAnswerOption>();
        public string SelectedAnswerOptionId { get; set; }
    }
}

namespace JobMediation.Api.Models.Onboarding.ViewModels
{
    public class OnboardingTestAnswerOption
    {
        public string AnswerOptionId { get; set; }
        public string Text { get; set; }
    }
}

My Controller:

[HttpGet]
[Route("/route")]
[CanBeLoggedInAttribute]
public async Task<IActionResult> StartOnboardingOnlineTest()
{
        var model = new StartOnboardingOnlineTestModel
        {
            OnboardingTestQuestions = new List<OnboardingTestQuestion> {
                new Models.Onboarding.ViewModels.OnboardingTestQuestion
                {
                    QuestionId = Guid.NewGuid().ToString(),
                    QuestionText = "Question 1",
                    OnboardingTestAnswerOptions = new List<OnboardingTestAnswerOption>
                    {
                        new Models.Onboarding.ViewModels.OnboardingTestAnswerOption
                        {
                            AnswerOptionId = Guid.NewGuid().ToString(),
                            Text = "Option 1"
                        },
                        new Models.Onboarding.ViewModels.OnboardingTestAnswerOption
                        {
                            AnswerOptionId = Guid.NewGuid().ToString(),
                            Text = "Option 2"
                        }
                    },
                    SelectedAnswerOptionId = Guid.Empty.ToString()
                },
                new Models.Onboarding.ViewModels.OnboardingTestQuestion
                {
                    QuestionId = Guid.NewGuid().ToString(),
                    QuestionText = "Question 2",
                    OnboardingTestAnswerOptions = new List<OnboardingTestAnswerOption>
                    {
                        new Models.Onboarding.ViewModels.OnboardingTestAnswerOption
                        {
                            AnswerOptionId = Guid.NewGuid().ToString(),
                            Text = "Option A"
                        },
                        new Models.Onboarding.ViewModels.OnboardingTestAnswerOption
                        {
                            AnswerOptionId = Guid.NewGuid().ToString(),
                            Text = "Option B"
                        }
                    },
                    SelectedAnswerOptionId = Guid.Empty.ToString()
                }
            }
        };

        return View("TEMP", model);
}

[HttpPost]
[Route("/route")]
public async Task<IActionResult> StartOnboardingOnlineTest(StartOnboardingOnlineTestModel model)
{
       // Handle the returned questions, but when debugging the model-parameter returns an empty list as model.OnboardingTestQuestions, see picture below
}

Picture of the returned empty list of OnboardingTestQuestions, from debug:

enter image description here

I have googled for similar posts but I already implemented according to them as far as I can see. I use for-loops instead of foreach and the indices look correct, I use asp-for and HTML-inputs to get the data in return but without success.

E.g. I checked this post, Why does my bound List<T> return empty using foreach loop to display values on razor page?, among many others, which I think I have done the same as, but my code still doesn't work. I have also tried with and without [BindProperty] on the OnboardingTestQuestions-list and [BindProperties] on the StartOnboardingOnlineTestModel-class, without any difference. I have also tried with and without the list-instantiations in OnboardingTestQuestions-list and the OnboardingTestAnswerOption.-list, also with the same result.

GET works perfectly. The View looks as expected. The indices of Questions and answeroptions work as I view them in the view and I have also inspected the produced HTML by right clicking and choosing inspect - but apparently something is not working still. Below is the form-HTML produced:

<form action="/soot?OnboardingTestQuestions=JobMediation.Api.Models.Onboarding.ViewModels.OnboardingTestQuestion&amp;OnboardingTestQuestions=JobMediation.Api.Models.Onboarding.ViewModels.OnboardingTestQuestion" method="post">    <div>
            <p>
                1. Question 1
            </p>
                <div>
                    <input type="radio" value="1dd8ff8d-fcaa-4a7d-b10f-e7c41bdd8c11" id="OnboardingTestQuestions_0__SelectedAnswerOptionId" name="OnboardingTestQuestions[0].SelectedAnswerOptionId">
                    <label><p>1. Option 1</p></label>
                </div>
                <div>
                    <input type="radio" value="37c6c1d3-40af-4fc0-a171-4546413c59f0" id="OnboardingTestQuestions_0__SelectedAnswerOptionId" name="OnboardingTestQuestions[0].SelectedAnswerOptionId">
                    <label><p>2. Option 2</p></label>
                </div>
            <div style="display:none">
                <br>questionId: <input hidden="" type="text" required="" id="OnboardingTestQuestions_0__QuestionId" name="OnboardingTestQuestions[0].QuestionId" value="c90d42ff-a67e-42e2-9ef1-e61ad64f783f">
                <br>questionText: <input hidden="" type="text" required="" id="OnboardingTestQuestions_0__QuestionText" name="OnboardingTestQuestions[0].QuestionText" value="Question 1">
            </div>
            <p>
                2. Question 2
            </p>
                <div>
                    <input type="radio" value="a9021d17-86d5-4d08-a36a-eeb88661a756" id="OnboardingTestQuestions_1__SelectedAnswerOptionId" name="OnboardingTestQuestions[1].SelectedAnswerOptionId">
                    <label><p>1. Option A</p></label>
                </div>
                <div>
                    <input type="radio" value="9ed4e62c-2a7c-42d8-a2b6-6e97d2d07806" id="OnboardingTestQuestions_1__SelectedAnswerOptionId" name="OnboardingTestQuestions[1].SelectedAnswerOptionId">
                    <label><p>2. Option B</p></label>
                </div>
            <div style="display:none">
                <br>questionId: <input hidden="" type="text" required="" id="OnboardingTestQuestions_1__QuestionId" name="OnboardingTestQuestions[1].QuestionId" value="0b420127-bfde-4a77-b109-683c4395da27">
                <br>questionText: <input hidden="" type="text" required="" id="OnboardingTestQuestions_1__QuestionText" name="OnboardingTestQuestions[1].QuestionText" value="Question 2">
            </div>

        <button>Send</button>
    </div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8AnsrMzqwIVDvpNxYQXCuLVEqQaff31nYTXBVOW9EkiqYNmFWZHDGA2PfVBLPbbw_FTkHwn5ZsC7zmr1s8lYvxiXRwcCyVTOY4Lq2rbMfasFP4K_5XXEvY2TlqOOGFDUwpj1GHU1X7ZmRIDqHEh3UvU"></form>

POST gets no questions back. Why? What am I missing? Please help!


Solution

  • Using below code can fix the issue, here is the test result.

    enter image description here

    @model JobMediation.Models.StartOnboardingOnlineTestModel
    
    @{
        ViewData["Title"] = "Online-test!";
    }
    
    @using (Html.BeginForm("StartOnboardingOnlineTest", "Onboarding", FormMethod.Post))
    {
        <div>
            @for (var i = 0; i < Model.OnboardingTestQuestions.Count; i++)
            {
                <p>@(i + 1). @Model.OnboardingTestQuestions[i].QuestionText</p>
    
                @for (var j = 0; j < Model.OnboardingTestQuestions[i].OnboardingTestAnswerOptions.Count; j++)
                {
                    <div>
                        <input type="radio" asp-for="@Model.OnboardingTestQuestions[i].SelectedAnswerOptionId" value="@Model.OnboardingTestQuestions[i].OnboardingTestAnswerOptions[j].AnswerOptionId" />
                        <label>@(j + 1). @Model.OnboardingTestQuestions[i].OnboardingTestAnswerOptions[j].Text</label>
                    </div>
                }
    
                <div style="display:none">
                    <input hidden asp-for="@Model.OnboardingTestQuestions[i].QuestionId" type="text" required />
                    <input hidden asp-for="@Model.OnboardingTestQuestions[i].QuestionText" type="text" required />
                </div>
            }
    
            <button>Send</button>
        </div>
    }