Dynamic MVC RadioButton Group Selected Answers

2019-06-10 05:47发布

I have a dynamically populated list of questions and answers.

Two problems:

  1. The questions and answers do not display after postback
  2. The selected answers are missing

Viewmodel

public class RegistrationViewModel : RegisterExternalLoginModel
{
    //...etc...
    public Question Question { get; set; }
    public Answer Answer { get; set; }
    public List<Question> Questions { get; set; }
    public IList<Answer> PossibleAnswers { get; set; }
    public List<SelectedAnswer> SelectedAnswers { get; set; }
    public IList<SelectedAnswer> PreviousAnswers 
    { 
        set 
        { 
            foreach(Question q in Questions)
            {
                q.SelectedAnswers = value.Where(t => t.questionId == q.objectId).ToList() ;
            }
        } 
    }
}

Selected Answer

 public Answer SelectedAnswer 
    { 
        get 
        {
            if (SelectedAnswers != null && SelectedAnswers.Count > 0)
            {
                var answers = SelectedAnswers.Where(t => t.questionId == objectId);
                if (answers.Count() == 1) 
                {
                    var result = Answers.Where(t => t.objectId == answers.First().answerId).First();
                    return result;
                }
            }
            return null;
        } 
    }

ActionResult

 public ActionResult CreateQuestions()
    {
        RegistrationViewModel vm = new RegistrationViewModel();
        IQFacade facade = new QFacade(CreateUserContext(true));

        //Questions and Answers
        vm.Questions = facade.GetQuestions().ToList();
        vm.PossibleAnswers = facade.GetPossibleAnswers();

        return View(vm);

    }

Post

 [HttpPost]
 public ActionResult CreateQuestions(RegistrationViewModel vm)
    {
        var context = CreateUserContext(true);

            try{
                IQFacade f = new QFacade(context);
                f.CreateSomething(vm.User.name, vm.etc, vm.SelectedAnswers);//Need all the answers here, but null
            }
            catch (Exception ex)
            {
                //error stuff, etc...
                return View(vm);//the questions do not appear after this point. Do I need to bind them again from GetQuestions or shouldn't they still be a part of the vm object that I am returning?
            }
        }

        return RedirectToAction("Index");
    }

In the views, I am using an Editor Template

 @Html.EditorFor(x => x.Questions)

Template

  @foreach (var possibleAnswer in Model.Answers)
{
    <div class="radio">
        @Html.RadioButtonFor(question => question.SelectedAnswer, possibleAnswer.objectId, new { id = possibleAnswer.objectId })

        <label for="@possibleAnswer.objectId">@possibleAnswer.text <span>@possibleAnswer.value</span></label> <p>@possibleAnswer.description</p>
    </div>
}

Everything works the first time, but not after the postback. I have read through the dozens of similar SO posts. What am I missing?

1条回答
Bombasti
2楼-- · 2019-06-10 06:00

Based on comments, your models should be something like (not sure of all your model properties so I'm making some assumptions here)

public class QuestionVM
{
  public int ID { get; set; } // for binding
  public string Text { get; set; }
  [Required]
  public int? SelectedAnswer { get; set; } // for binding
  public IEnumerable<Answer> PossibleAnswers { get; set; }
}

public class RegistrationViewModel : RegisterExternalLoginModel
{
  public RegistrationViewModel()
  {
    Questions = new List<QuestionVM>();
  }
  //...etc...
  public List<QuestionVM> Questions { get; set; }
}

GET method

public ActionResult CreateQuestions()
{
    RegistrationViewModel vm = new RegistrationViewModel();
    .....
    // Populate the questions and answers
    var questions = facade.GetQuestions().ToList();
    var answers = facade.GetPossibleAnswers();
    foreach (var question in questions)
    {
      QuestionVM qvm = new QuestionVM();
      qvm.ID = question.ID;
      qvm.Test = question.Text;
      // Add possible answers for the question
      qvm.PossibleAnswers = answers.Where(a => a.QuestionID ==  question.ID);
      // If loading existing questions/answers for existing user, also set value of current SelectedAnswer so its selected by default in the view
      vm.Questions.Add(qvm);
    }
    return View(vm);
}

View

@model YourAssembly.RegistrationViewModel
....

@for(int i = 0; i < Model.Questions.Count; i++)
{
  @Html.HiddenFor(m > m.Questions[i].ID) // for binding
  @Html.DisplayFor(m > m.Questions[i].Text)
  foreach(var answer in Model.Questions[i].PossibleAnswers)
  {
    @Html.RadioButtonFor(m => m.Questions[i].SelectedAnswer, answer.ID, new { id = answer.ID})
    <label for="@answer.ID">answer.Text</label>
  }
}

POST method

[HttpPost]
public ActionResult CreateQuestions(RegistrationViewModel vm)
{
  if (!ModelState.IsValid)
  {
    // You need to rebuild the question text and possible answers because these are not posted back
    return View(vm);
  }
  // Your model is now populated with the ID of each question and the selected answer which can be saved to the database

Note you could add hidden inputs for the question and answer text values so they post back, but generally its better performance to reload in your controller (if you include the correct data annotations and include client side validation, the model should always be valid on postback anyway)

查看更多
登录 后发表回答