Реплицированное поведение вложенных повторителей в MVC

Сегодня я решил попробовать MVC, и, хотя мне действительно нравится эта идея, мне было довольно сложно перейти от ASP.NET и понять некоторые базовые концепции, такие как использование foreach вместо вложенных повторителей.

Мне понадобилось несколько часов, чтобы придумать это решение, но оно кажется не совсем правильным. Может кто-нибудь объяснить, что не так с этим кодом, и как это правильно сделать. Вот мое решение:

По сути, это опрос, который состоит из нескольких вопросов, на каждый из которых есть несколько ответов. У меня есть таблицы в БД, которые представлены как строго типизированные сущности. Контроллер выглядит так:

public ActionResult Details(int id)
{
    return View(new Models.Entities().Questions.Where(r => r.PROMId == id));
}

и соответствующий вид, как это:

<% foreach (var question in Model) { %>

    <h3>Question <%: Array.IndexOf(Model.ToArray(), question) + 1 %></h3>
    <p><%: question.QuestionPart1 %></p>
    <p><%: question.QuestionPart2 %></p>
    <% var answers = new Surveys_MVC.Models.Entities().Answers.Where(r => r.QuestionId == question.QuestionId); %>
    <% foreach (var answer in answers) { %>
        <input type="radio" /><%: answer.Text %>
    <% } %>

<% } %>

Все отзывы приветствуются.

2 ответа

Решение

Что касается использования for циклы для поведения вложенных повторителей, я думаю, что это лучший способ сделать это в MVC. Но я бы посоветовал вам использовать специальные ViewModels.

ViewModel:

public class RadioQuestionListViewModel
{
    public IEnumerable<RadioQuestionViewModel> Questions {get;set;}
}

public class RadioQuestionViewModel
{
    public int QuestionNumber {get;set;}
    public string InputName {get;set;}
    public string QuestionPart1 {get;set;}
    public string QuestionPart2 {get;set;}
    public IEnumerable<RadioAnswerViewModel> PossibleAnswers {get;set;}
}
public class RadioAnswerViewModel
{
    public int AnswerId {get;set;}
    public string Text {get;set;}
}

контроллер:

public ActionResult Details(int id)
{
    var model = GetRadioQuestionListModelById(id);
    return View(model);
}

Посмотреть:

<% foreach (var question in Model) { %>

    <h3>Question <%: question.QuestionNumber %></h3>
    <p><%: question.QuestionPart1 %></p>
    <p><%: question.QuestionPart2 %></p>
    <% foreach (var answer in question.PossibleAnswers) { %>
        <%: Html.RadioButton(question.InputName, answer.AnswerId) %>
        <%: answer.Text %>
    <% } %>
<% } %>

Этот подход имеет несколько преимуществ:

  1. Это препятствует тому, чтобы ваш код представления зависел от ваших классов доступа к данным. Код представления должен отвечать только за решение, каким образом желаемая модель представления будет отображаться в HTML.
  2. Он сохраняет логику, не связанную с отображением, в коде вашего представления. Если вы позже решите разместить свои вопросы на странице и теперь будете показывать вопросы 11-20 вместо 1 - что угодно, вы можете использовать точно такое же представление, потому что контроллер позаботился о том, чтобы выяснить номера вопросов для отображения.
  3. Это облегчает избегать Array.IndexOf(Model.ToArray(), question) и база данных туда и обратно for Цикл, который может стать довольно дорогостоящим, если у вас есть несколько вопросов на странице.

И, конечно, ваши радиокнопки должны иметь имя и значение ввода, связанные с ними, иначе у вас не будет возможности получить эту информацию при отправке формы. Заставив контроллер решить, как генерируется входное имя, вы сделаете более очевидным, как Details метод соответствует вашему SaveAnswers метод.

Вот возможная реализация GetRadioQuestionListModelById:

public RadioQuestionListViewModel GetRadioQuestionListModelById(int id)
{
    // Make sure my context gets disposed as soon as I'm done with it.
    using(var context = new Models.Entities())
    {
        // Pull all the questions and answers out in a single round-trip
        var questions = context.Questions
            .Where(r => r.PROMId == id)
            .Select(r => new RadioQuestionViewModel
                {
                    QuestionPart1 = r.q.QuestionPart1,
                    QuestionPart2 = r.q.QuestionPart2,
                    PossibleAnswers = r.a.Select(
                        a => new RadioAnswerViewModel
                             {
                                AnswerId = a.AnswerId,
                                Text = a.Text
                             })
                })
            .ToList();
    }
    // Populate question number and name
    for(int i = 0; i < questions.Count; i++)
    {
        var q = questions[i];
        q.QuestionNumber = i;
        q.InputName = "Question_" + i;
    }
    return new RadioQuestionListViewModel{Questions = questions};
}

Я не знаю, лучше ли это, но вы можете создать помощника, чтобы сделать это для вас:

public static void Repeater<T>(this HtmlHelper html, IEnumerable<T> items, string cssClass, string altCssClass, string cssLast, Action<T, string> render)
        {
            if (items == null)
                return;
            var i = 0;
            foreach (var item in items)
            {
                i++;
                if (i == items.Count())
                    render(item, cssLast);
                else
                    render(item, (i % 2 == 0) ? cssClass : altCssClass);
            }
        }

Тогда вы можете назвать это так:

<%Html.Repeater(Model, "css", "altCss", "lastCss", (question, css) => { %>
    <h3>Question <%: Array.IndexOf(Model.ToArray(), question) + 1 %></h3>
    <p><%: question.QuestionPart1 %></p>
    <p><%: question.QuestionPart2 %></p>
    <% var answers = new Surveys_MVC.Models.Entities().Answers.Where(r => r.QuestionId == question.QuestionId); %>
    <% foreach (var answer in answers) { %>
        <input type="radio" /><%: answer.Text %>
    <% } %>

<% }); %>

Это имеет много силы, и выше, это просто общий пример. Вы можете прочитать больше здесь http://haacked.com/archive/2008/05/03/code-based-repeater-for-asp.net-mvc.aspx

Другие вопросы по тегам