Korzystanie z podejścia ViewData i implementowanie klas ViewModel

autor: Microsoft

Pobierz plik PDF

Jest to krok 6 bezpłatnego samouczka aplikacji "NerdDinner" , który zawiera instrukcje dotyczące tworzenia małej, ale kompletnej aplikacji internetowej przy użyciu ASP.NET MVC 1.

Krok 6 pokazuje, jak włączyć obsługę bardziej rozbudowanych scenariuszy edycji formularzy, a także omawia dwa podejścia, które mogą służyć do przekazywania danych z kontrolerów do widoków: ViewData i ViewModel.

Jeśli używasz ASP.NET MVC 3, zalecamy skorzystanie z samouczków Wprowadzenie With MVC 3 lub MVC Music Store .

NerdDinner Krok 6: ViewData i ViewModel

Omówiliśmy szereg scenariuszy publikowania formularzy i omówiliśmy sposób implementowania obsługi tworzenia, aktualizowania i usuwania (CRUD). Teraz zajmiemy się dalszą implementacją DinnersController i włączymy obsługę bardziej rozbudowanych scenariuszy edycji formularzy. W tym celu omówimy dwa podejścia, które mogą służyć do przekazywania danych z kontrolerów do widoków: ViewData i ViewModel.

Przekazywanie danych z kontrolerów do View-Templates

Jedną z charakterystycznych cech wzorca MVC jest ścisłe "rozdzielenie obaw", które pomaga wymusić między różnymi składnikami aplikacji. Modele, kontrolery i widoki mają dobrze zdefiniowane role i obowiązki oraz komunikują się między sobą w sposób dobrze zdefiniowany. Ułatwia to promowanie możliwości testowania i ponownego używania kodu.

Gdy klasa Controller zdecyduje się renderować odpowiedź HTML z powrotem do klienta, jest odpowiedzialna za jawne przekazanie do szablonu widoku wszystkich danych potrzebnych do renderowania odpowiedzi. Szablony widoków nigdy nie powinny wykonywać żadnych operacji pobierania danych lub logiki aplikacji — zamiast tego należy ograniczyć tylko kod renderowania, który jest kierowany z modelu/danych przekazanych do niego przez kontroler.

W tej chwili dane modelu przekazywane przez naszą klasę DinnersController do naszych szablonów widoków są proste i proste — lista obiektów kolacji w przypadku indeksu() i pojedynczego obiektu kolacji w przypadku Details(), Edit(), Create() i Delete(). W miarę dodawania większej liczby możliwości interfejsu użytkownika do naszej aplikacji często będziemy musieli przekazać więcej niż tylko te dane, aby renderować odpowiedzi HTML w szablonach widoków. Na przykład możemy zmienić pole "Kraj" w obszarze Edytuj i Utwórz widoki z bycia polem tekstowym HTML na listę rozwijaną. Zamiast trwale kodować listę rozwijaną nazw krajów i regionów w szablonie widoku, możemy wygenerować ją na podstawie listy obsługiwanych krajów i regionów, które wypełniamy dynamicznie. Będziemy potrzebować sposobu przekazywania zarówno obiektu Dinner , jak i listy obsługiwanych krajów i regionów z naszego kontrolera do szablonów widoków.

Przyjrzyjmy się dwo sposobom, w jaki możemy to osiągnąć.

Korzystanie ze słownika ViewData

Klasa podstawowa Controller uwidacznia właściwość słownika "ViewData", która może służyć do przekazywania dodatkowych elementów danych z kontrolerów do widoków.

Na przykład w celu obsługi scenariusza, w którym chcemy zmienić pole tekstowe "Kraj" w naszym widoku Edycji z bycia polem tekstowym HTML na listę rozwijaną, możemy zaktualizować metodę akcji Edit(), aby przekazać (oprócz obiektu Obiad) obiekt SelectList, który może być używany jako model listy rozwijanej "Kraje".

//
// 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);
}

Konstruktor powyższej listy SelectList akceptuje listę krajów i regionów, aby wypełnić listę rozwijaną za pomocą, a także aktualnie wybraną wartość.

Następnie możemy zaktualizować szablon widoku Edit.aspx, aby użyć metody pomocnika Html.DropDownList() zamiast metody pomocnika Html.TextBox() użytej wcześniej:

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

Metoda pomocnika Html.DropDownList() powyżej przyjmuje dwa parametry. Pierwszy to nazwa elementu formularza HTML do danych wyjściowych. Drugi to model "SelectList", który przekazaliśmy za pośrednictwem słownika ViewData. Używamy słowa kluczowego "as" języka C#, aby rzutować typ w słowniku jako SelectList.

Teraz, gdy uruchomimy aplikację i uzyskamy dostęp do adresu URL /Dinners/Edit/1 w przeglądarce, zobaczymy, że nasz interfejs użytkownika edycji został zaktualizowany, aby wyświetlić listę rozwijaną krajów i regionów zamiast pola tekstowego:

Zrzut ekranu przedstawiający edytowanie interfejsu użytkownika z listą rozwijaną krajów i regionów wyróżnionych czerwoną strzałką.

Ponieważ renderujemy również szablon Edytuj widok z metody edycji HTTP-POST (w scenariuszach, gdy wystąpią błędy), chcemy upewnić się, że ta metoda również zostanie zaktualizowana, aby dodać metodę SelectList to ViewData, gdy szablon widoku jest renderowany w scenariuszach błędów:

//
// 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);
    }
}

A teraz nasz scenariusz edycji DinnersController obsługuje listę DropDownList.

Korzystanie ze wzorca ViewModel

Podejście do słownika ViewData ma korzyść z bycia dość szybkim i łatwym w implementacji. Niektórzy deweloperzy nie lubią jednak używać słowników opartych na ciągach, ponieważ literówki mogą prowadzić do błędów, które nie zostaną przechwycone w czasie kompilacji. Słownik ViewData bez wpisywania wymaga również używania operatora "as" lub rzutowania w przypadku korzystania z silnie typizowanego języka, takiego jak C# w szablonie widoku.

Alternatywną metodą, której moglibyśmy użyć, jest jeden z często określany jako wzorzec "ViewModel". W przypadku korzystania z tego wzorca tworzymy silnie typizowane klasy, które są zoptymalizowane pod kątem naszych konkretnych scenariuszy wyświetlania i które uwidaczniają właściwości dla wartości dynamicznych/zawartości wymaganych przez szablony widoku. Nasze klasy kontrolera mogą następnie wypełniać i przekazywać te klasy zoptymalizowane pod kątem widoku do szablonu widoku do użycia. Umożliwia to bezpieczeństwo typów, sprawdzanie czasu kompilacji i funkcję intellisense edytora w szablonach widoków.

Aby na przykład włączyć scenariusze edytowania formularzy kolacji, możemy utworzyć klasę "DinnerFormViewModel", jak poniżej, która uwidacznia dwie silnie typizowane właściwości: obiekt Kolacja i model SelectList potrzebny do wypełnienia listy rozwijanej "Kraje":

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);
    }
}

Następnie możemy zaktualizować metodę akcji Edit(), aby utworzyć model DinnerFormViewModel przy użyciu obiektu Dinner pobranego z naszego repozytorium, a następnie przekazać go do szablonu widoku:

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

[Authorize]
public ActionResult Edit(int id) {

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

Następnie zaktualizujemy szablon widoku, aby oczekiwał obiektu "DinnerFormViewModel" zamiast obiektu "Kolacja", zmieniając atrybut "dziedziczy" u góry strony edit.aspx w następujący sposób:

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

Gdy to zrobimy, funkcja intellisense właściwości "Model" w szablonie widoku zostanie zaktualizowana w celu odzwierciedlenia modelu obiektów typu DinnerFormViewModel, który przekazujemy:

Zrzut ekranu przedstawiający okno edytora kodu z listą rozwijaną i elementem listy kolacja wyróżnionym niebieskim prostokątem.

Zrzut ekranu przedstawiający okno edytora kodu z listą rozwijaną i elementem listy Adres wyróżnionym szarym prostokątem kropkowym.

Następnie możemy zaktualizować kod widoku, aby go wyłączyć. Zwróć uwagę na to, że nie zmieniamy nazw tworzonych elementów wejściowych (elementy formularza nadal będą mieć nazwę "Title", "Country") — ale aktualizujemy metody pomocnika HTML w celu pobrania wartości przy użyciu klasy 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>

Zaktualizujemy również metodę Edit post, aby używać klasy DinnerFormViewModel podczas renderowania błędów:

//
// 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));
    }
}

Możemy również zaktualizować metody akcji Create(), aby ponownie użyć dokładnie tej samej klasy DinnerFormViewModel , aby włączyć listę rozwijaną "Kraje" w ramach tych elementów. Poniżej znajduje się implementacja HTTP-GET:

//
// GET: /Dinners/Create

public ActionResult Create() {

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

    return View(new DinnerFormViewModel(dinner));
}

Poniżej przedstawiono implementację metody HTTP-POST Create:

//
// 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));
}

Teraz zarówno lista rozwijana Edytuj, jak i Utwórz ekrany obsługują listy rozwijane do wybierania kraju lub regionu.

Klasy ViewModel w kształcie niestandardowym

W powyższym scenariuszu klasa DinnerFormViewModel bezpośrednio uwidacznia obiekt modelu Dinner jako właściwość wraz z obsługą właściwości modelu SelectList. Takie podejście działa prawidłowo w scenariuszach, w których interfejs użytkownika HTML, który chcemy utworzyć w naszym szablonie widoku, odpowiada stosunkowo blisko obiektom modelu domeny.

W scenariuszach, w których tak nie jest, jedną z opcji, której można użyć, jest utworzenie niestandardowej klasy ViewModel, której model obiektów jest bardziej zoptymalizowany pod kątem użycia przez widok — i który może wyglądać zupełnie inaczej niż bazowy obiekt modelu domeny. Może to na przykład potencjalnie uwidocznić różne nazwy właściwości i/lub zagregowane właściwości zebrane z wielu obiektów modelu.

Klasy ViewModel w kształcie niestandardowym mogą służyć zarówno do przekazywania danych z kontrolerów do widoków do renderowania, jak i obsługi danych formularzy publikowanych z powrotem do metody akcji kontrolera. W tym późniejszym scenariuszu można zaktualizować obiekt ViewModel za pomocą danych opublikowanych w formularzu, a następnie użyć wystąpienia ViewModel do mapowania lub pobierania rzeczywistego obiektu modelu domeny.

Klasy ViewModel w kształcie niestandardowym mogą zapewnić dużą elastyczność i są czymś, co można zbadać za każdym razem, gdy znajdziesz kod renderowania w szablonach widoków lub kod publikowania formularzy w metodach akcji, które zaczynają się zbyt skomplikowane. Często jest to znak, że modele domeny nie odpowiadają generowanemu interfejsowi użytkownika i że pośrednia klasa ViewModel w kształcie niestandardowym może pomóc.

Następny krok

Przyjrzyjmy się teraz, jak możemy ponownie używać części i stron wzorcowych do ponownego użycia i udostępniania interfejsu użytkownika w naszej aplikacji.