Группа переключателей ASP.NET MVC 5

Я начинаю свой первый проект ASP.NET MVC, поэтому у меня есть один простой вопрос. У меня есть следующий код:

foreach(var question in Model.GeneralQuestions)
{
    <div class = "well">
        <h3>
            <strong>@question.QuestionString</strong>
        </h3>
        @foreach (var answer in question.PossibleAnswers)
        {
            @Html.RadioButtonFor(model => question.QuestionString, answer.Answer)
            @Html.Label(answer.Answer)
            <br />
        }
    </div>
}

Все вопросы в Model.GeneralQuestions уникальны, поэтому переключатели должны быть разделены на группы по атрибуту name (для каждого вопроса одна группа переключателей). Но этот код создает только одну группу, поэтому, когда я отвечаю на второй вопрос, сначала снимается. Что мне нужно изменить?

ИЗМЕНИТЬ
Моя модель выглядит следующим образом:

public class StudentViewModel
{
    public Student Student { get; set; }
    public List<Question> GeneralQuestions { get; set; }
    public List<SubjectQuestions> SubjectQuestions { get; set; }
}
public class Student
{
    public int StudentID { get; set; }
    public string Index { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }

    public virtual ICollection<Subject> Subjects { get; set; }
}
public class Question
{
    public int QuestionID { get; set; }
    public string QuestionString { get; set; }
    public bool IsAssociatedWithSubject { get; set; }

    public virtual ICollection<PossibleAnswer> PossibleAnswers { get; set; }
    public virtual ICollection<Results> Results { get; set; }
}
public class SubjectQuestions
{
    public Subject Subject { get; set; }
    public List<Question> Questions { get; set; }
}
public class Results
{
    public int ResultsID { get; set; }
    public int QuestionID { get; set; }
    public int? SubjectID { get; set; }
    public int PossibleAnswerID { get; set; }

    public virtual Question Question { get; set; }
    public virtual PossibleAnswer PossibleAnswer { get; set; }
    public virtual Subject Subject { get; set; }
}

В одном экземпляре StudentViewModel я сохраняю одного студента и все вопросы, на которые он должен ответить (как общие, так и связанные с предметами, которые он изучает) и передать его для просмотра. В виду, я задал все вопросы в одной форме, и они все типа радио. Итак, может ли кто-нибудь помочь мне с группировкой переключателей и правильной отправкой этой формы?

Ответы

Ответ 1

В вашем коде есть ряд проблем, включая создание дубликата id (недопустимый html), создание дубликатов атрибутов name (поэтому вы создаете только одну группу, но, что более важно, это не даст вам привязки к модели, когда вы отправляете назад), и вы вообще не привязываетесь к действительному свойству.

Вам нужно будет создать модели представлений для представления того, что вы хотите отображать, а также редактировать и генерировать радиокнопки в цикле for (или с помощью EditorTemplate), чтобы они были правильно названы с индексаторами.

Просмотр моделей

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<AnswerVM> PossibleAnswers { get; set; }
}

public class SubjectVM
{
  public int? ID { get; set; }
  [DisplayFormat(NullDisplayText = "General")]
  public string Name { get; set; }
  public List<QuestionVM> Questions { get; set; }
}

public class AnswerVM
{
  public int ID { get; set; }
  public string Text { get; set; }
}

public class StudentVM
{
  public int ID { get; set; }
  public string Name { get; set; }
  // plus any other properties of student that you want to display in the view
  public List<SubjectVM> Subjects { get; set; }
}

Вид

@model YourAssembly.StudentVM
@using(Html.BeginForm())
{
  @Html.HiddenFor(m => m.ID)
  @Html.DisplayFor(m => m.Name)
  for(int i = 0; i < Model.Subjects.Count; i++)
  {
    @Html.HiddenFor(m => m.Subjects[i].ID)
    @Html.DisplayFor(m => m.Subjects[i].Name) // will display "General" if no name
    for (int j = 0; j < Model.Subjects[i].Questions.Count; j++)
    {
      @Html.HiddenFor(m => m.Subjects[i].Questions[j].ID)
      @Html.DisplayFor(m => m.Subjects[i].Questions[j].Text)
      foreach(var answer in Model.Subjects[i].Questions[j].PossibleAnswers )
      {
        <div>
          @Html.RadioButtonFor(m => m.Subjects[i].Questions[j].SelectedAnswer, answer.ID, new { id = answer.ID})
          <label for="@answer.ID">@answer.Text</label>
        </div>
      }
      @Html.ValidationMessageFor(m => m.Subjects[i].Questions[j].SelectedAnswer)
    }
  }
  <input type="submit" value="save" />
}

контроллер

public ActionResult Edit(int ID)
{
  StudentVM model = new StudentVM();
  // populate your view model with values from the database
  return View(model);
}

[HttpPost]
public ActionResult Edit(StudentVM model)
{
  // save and redirect
}

Примечание. Я немного смущен структурой базы данных, подразумеваемой вашими моделями (например, почему вам нужны отдельные модели для Question и SubjectQuestion, когда значение null для SubjectID идентифицирует его как "Общее" вопрос). Я предлагаю вам начать с жесткого кодирования некоторых значений в методе GET, чтобы увидеть, как он работает и отправляет сообщения.

StudentVM model = new StudentVM();
model.ID = 1;
model.Name = "bambiinela";
model.Subjects = new List<SubjectVM>()
{
  new SubjectVM()
  {
    Questions = new List<QuestionVM>()
    {
      new QuestionVM()
      {
        ID = 1,
        Text = "Question 1",
        SelectedAnswer = ?, // set this if you want to preselect an option
        PossibleAnswers = new List<AnswerVM>()
        {
          new AnswerVM()
          {
            ID = 1,
            Text = "Answer A"
          },
          new AnswerVM()
          {
            ID = 1,
            Text = "Answer B"
          }
        }
      },
      new QuestionVM()
      {
        ID = 2,
        Text = "Question 2",
        PossibleAnswers = new List<AnswerVM>()
        {
          // similar to above
        }
      }
    }
  },
  new SubjectVM()
  {
    ID = 1,
    Name = "Math",
    Questions = new List<QuestionVM>()
    {
      // similar to above
    }
  }
};

Когда вы отправляете сообщение, модель заполняется идентификатором выбранного ответа для каждого вопроса в каждом предмете. Обратите внимание на использование DisplayFor() для некоторых свойств. Они не будут отправлять сообщения, поэтому вам нужно будет повторно заполнить эти свойства, если вы вернете представление (например, ModelState недействительно). В качестве альтернативы вы можете создать текстовое поле только для чтения или добавить скрытый ввод для этих свойств. Я также предлагаю вам проверить созданный HTML-код, в частности атрибуты имени, которые будут выглядеть примерно как

<input type="radio" name="Subjects[0].Questions[0].SelectedAnswer" ...

чтобы дать вам представление о том, как коллекции привязаны к вашей модели при отправке назад.

Ответ 2

Хитрость заключается в использовании выражения (первый параметр для Html.RadioButtonFor), который содержит значение, которое изменяется на группу радиокнопки. В вашем случае это будет индекс в списке вопросов.

Вот пример кода:

 @for (int i = 0; i < Model.GeneralQuestions.Count; i++)
 {
     var question = Model.GeneralQuestions[i];
     @Html.Label(question.QuestionString)
     <br />
     foreach (var answer in question.PossibleAnswers)
     {
         @Html.RadioButtonFor(model => 
           Model.GeneralQuestions[i].SelectedAnswerId, answer.Id)
         @Html.Label(answer.Answer)
         <br />
     }
 }

В результате создается следующий HTML:

<label for="Q1">Q1</label>
<br />
<input id="GeneralQuestions_0__SelectedAnswerId" 
  name="GeneralQuestions[0].SelectedAnswerId" type="radio" value="1" />
<label for="A01">A01</label>
<br />
<input id="GeneralQuestions_0__SelectedAnswerId" 
  name="GeneralQuestions[0].SelectedAnswerId" type="radio" value="2" />
<label for="A02">A02</label>
<br />
<label for="Q2">Q2</label>
<br />
<input id="GeneralQuestions_1__SelectedAnswerId" 
  name="GeneralQuestions[1].SelectedAnswerId" type="radio" value="11" />
<label for="A11">A11</label>
<br />
<input id="GeneralQuestions_1__SelectedAnswerId" 
  name="GeneralQuestions[1].SelectedAnswerId" type="radio" value="12" />
<label for="A12">A12</label>
<br />

И для полноты, вот приведенная версия используемых моделей:

public class StudentViewModel
{
    public List<Question> GeneralQuestions { get; set; }
}

public class Question
{
    public int QuestionId { get; set; }
    public string QuestionString { get; set; }
    public ICollection<PossibleAnswer> PossibleAnswers { get; set; }
    public int SelectedAnswerId { get; set; }
}

public class PossibleAnswer
{
    public int Id { get; set; }
    public string Answer { get; set; }
}

и вот код из метода действия:

return View(new StudentViewModel
{
    GeneralQuestions =
        new List<Question>
        {
            new Question
            {
                QuestionString = "Q1",
                PossibleAnswers =
                    new[]
                    {
                        new PossibleAnswer {Id = 1, Answer = "A01"},
                        new PossibleAnswer {Id = 2, Answer = "A02"}
                    }
            },
            new Question
            {
                QuestionString = "Q2",
                PossibleAnswers =
                    new[]
                    {
                        new PossibleAnswer {Id = 11, Answer = "A11"},
                        new PossibleAnswer {Id = 12, Answer = "A12"}
                    }
            },
        }
});