Поделиться через


Использование ViewData и реализация классов ViewModel

от Корпорации Майкрософт

Загрузить PDF-файл

Это шаг 6 бесплатного руководства по приложению "NerdDinner" , в которых показано, как создать небольшое, но полное веб-приложение с помощью ASP.NET MVC 1.

На шаге 6 показано, как включить поддержку расширенных сценариев редактирования форм, а также рассматриваются два подхода, которые можно использовать для передачи данных из контроллеров в представления: ViewData и ViewModel.

Если вы используете ASP.NET MVC 3, рекомендуем следовать руководствам по начало работы С MVC 3 или MVC Music Store.

Шаг 6. NerdDinner: ViewData и ViewModel

Мы рассмотрели ряд сценариев post формы и обсудили, как реализовать поддержку создания, обновления и удаления (CRUD). Теперь мы продолжим реализацию DinnersController и включим поддержку расширенных сценариев редактирования форм. При этом мы рассмотрим два подхода, которые можно использовать для передачи данных из контроллеров в представления: ViewData и ViewModel.

Передача данных от контроллеров в View-Templates

Одной из определяющих характеристик шаблона MVC является строгое "разделение задач", которое помогает применяться между различными компонентами приложения. Модели, контроллеры и представления имеют четко определенные роли и обязанности и взаимодействуют друг с другом четко определенными способами. Это помогает повысить удобство тестирования и повторное использование кода.

Когда класс Controller решает отрисовку HTML-ответа клиенту, он отвечает за явную передачу в шаблон представления всех данных, необходимых для отрисовки ответа. Шаблоны представлений никогда не должны выполнять извлечение данных или логику приложения. Вместо этого они должны ограничиваться только кодом отрисовки, который управляется моделью или данными, передаваемыми ему контроллером.

Сейчас данные модели, передаваемые классом DinnersController в наши шаблоны представлений, являются простыми и простыми: список объектов Dinner в случае Index() и один объект Dinner в случае Details(), Edit(), Create() и Delete(). По мере добавления дополнительных возможностей пользовательского интерфейса в наше приложение нам часто требуется передать не только эти данные, чтобы отобразить HTML-ответы в наших шаблонах представлений. Например, может потребоваться изменить поле "Страна" в представлениях "Изменить" и "Создать" с текстового поля HTML на раскрывающийся список. Вместо жесткого написания раскрывающегося списка названий стран и регионов в шаблоне представления мы можем создать его из списка поддерживаемых стран и регионов, которые мы заполняем динамически. Нам потребуется способ передачи объекта Dinner и списка поддерживаемых стран и регионов от нашего контроллера в наши шаблоны представлений.

Давайте рассмотрим два способа достижения этой цели.

Использование словаря ViewData

Базовый класс Controller предоставляет свойство словаря ViewData, которое можно использовать для передачи дополнительных элементов данных из контроллеров в представления.

Например, для поддержки сценария, в котором мы хотим изменить текстовое поле "Страна" в нашем представлении правки с текстового поля HTML на раскрывающийся список, мы можем обновить наш метод действия Edit(), чтобы передать (в дополнение к объекту Dinner) объект SelectList, который можно использовать в качестве модели раскрывающегося списка "Страны".

//
// GET: /Dinners/Edit/5

[Authorize]
public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    ViewData["Countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country);

    return View(dinner);
}

Конструктор selectList выше принимает список стран и регионов для заполнения раскрывающегося списка, а также выбранное в настоящее время значение.

Затем мы можем обновить шаблон представления Edit.aspx, чтобы использовать вспомогательный метод Html.DropDownList() вместо вспомогательного метода Html.TextBox(), который мы использовали ранее:

<%= Html.DropDownList("Country", ViewData["Countries"] as SelectList) %>

Приведенный выше вспомогательный метод Html.DropDownList() принимает два параметра. Первый — это имя выходного html-элемента формы. Второй — это модель SelectList, переданная через словарь ViewData. Мы используем ключевое слово C# "as", чтобы привести тип в словаре как SelectList.

Теперь, когда мы запустите приложение и перейдем к URL-адресу /Dinners/Edit/1 в нашем браузере, мы увидим, что пользовательский интерфейс редактирования был обновлен для отображения раскрывающегося списка стран и регионов вместо текстового поля:

Снимок экрана: изменение пользовательского интерфейса с раскрывающимся списком стран и регионов, выделенных красной стрелкой.

Так как мы также отрисовываем шаблон представления Edit с помощью метода Edit HTTP-POST (в сценариях, когда возникают ошибки), мы также хотим обновить этот метод, чтобы добавить SelectList в ViewData, когда шаблон представления отображается в сценариях ошибок:

//
// POST: /Dinners/Edit/5

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    try {
    
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
    
        ModelState.AddModelErrors(dinner.GetRuleViolations());

        ViewData["countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country);

        return View(dinner);
    }
}

И теперь наш сценарий редактирования DinnersController поддерживает DropDownList.

Использование шаблона ViewModel

Подход к словарю ViewData обеспечивает довольно быструю и простую реализацию. Некоторые разработчики не любят использовать словари на основе строк, так как опечатки могут привести к ошибкам, которые не будут перехватываться во время компиляции. Нетипизированный словарь ViewData также требует использования оператора "as" или приведения при использовании строго типизированного языка, такого как C# в шаблоне представления.

Альтернативным подходом, который можно использовать, часто называют шаблоном ViewModel. При использовании этого шаблона мы создаем строго типизированные классы, оптимизированные для конкретных сценариев представления и предоставляющие свойства для динамических значений или содержимого, необходимых для шаблонов представлений. Затем классы контроллера могут заполнить и передать эти классы, оптимизированные для представления, в шаблон представления для использования. Это обеспечивает безопасность типов, проверку во время компиляции и редактор intellisense в шаблонах представлений.

Например, чтобы включить сценарии редактирования формы ужина, можно создать класс DinnerFormViewModel, как показано ниже, который предоставляет два строго типизированных свойства: объект Dinner и модель SelectList, необходимую для заполнения раскрывающегося списка "Страны":

public class DinnerFormViewModel {

    // Properties
    public Dinner     Dinner    { get; private set; }
    public SelectList Countries { get; private set; }

    // Constructor
    public DinnerFormViewModel(Dinner dinner) {
        Dinner = dinner;
        Countries = new SelectList(PhoneValidator.AllCountries, dinner.Country);
    }
}

Затем мы можем обновить метод действия Edit(), чтобы создать DinnerFormViewModel с помощью объекта Dinner, который мы извлекаем из репозитория, а затем передать его в наш шаблон представления:

//
// GET: /Dinners/Edit/5

[Authorize]
public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);
    
    return View(new DinnerFormViewModel(dinner));
}

Затем мы обновим наш шаблон представления таким образом, чтобы он ожидал "DinnerFormViewModel" вместо объекта "Dinner", изменив атрибут "наследует" в верхней части страницы edit.aspx следующим образом:

Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerFormViewModel>

После этого intellisense свойства "Model" в нашем шаблоне представления будет обновлена, чтобы отразить объектную модель типа DinnerFormViewModel, который мы его передаем:

Снимок экрана: окно редактора кода с раскрывающимся списком и элементом списка Dinner, выделенным синим прямоугольником.

Снимок экрана: окно редактора кода с раскрывающимся списком и элементом списка адресов, выделенным серым пунктиром прямоугольником.

Затем мы можем обновить код представления, чтобы отработать его. Ниже обратите внимание, что мы не изменяем имена входных элементов, которые мы создаем (элементы формы по-прежнему будут называться "Title", "Country"), но мы обновляем вспомогательные методы HTML для получения значений с помощью класса DinnerFormViewModel:

<p>
    <label for="Title">Dinner Title:</label>
    <%= Html.TextBox("Title", Model.Dinner.Title) %>
    <%=Html.ValidationMessage("Title", "*") %>
</p>

<p>
    <label for="Country">Country:</label>
    <%= Html.DropDownList("Country", Model.Countries) %>                
    <%=Html.ValidationMessage("Country", "*") %>
</p>

Мы также обновим наш метод Edit post, чтобы использовать класс DinnerFormViewModel при отрисовке ошибок:

//
// POST: /Dinners/Edit/5

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    try {
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
        ModelState.AddModelErrors(dinner.GetRuleViolations());

        return View(new DinnerFormViewModel(dinner));
    }
}

Мы также можем обновить методы действий Create() для повторного использования того же класса DinnerFormViewModel , чтобы включить в них раскрывающийся список "Страны". Ниже приведена реализация HTTP-GET:

//
// GET: /Dinners/Create

public ActionResult Create() {

    Dinner dinner = new Dinner() {
        EventDate = DateTime.Now.AddDays(7)
    };

    return View(new DinnerFormViewModel(dinner));
}

Ниже приведена реализация метода СОЗДАНИЯ HTTP-POST:

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {

    if (ModelState.IsValid) {

        try {
            dinner.HostedBy = "SomeUser";

            dinnerRepository.Add(dinner);
            dinnerRepository.Save();

            return RedirectToAction("Details", new { id=dinner.DinnerID });
        }
        catch {
            ModelState.AddModelErrors(dinner.GetRuleViolations());
        }
    }

    return View(new DinnerFormViewModel(dinner));
}

Теперь экраны "Изменить" и "Создать" поддерживают раскрывающиеся списки для выбора страны или региона.

Пользовательские классы ViewModel

В приведенном выше сценарии класс DinnerFormViewModel напрямую предоставляет объект модели Dinner в качестве свойства, а также вспомогательное свойство модели SelectList. Этот подход подходит для сценариев, в которых пользовательский интерфейс HTML, который мы хотим создать в нашем шаблоне представления, относительно близко соответствует объектам модели предметной области.

В сценариях, где это не так, можно использовать один из вариантов, который можно использовать, — создать класс ViewModel в пользовательской форме, объектная модель которого более оптимизирована для использования представлением и может выглядеть совершенно не так, как базовый объект модели предметной области. Например, он может потенциально предоставлять различные имена свойств и (или) агрегатные свойства, собранные из нескольких объектов модели.

Классы ViewModel пользовательской формы можно использовать как для передачи данных из контроллеров в представления для отрисовки, так и для обработки данных формы, передаваемых обратно в метод действия контроллера. В этом более позднем сценарии метод действия может обновить объект ViewModel данными, опубликованными в форме, а затем использовать экземпляр ViewModel для сопоставления или извлечения фактического объекта модели предметной области.

Классы ViewModel с настраиваемыми формами обеспечивают большую гибкость и позволяют исследовать код отрисовки в шаблонах представлений или код размещения форм в методах действий, которые становятся слишком сложными. Это часто свидетельствует о том, что модели предметной области не полностью соответствуют создаваемому пользовательскому интерфейсу и что может помочь промежуточный класс ViewModel пользовательской формы.

Следующий шаг

Теперь давайте рассмотрим, как мы можем использовать части и master-страницы для повторного использования пользовательского интерфейса и совместного использования в приложении.