Привязка в Razor возвращает значение null (OnPostAsyn c) - PullRequest
0 голосов
/ 09 мая 2020

В приведенном ниже коде все значения в QuestionViewModel равны нулю. Есть идеи, что я делаю неправильно по поводу привязки?

файл cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using VerityLearn.DataAccess;
using VerityLearn.Domain;
using VerityLearn.DataAccess.UOW;
using VerityLearn.WebUI.ViewModels;

namespace VerityLearn.WebUI.Pages.Questions
{
    [Authorize]
    public class StudentQuestionsModel : PageModel
    {
        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)

        #region User Properties
        public VerityUser VerityUser { get; set; }
        //[TempData]
        //public string UserId { get; set; }
        public Student Student { get; set; }
        public Prospect Prospect { get; set; }
        public Parent Parent { get; set; }
        public ExamUserViewModel ExamUserViewModel { get; set; }
        #endregion // User Properties

        public DateTime DateStarted { get; set; }

        public Exam Exam { get; set; }

        [TempData]
        public int ExamId { get; set; }

        ExamQuestion ExamQuestion { get; set; }

        public List<ExamQuestion> ExamQuestions { get; set; }

        [TempData]
        public int NbrOfExamQuestions { get; set; }

        public ExamViewModel ExamViewModel { get; set; }

        [TempData]
        public int QuestionNdx { get; set; }

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

        [ViewData]
        public string Message { get; set; }

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

        public async Task OnGetAsync(int? examId, int? questionNdx)
        {
            Message = string.Empty;

            if (_signInManager.IsSignedIn(HttpContext.User))
            {
                string email = HttpContext.User.Identity.Name;
                VerityUser = await _studentQuesionsUOW.GetVerityUser(email);
                //UserId = VerityUser.Id.ToString();
                // TODO: Setup priorities of setting Student, Prospect and Parent properties, might involve Enrollments 4/30/2020
                //Student = await _context.Students.Where(s => s.UserId == VerityUser.Id).FirstOrDefaultAsync<Student>();
                //Prospect = await _context.Prospects.Where(p => p.UserId == VerityUser.Id).FirstOrDefaultAsync<Prospect>();
                //Parent = await _context.Parents.Where(p => p.UserId == VerityUser.Id).FirstOrDefaultAsync<Parent>();
                DateStarted = DateTime.Now;

                if ((examId != null) && (examId.Value > 0))
                {

                   ExamId = examId.Value;
                    Exam = await _context.Exams.Where(e => e.ExamId == examId)
                        .Include(e => e.Course).ThenInclude(c => c.Subject).FirstOrDefaultAsync<Exam>();
                    if (Exam != null)
                    {

                        ExamUserViewModel = new ExamUserViewModel
                        {
                            ExamUserId = 0,
                            ExamId = Exam.ExamId,
                            TimeStarted = DateTime.Now,
                            Status = ExamUserStatus.InProgress,
                            StudentId = VerityUser.StudentId,
                            ProspectId = VerityUser.ProspectId,
                            ParentId = VerityUser.ParentId
                        };
                        // TODO: If this is a new ExamUser, we must insert it to VerityLearnDB2.ExamUsers

                        if (NbrOfExamQuestions == 0)
                        {
                            ExamQuestions = await _context.ExamQuestions
                                .Where(eq => eq.ExamId == examId)
                                .ToListAsync<ExamQuestion>();
                            NbrOfExamQuestions = ExamQuestions.Count;
                            TempData.Keep("NbrOfExamQuestions");
                        } // endif (NbrOfExamQuestions == 0)

                        if ((questionNdx == null) || (questionNdx.Value == 0))
                        {
                            questionNdx = 1;
                        } // endif ((questionNdx == null) || (questionNdx.Value == 0))
                        QuestionNdx = questionNdx.Value;
                        TempData.Keep("QuestionNdx");

                        ExamQuestion = await _context.ExamQuestions
                            .Include(eq => eq.Question)
                            .ThenInclude(q => q.Options)
                            .Where(eq => eq.ExamQuestionOrder == questionNdx)
                            .FirstOrDefaultAsync<ExamQuestion>();

                        QuestionViewModel = new QuestionViewModel
                        {
                            QuestionId = ExamQuestion.QuestionId,
                            ExamQuestionOrder = ExamQuestion.ExamQuestionOrder,
                            QuestionText = ExamQuestion.Question.QuestionText,
                            IsSingleSelection = ExamQuestion.Question.IsSingleSelection,
                            Options = new List<OptionViewModel>()
                        };

                        ExamViewModel = new ExamViewModel
                        {
                            ExamId = Exam.ExamId,
                            ExamName = Exam.ExamName,
                            Questions = new List<QuestionViewModel>()
                        };

                        ExamViewModel.Questions.Add(QuestionViewModel);
                        ExamViewModel.ExamUserViewModel = ExamUserViewModel;

                        List<AnswerOption> answerOptions = _context.AnswerOptions
                            .Where(ao => ao.QuestionId == ExamQuestion.QuestionId)
                            .ToList<AnswerOption>();
                        foreach (AnswerOption ao in answerOptions)
                        {
                            OptionViewModel ovm = new OptionViewModel
                            {
                                OptionId = ao.OptionId,
                                OptionText = ao.OptionText,
                                IsCorrect = ao.IsCorrect
                            };
                            ovm.UserExamOptionViewModel = new UserExamOptionViewModel
                            {
                                UserExamOptionId = ExamUserViewModel.ExamUserId,
                                UserExamId = 0,
                                OptionId = ao.OptionId,
                                IsSelected = false
                            };
                            QuestionViewModel.Options.Add(ovm);
                        } 

                    }
                    else
                    {
                        Message = String.Format("Error: Exam with Identifier, {0}, was not found.", examId);
                    } 
                } 
                else
                {
                    Message = String.Format("Error: Exam with Identifier, {0}, was not found.", examId);
                } 

            }
            else
            {
                Message = "Error: Login is required.";
            }

        } 


        public async Task<IActionResult> OnPostAsync(QuestionViewModel QuestionViewModel)
        {
            var t = QuestionViewModel;
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Attach(Question).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {

            }

            return RedirectToPage("./Index");

        } 
    } 
} 

cs html файл:

@page "{examId:int?}"
@model VerityLearn.WebUI.Pages.Questions.StudentQuestionsModel
@{
    ViewData["Title"] = "StudentQuestions";
}

@if (String.IsNullOrEmpty(@Model.Message))
{
    <div class="row" style="background-color: #5D2685; color: #FFFF80;">
        <div class="col-md-6">
            <h4>Exam: @Html.DisplayFor(model => model.Exam.ExamName)</h4>
        </div>
        <div class="col-md-6">
            <h4>Course: @Html.DisplayFor(model => model.Exam.Course.CourseName)</h4>
        </div>
    </div>
    <br />
    <br />

    <div>
        <h4>Question</h4>
        <hr />

        <form method="post">

            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Question.QuestionId" />
            <div class="form-group">
                <label asp-for="Question.QuestionText" class="control-label"></label>
                <input asp-for="Question.QuestionText" class="form-control" />
                <span asp-validation-for="Question.QuestionText" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Question.KeyToAnswer" class="control-label"></label>
                <input asp-for="Question.KeyToAnswer" class="form-control" />
                <span asp-validation-for="Question.KeyToAnswer" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Question.IsSingleSelection" class="control-label"></label>
                <input asp-for="Question.IsSingleSelection" class="form-control" />
                <span asp-validation-for="Question.IsSingleSelection" class="text-danger"></span>
            </div>



            <label asp-for="QuestionViewModel.QuestionText" class="control-label"></label>

            @if (Model.QuestionViewModel.IsSingleSelection.Value)
            {
                <p>Select the correct option.</p>

                @foreach (OptionViewModel opt in Model.QuestionViewModel.Options)
                {
                    <input type="radio" name="option" value="@opt.UserExamOptionViewModel.IsSelected"><label for=" @opt.OptionText">&nbsp;&nbsp; @opt.OptionText </label><br />
                }
            }
            else
            {
                <p>Select the correct options (More than one).</p>
                @foreach (OptionViewModel opt in Model.QuestionViewModel.Options)
                {
                    <input type="checkbox" name="option" value="@opt.UserExamOptionViewModel.IsSelected"><label for=" @opt.OptionText">&nbsp;&nbsp;  @opt.OptionText </label><br />
                }
            }

            @{
                var prevDisabled = (Model.QuestionNdx <= 1) ? "disabled" : "";
                var nextDisabled = (Model.QuestionNdx >= Model.NbrOfExamQuestions) ? "disabled" : "";
            }

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

            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>

    </div>

}
else
{
<div class="row" style="background-color: #5D2685; color: #FFFF80;">
    <div class="col-md-6">
        <h4>@Model.Message</h4>
    </div>
</div>
}

ОБНОВЛЕНИЕ (5 / 11/2020):

Я упростил вид следующим образом:

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

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

Я вижу 4 варианта, но все они выбраны. Я ожидаю, что будет выбран только один переключатель за раз. Я все равно нажимаю Далее

enter image description here

Теперь я вижу, что текст параметров привязан. Вопрос: Как определить, какой из них выбран?

enter image description here

1 Ответ

1 голос
/ 11 мая 2020

Проблема в том, что вы используете сложную модель, но ваш HTML не указывает на то, что модель сложная. Я создал демонстрацию , чтобы выделить проблему:

Здесь Student содержит Address и List<Subject> (это сложная модель)

public class Subject 
{
    public string SubjectName { get; set; }
}

public class Address
{
    public string StreetAddress { get; set; }
}

public class Student
{
    public string Name { get; set; }
    public Address HomeAddress { get; set; }
    public List<Subject> Subjects { get; set; }
}

Когда отображая сложную модель в форме, вам нужно использовать всю модель во входном теге html, таким образом ASP. NET MVC ModelBinder знает, как привязать ваш HTML входные данные для вашей серверной модели.

В приведенной выше демонстрации есть 3 входа, я использую HTML.TextBoxFor, который является более старым эквивалентом из asp-for Tag Helper.

Вход 1:

@Html.TextBoxFor(model => model.Name) 

Создает следующее HTML:

<input id="Name" name="Name" type="text" value="John Smith">

Связывание модели использует name и связывает указанный выше ввод с Student.Name

Вход 2

@Html.TextBoxFor(model => model.HomeAddress.StreetAddress) 

Создает следующее HTML:

<input id="HomeAddress_StreetAddress" name="HomeAddress.StreetAddress" type="text" value="some address">

Здесь Связывание модели знает, что этот вход должен быть привязан к Student.HomeAddress.StreetAddress, потому что это то, что указывает имя входа

Вход 3

@Html.TextBoxFor(model => model.Subjects[0].SubjectName) 

Создает следующее HTML:

<input id="Subjects_0__SubjectName" name="Subjects[0].SubjectName" type="text" value="Math">

Связывание модели свяжет ab введите Student.Subjects[0].SubjectName

См. эту статью для получения дополнительной информации.


В вашем примере Model Binder не имеет возможности знайте, что option принадлежит QuestionViewModel.Options, потому что вы не указываете его в имени ввода:

<input type="checkbox" name="option" value="@opt.UserExamOptionViewModel.IsSelected">
...