0

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?

tereško
  • 58,060
  • 25
  • 98
  • 150
User970008
  • 1,135
  • 3
  • 20
  • 49
  • 1. Your property `SelectedAnswer` does not have a setter. 2. Your trying to bind to a complex property (`Answer`) but radio button groups only post back a single value. You should have `public int SelectedAnswer { get; set; }`. –  Sep 13 '14 at 04:01
  • What about the questions? Do I need to make another database call GetQuestions() in the error catch or should that remain populated after the post since I am passing it through? – User970008 Sep 14 '14 at 12:09
  • A lot of what you doing here is confusing, for example you have a collection `SelectedAnswers` - can there be more than one answer per question? You should also have simple getters and setter on you view model properties and populate them in the controller, not in the getter. Since you don't appear to be rendering any input controls for the questions, then they wont be posted back and you will need to get them again (but that probably better performance wise than posing back a whole lot of extra inputs just in case an exception is thrown. –  Sep 14 '14 at 12:44
  • There is only one answer per question. SelectedAnswers is the list of all of the answers, and there are 15 questions and answers. The setter for SelectedAnswer is the auto-generated entity. – User970008 Sep 14 '14 at 21:41
  • In you model you also have `public Question Question` and `public List Questions`. Again this does not make sense. What I think you want is: (1). you present the user with a list of questions (2). each question has a number of possible answers related to that question (3). the user selects an answer for each question (4). you want to store the users selected answer to each question in a database. Is that correct? –  Sep 14 '14 at 21:53
  • Yes, that is correct. – User970008 Sep 14 '14 at 21:54

1 Answers1

1

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)

  • Thanks, I had to modify things a bit because the SelectedAnswers and Questions are populated from auto-generated EF entities. But I got it working. – User970008 Sep 15 '14 at 02:21