Badanie sposobu ASP.NET szkieletów MVC pomocnika DropDownList

Autor : Rick Anderson

W Eksplorator rozwiązań kliknij prawym przyciskiem myszy folder Controllers, a następnie wybierz polecenie Dodaj kontroler. Nadaj kontrolerowi nazwę StoreManagerController. Ustaw opcje okna dialogowego Dodawanie kontrolera , jak pokazano na poniższej ilustracji.

Obraz przedstawiający okno dialogowe Dodawanie kontrolera Eksplorator rozwiązań

Edytuj widok StoreManager\Index.cshtml i usuń element AlbumArtUrl. Usunięcie AlbumArtUrl sprawi, że prezentacja będzie bardziej czytelna. Ukończony kod pokazano poniżej.

@model IEnumerable<MvcMusicStore.Models.Album>

@{

    ViewBag.Title = "Index";

}

<h2>Index</h2>

<p>

    @Html.ActionLink("Create New", "Create")

</p>

<table>

    <tr>

        <th>

            Genre

        </th>

        <th>

            Artist

        </th>

        <th>

            Title

        </th>

        <th>

            Price

        </th>

        <th></th>

    </tr>

@foreach (var item in Model) {

    <tr>

        <td>

            @Html.DisplayFor(modelItem => item.Genre.Name)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.Artist.Name)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.Title)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.Price)

        </td>

        <td>

            @Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |

            @Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |

            @Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })

        </td>

    </tr>

}

</table>

Otwórz plik Controllers\StoreManagerController.cs i znajdź metodę Index . Dodaj klauzulę , OrderBy aby albumy zostały posortowane według ceny. Poniżej przedstawiono kompletny kod.

public ViewResult Index()
{

    var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist)

        .OrderBy(a => a.Price);

    return View(albums.ToList());

}

Sortowanie według ceny ułatwi przetestowanie zmian w bazie danych. Podczas testowania metod edycji i tworzenia można użyć niskiej ceny, aby zapisane dane pojawiły się jako pierwsze.

Otwórz plik StoreManager\Edit.cshtml . Dodaj następujący wiersz tuż po tagu legendy.

@Html.HiddenFor(model => model.AlbumId)

Poniższy kod przedstawia kontekst tej zmiany:

@using (Html.BeginForm()) {

    @Html.ValidationSummary(true)

    <fieldset>

        <legend>Album</legend>

        @Html.HiddenFor(model => model.AlbumId)

        <div class="editor-label">

            @Html.LabelFor(model => model.GenreId, "Genre")

        </div>

        <div class="editor-field">

            @Html.DropDownList("GenreId", String.Empty)

            @Html.ValidationMessageFor(model => model.GenreId)

        </div>

        <!-- Items removed for brevity. -->

}

Wymagane AlbumId jest wprowadzenie zmian w rekordzie albumu.

Naciśnij klawisze CTRL+F5, aby uruchomić aplikację. Wybierz link Administracja, a następnie wybierz link Utwórz nowy, aby utworzyć nowy album. Sprawdź, czy informacje o albumie zostały zapisane. Edytuj album i sprawdź, czy wprowadzone zmiany zostały utrwalone.

Schemat albumu

Kontroler StoreManager utworzony przez mechanizm tworzenia szkieletów MVC umożliwia dostęp CRUD (Create, Read, Update, Delete) do albumów w bazie danych sklepu muzycznego. Poniżej przedstawiono schemat informacji o albumie:

Obraz schematu albumu

Tabela Albums nie przechowuje gatunku albumu i opisu, przechowuje klucz obcy do Genres tabeli. Tabela Genres zawiera nazwę gatunku i opis. Podobnie Albums tabela nie zawiera nazwy artystów albumów, ale klucza obcego Artists do tabeli. Tabela Artists zawiera nazwę artysty. Jeśli zbadasz dane w Albums tabeli, zobaczysz, że każdy wiersz zawiera klucz Genres obcy do tabeli i klucz obcy tabeli Artists . Na poniższej ilustracji Albums przedstawiono dane tabeli z tabeli.

Obraz przedstawiający niektóre dane z tabeli Albumy

The HTML Select Tag

Element HTML <select> (utworzony przez pomocnik HTML DropDownList ) służy do wyświetlania pełnej listy wartości (takich jak lista gatunków). W przypadku formularzy edycji, gdy bieżąca wartość jest znana, lista wyboru może wyświetlić bieżącą wartość. Widzieliśmy to wcześniej, gdy ustawiliśmy wybraną wartość na Comedy. Lista wyboru jest idealna do wyświetlania danych kategorii lub klucza obcego. Element <select> klucza obcego gatunku wyświetla listę możliwych nazw gatunków, ale po zapisaniu formularza właściwość Gatunek jest aktualizowana przy użyciu wartości klucza obcego gatunku, a nie wyświetlanej nazwy gatunku. Na poniższej ilustracji wybrany gatunek to Disco , a artysta jest Donna Summer.

Obraz wybranego gatunku Disco

Badanie ASP.NET kodu szkieletowego MVC

Otwórz plik Controllers\StoreManagerController.cs i znajdź metodę HTTP GET Create .

public ActionResult Create()

{

    ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");

    ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");

    return View();

}

Metoda Create dodaje do obiektu ViewBagdwa obiekty SelectList , jeden zawierający informacje o gatunku i jeden zawierający informacje o wykonawcy. Powyższe przeciążenie konstruktora SelectList przyjmuje trzy argumenty:

public SelectList(

    IEnumerable items,

    string dataValueField,

    string dataTextField

)
  1. items: Element IEnumerable zawierający elementy na liście. W powyższym przykładzie lista gatunków zwracanych przez db.Genreselement .
  2. dataValueField: nazwa właściwości na liście IEnumerable zawierająca wartość klucza. W powyższym GenreId przykładzie i ArtistId.
  3. dataTextField: nazwa właściwości na liście IEnumerable zawierająca informacje do wyświetlenia. W tabeli name artystów i gatunku używane jest pole .

Otwórz plik Views\StoreManager\Create.cshtml i sprawdź Html.DropDownList znaczniki pomocnika dla pola gatunku.

@model MvcMusicStore.Models.Album

@*        Markup removed for clarity.*@

@Html.DropDownList("GenreId", String.Empty)

Pierwszy wiersz pokazuje, że widok tworzenia przyjmuje Album model. W przedstawionej powyżej metodzie Create nie przekazano żadnego modelu, więc widok pobiera model o wartości nullAlbum . W tym momencie tworzymy nowy album, abyśmy nie mieli dla niego żadnych Album danych.

Przeciążenie Html.DropDownList pokazane powyżej przyjmuje nazwę pola, które ma być powiązane z modelem. Ta nazwa jest również używana do wyszukiwania obiektu ViewBag zawierającego obiekt SelectList . Przy użyciu tego przeciążenia należy nazwać obiekt GenreIdViewBag SelectList . Drugi parametr (String.Empty) to tekst, który ma być wyświetlany, gdy nie wybrano żadnego elementu. Jest to dokładnie to, czego chcemy podczas tworzenia nowego albumu. Jeśli usunięto drugi parametr i użyto następującego kodu:

@Html.DropDownList("GenreId")

Lista wyboru domyślnie zostanie ustawiona na pierwszy element lub Skała w naszym przykładzie.

Obraz domyślnego pierwszego elementu

HTTP POST Create Badanie metody.

//

// POST: /StoreManager/Create

[HttpPost]

public ActionResult Create(Album album)

{

    if (ModelState.IsValid)

    {

        db.Albums.Add(album);

        db.SaveChanges();

        return RedirectToAction("Index");  

    }

    ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name",

        album.GenreId);

    ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name",

        album.ArtistId);

    return View(album);

}

To przeciążenie Create metody przyjmuje album obiekt utworzony przez system powiązań modelu MVC ASP.NET z opublikowanych wartości formularza. Po przesłaniu nowego albumu, jeśli stan modelu jest prawidłowy i nie ma błędów bazy danych, nowy album zostanie dodany do bazy danych. Na poniższej ilustracji przedstawiono tworzenie nowego albumu.

Obraz przedstawiający tworzenie nowego albumu

Za pomocą narzędzia fiddler można sprawdzić opublikowane wartości formularzy, które ASP.NET powiązania modelu MVC używane do tworzenia obiektu albumu.

Obraz narzędzia Fiddler.

Refaktoryzacja tworzenia elementu ViewBag SelectList

Edit Obie metody i HTTP POST Create metoda mają identyczny kod, aby skonfigurować selectList w ViewBag. W duchu DRY refaktoryzujemy ten kod. Użyjemy tego refaktoryzowanego kodu później.

Utwórz nową metodę, aby dodać gatunek i artystę SelectList do kontrolki ViewBag.

private void SetGenreArtistViewBag(int? GenreID = null, int? ArtistID = null) {

    if (GenreID == null)

        ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");

    else

        ViewBag.GenreId = new SelectList(db.Genres.ToArray(), "GenreId", "Name", GenreID);

    if (ArtistID == null)

        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");

    else

        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", ArtistID);

}

Zastąp dwa wiersze ustawieniem ViewBag w każdej z Create metod i Edit wywołaniem SetGenreArtistViewBag metody . Ukończony kod pokazano poniżej.

//

// GET: /StoreManager/Create

public ActionResult Create() {

    SetGenreArtistViewBag();

    return View();

}

//

// POST: /StoreManager/Create

[HttpPost]

public ActionResult Create(Album album) {

    if (ModelState.IsValid) {

        db.Albums.Add(album);

        db.SaveChanges();

        return RedirectToAction("Index");

    }

    SetGenreArtistViewBag(album.GenreId, album.ArtistId);

    return View(album);

}

//

// GET: /StoreManager/Edit/5

public ActionResult Edit(int id) {

    Album album = db.Albums.Find(id);

    SetGenreArtistViewBag(album.GenreId, album.ArtistId);

    return View(album);

}

//

// POST: /StoreManager/Edit/5

[HttpPost]

public ActionResult Edit(Album album) {

    if (ModelState.IsValid) {

        db.Entry(album).State = EntityState.Modified;

        db.SaveChanges();

        return RedirectToAction("Index");

    }

    SetGenreArtistViewBag(album.GenreId, album.ArtistId);

    return View(album);

}

Utwórz nowy album i edytuj album, aby sprawdzić, czy zmiany działają.

Jawne przekazywanie listy SelectList do listy rozwijanej

Tworzenie i edytowanie widoków utworzonych przez szkielet ASP.NET MVC używa następującego przeciążenia Listy rozwijanej :

public static MvcHtmlString DropDownList(

    this HtmlHelper htmlHelper,

    string name,         // The name of the ViewModel property to bind.

    string optionLabel   // The string added to the top of the list

                         // typically  String.Empty or "Select a Genre"

)

Poniżej DropDownList przedstawiono znaczniki dla widoku tworzenia.

@Html.DropDownList("GenreId", String.Empty)

ViewBag Ponieważ właściwość dla SelectList właściwości ma nazwę GenreId, pomocnik DropDownList użyje GenreIdlisty SelectList w obiekcie ViewBag. W poniższym przeciążeniu SelectListDropDownList element jest jawnie przekazywany.

public static MvcHtmlString DropDownList(

    this HtmlHelper htmlHelper,

    string name,            // The name of the ViewModel property to bind.

    IEnumerable selectList  // The SelectList

)

Otwórz plik Views\StoreManager\Edit.cshtml i zmień wywołanie DropDownList , aby jawnie przekazać element SelectList przy użyciu powyższego przeciążenia. Zrób to dla kategorii Gatunek. Ukończony kod przedstawiono poniżej:

@Html.DropDownList("GenreId", ViewBag.GenreId as SelectList)

Uruchom aplikację i kliknij link Administracja, a następnie przejdź do albumu Jazz i wybierz link Edytuj.

Obraz przedstawiający wybór albumu Jazz do edycji

Zamiast pokazywać Jazz jako aktualnie wybrany gatunek, rock jest wyświetlany. Gdy argument ciągu (właściwość do powiązania) i obiekt SelectList mają taką samą nazwę, wybrana wartość nie jest używana. Jeśli nie podano żadnej wybranej wartości, przeglądarki są domyślnie wybierane jako pierwszy element w elemecie SelectList(czyli Rock w powyższym przykładzie). Jest to znane ograniczenie pomocnika DropDownList .

Otwórz plik Controllers\StoreManagerController.cs i zmień nazwy obiektów SelectList na Genres i Artists. Ukończony kod przedstawiono poniżej:

private void SetGenreArtistViewBag(int? GenreID = null, int? ArtistID = null) {
    if (GenreID == null)

        ViewBag.Genres = new SelectList(db.Genres, "GenreId", "Name");

    else

        ViewBag.Genres = new SelectList(db.Genres.ToArray(), "GenreId", "Name", GenreID);

    if (ArtistID == null)

        ViewBag.Artists = new SelectList(db.Artists, "ArtistId", "Name");

    else

        ViewBag.Artists = new SelectList(db.Artists, "ArtistId", "Name", ArtistID);

}

Nazwy Gatunki i Artyści są lepszymi nazwami kategorii, ponieważ zawierają więcej niż tylko identyfikator każdej kategorii. Refaktoryzacja, którą zrobiliśmy wcześniej, opłaciła się. Zamiast zmieniać element ViewBag w czterech metodach, nasze zmiany zostały odizolowane od SetGenreArtistViewBag metody .

Zmień wywołanie Listy rozwijanej w widokach tworzenia i edytowania, aby używać nowych nazw SelectList . Poniżej przedstawiono nowe znaczniki dla widoku edycji:

<div class="editor-label">

    @Html.LabelFor(model => model.GenreId, "Genre")

</div>

<div class="editor-field">

    @Html.DropDownList("GenreId", ViewBag.Genres as SelectList)

    @Html.ValidationMessageFor(model => model.GenreId)

</div>

<div class="editor-label">

    @Html.LabelFor(model => model.ArtistId, "Artist")

</div>

<div class="editor-field">

    @Html.DropDownList("ArtistId", ViewBag.Artists as SelectList)

    @Html.ValidationMessageFor(model => model.ArtistId)

</div>

Widok Tworzenie wymaga pustego ciągu, aby zapobiec wyświetlaniu pierwszego elementu na liście SelectList.

<div class="editor-label">

    @Html.LabelFor(model => model.GenreId, "Genre" )

</div>

<div class="editor-field">

    @Html.DropDownList("GenreId", ViewBag.Genres as SelectList, String.Empty)

    @Html.ValidationMessageFor(model => model.GenreId)

</div>

<div class="editor-label">

    @Html.LabelFor(model => model.ArtistId, "Artist")

</div>

<div class="editor-field">

    @Html.DropDownList("ArtistId", ViewBag.Artists as SelectList, String.Empty)

    @Html.ValidationMessageFor(model => model.ArtistId)

</div>

Utwórz nowy album i edytuj album, aby sprawdzić, czy zmiany działają. Przetestuj kod edycji, wybierając album z gatunkiem innym niż Rock.

Używanie modelu widoku z pomocnikiem Listy rozwijanej

Utwórz nową klasę w folderze ViewModels o nazwie AlbumSelectListViewModel. Zastąp kod w AlbumSelectListViewModel klasie następującym kodem:

using MvcMusicStore.Models;

using System.Web.Mvc;

using System.Collections;

namespace MvcMusicStore.ViewModels {

    public class AlbumSelectListViewModel {

        public Album Album { get; private set; }

        public SelectList Artists { get; private set; }

        public SelectList Genres { get; private set; }

        public AlbumSelectListViewModel(Album album, 

                                        IEnumerable artists, 

                                        IEnumerable genres) {

            Album = album;

            Artists = new SelectList(artists, "ArtistID", "Name", album.ArtistId);

            Genres = new SelectList(genres, "GenreID", "Name", album.GenreId);

        }

    }

}

Konstruktor AlbumSelectListViewModel przyjmuje album, listę artystów i gatunków i tworzy obiekt zawierający album oraz dla gatunków i SelectList artystów.

Skompiluj projekt tak, aby AlbumSelectListViewModel był dostępny po utworzeniu widoku w następnym kroku.

Dodaj metodę EditVM do elementu StoreManagerController. Ukończony kod jest pokazany poniżej.

//

// GET: /StoreManager/EditVM/5

public ActionResult EditVM(int id) {

    Album album = db.Albums.Find(id);

    if (album == null)

        return HttpNotFound();

    AlbumSelectListViewModel aslvm = new AlbumSelectListViewModel(album, db.Artists, db.Genres);

    return View(aslvm);

}

Kliknij prawym przyciskiem myszy AlbumSelectListViewModelpozycję , wybierz pozycję Rozwiąż, a następnie użyj polecenia MvcMusicStore.ViewModels;.

Obraz z wybieraniem rozwiązania

Alternatywnie można dodać następującą instrukcję using:

using MvcMusicStore.ViewModels;

Kliknij prawym przyciskiem myszy EditVM i wybierz pozycję Dodaj widok. Użyj opcji przedstawionych poniżej.

Obraz przedstawiający okno dialogowe Dodawanie widoku

Wybierz pozycję Dodaj, a następnie zastąp zawartość pliku Views\StoreManager\EditVM.cshtml następującymi elementami:

@model MvcMusicStore.ViewModels.AlbumSelectListViewModel

@{

    ViewBag.Title = "EditVM";

}

<h2>Edit VM</h2>

@using (Html.BeginForm("Edit","StoreManager",FormMethod.Post)) {

    @Html.ValidationSummary(true)

    <fieldset>

        <legend>Album</legend>

        @Html.HiddenFor(model => model.Album.AlbumId )

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.GenreId, "Genre")

        </div>

        <div class="editor-field">

            @Html.DropDownList("Album.GenreId", Model.Genres)

            @Html.ValidationMessageFor(model => model.Album.GenreId)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.ArtistId, "Artist")

        </div>

        <div class="editor-field">

            @Html.DropDownList("Album.ArtistId", Model.Artists)

            @Html.ValidationMessageFor(model => model.Album.ArtistId)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.Title)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Album.Title)

            @Html.ValidationMessageFor(model => model.Album.Title)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.Price)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Album.Price)

            @Html.ValidationMessageFor(model => model.Album.Price)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.AlbumArtUrl)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Album.AlbumArtUrl)

            @Html.ValidationMessageFor(model => model.Album.AlbumArtUrl)

        </div>

        <p>

            <input type="submit" value="Save" />

        </p>

    </fieldset>

}

<div>

    @Html.ActionLink("Back to List", "Index")

</div>

Znaczniki EditVM są bardzo podobne do oryginalnego Edit adiustacji z następującymi wyjątkami.

  • Właściwości modelu w Edit widoku są formą model.property(na przykład model.Title ). Właściwości modelu w EditVm widoku są formą model.Album.property(na przykład model.Album.Title). Dzieje się tak, ponieważ EditVM widok jest przekazywany do kontenera dla elementu Album, a nie Album jako w Edit widoku.
  • Drugi parametr DropDownList pochodzi z modelu widoku, a nie ViewBag.
  • Pomocnik BeginForm w EditVM widoku jawnie publikuje z powrotem do Edit metody akcji. Publikując ponownie Edit akcję, nie musimy pisać HTTP POST EditVM akcji i ponownie użyć HTTP POSTEdit akcji.

Uruchom aplikację i edytuj album. Zmień adres URL, aby użyć polecenia EditVM. Zmień pole i naciśnij przycisk Zapisz , aby sprawdzić, czy kod działa.

Obraz ze zmianą języka U R L na Edytuj maszynę wirtualną

Którego podejścia należy użyć?

Wszystkie trzy pokazane podejścia są dopuszczalne. Wielu deweloperów woli jawnie przekazać element SelectList do DropDownList elementu za pomocą polecenia ViewBag. To podejście ma dodatkową zaletę, zapewniając elastyczność używania bardziej odpowiedniej nazwy dla kolekcji. Jednym z zastrzeżeń jest to, że nie można nazwać ViewBag SelectList obiektu o tej samej nazwie co właściwość modelu.

Niektórzy deweloperzy preferują podejście ViewModel. Inni uważają, że bardziej pełne adiustowanie i wygenerowany kod HTML modelu ViewModel jest wadą.

W tej sekcji przedstawiono trzy podejścia do używania listy Rozwijanej z danymi kategorii. W następnej sekcji pokażemy, jak dodać nową kategorię.