Udostępnij za pośrednictwem


Badanie sposobu tworzenia szkieletu pomocnika DropDownList przez wzorzec ASP.NET MVC

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ń polecenie AlbumArtUrl. Usunięcie AlbumArtUrl spowoduje, że prezentacja będzie bardziej czytelna. Ukończony kod jest pokazany 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. Pełny kod jest pokazany poniżej.

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 testowanie 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 Administrator, 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 są utrwalane.

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. Schemat informacji o albumie jest pokazany poniżej:

Obraz schematu albumu

Tabela Albums nie przechowuje gatunku albumu i opisu, przechowuje klucz obcy do Genres tabeli. Tabela Genres zawiera nazwę gatunku i opis. Albums Podobnie tabela nie zawiera nazwy artystów albumów, ale klucza obcego do Artists 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 w Artists tabeli. Na poniższej ilustracji Albums przedstawiono niektóre 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 zostanie zaktualizowana o wartość klucza obcego gatunku, a nie wyświetlaną nazwę gatunku. Na poniższej ilustracji wybrany gatunek to Disco , a artysta jest Donna Summer.

Obraz wybranego gatunku Disco

Badanie kodu szkieletowego ASP.NET 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 dwa obiekty SelectList do obiektu , które zawierają informacje o gatunku ViewBag, 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 znacznik 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 metodzie pokazanej Create powyżej nie przekazano żadnego modelu, więc widok pobiera model o wartości nullAlbum. W tym momencie tworzymy nowy album, więc nie mamy dla niego żadnych Album danych.

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

@Html.DropDownList("GenreId")

Lista wyboru jest domyślnie ustawiona na pierwszy element lub Rock w naszym przykładzie.

Obraz przedstawiający domyślny pierwszy element

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ązania 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 Zarówno metody, jak 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 elementu 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 jest pokazany 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 DropDownList :

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ść obiektu SelectList ma nazwę GenreId, pomocnik DropDownList użyjeGenreId kontrolki SelectList w obiekcie ViewBag. W poniższym przeciążeniu SelectList DropDownList parametr 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 przeciążenia powyżej. Zrób to dla kategorii Gatunek. Ukończony kod jest pokazany poniżej:

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

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

Obraz wyboru 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 jest podana żadna wybrana wartość, przeglądarki są domyślne dla pierwszego elementu 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 jest pokazany 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 Gatunek i Artyści są lepszymi nazwami kategorii, ponieważ zawierają więcej niż tylko identyfikator każdej kategorii. Refaktoryzacja, którą wcześniej opłaciliśmy. Zamiast zmieniać element ViewBag w czterech metodach, nasze zmiany zostały odizolowane od SetGenreArtistViewBag metody .

Zmień wywołanie DropDownList w widokach tworzenia i edytowania, aby używać nowych nazw SelectList . Nowy znacznik dla widoku edycji jest pokazany poniżej:

<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 DropDownList

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 SelectList obiekt dla gatunków i artystów.

Skompiluj projekt, AlbumSelectListViewModel aby był dostępny podczas tworzenia widoku w następnym kroku.

Dodaj metodę EditVM do metody 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 AlbumSelectListViewModel, wybierz pozycję Rozwiąż, a następnie użyj mvcMusicStore.ViewModels;.

Obraz z wybieraniem rozwiązania

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

using MvcMusicStore.ViewModels;

Kliknij prawym przyciskiem myszy EditVM i wybierz polecenie 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ącym kodem:

@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 mają postać model.property(na przykład model.Title ). Właściwości modelu w EditVm widoku mają postać model.Album.property(na przykład model.Album.Title). Dzieje się tak, ponieważ EditVM widok jest przekazywany do kontenera dla Albumelementu , a nie Album jako w Edit widoku.
  • Drugi parametr DropDownList pochodzi z modelu widoku, a nie viewBag.
  • Pomocnik BeginForm w EditVM widoku jawnie publikuje dane z powrotem do Edit metody akcji. Publikując z powrotem do Edit akcji, nie musimy pisać HTTP POST EditVM akcji i możemy ponownie użyć HTTP POST Edit akcji.

Uruchom aplikację i edytuj album. Zmień adres URL, aby używał 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 V M

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 metody przy użyciu polecenia ViewBag. Takie podejście ma dodatkową zaletę zapewniania elastyczności 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 znaczniki i wygenerowany kod HTML modelu ViewModel jest wadą.

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