Condividi tramite


Analisi della modalità di scaffolding dell'helper DropDownList in ASP.NET MVC

di Rick Anderson

In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Controller e quindi scegliere Aggiungi controller. Assegnare al controller il nome StoreManagerController. Impostare le opzioni per la finestra di dialogo Aggiungi controller , come illustrato nell'immagine seguente.

Immagine di Esplora soluzioni finestra di dialogo Aggiungi controller

Modificare la vista StoreManager\Index.cshtml e rimuovere AlbumArtUrl. La rimozione AlbumArtUrl rende la presentazione più leggibile. Il codice completo è illustrato di seguito.

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

Aprire il file Controllers\StoreManagerController.cs e trovare il Index metodo . Aggiungere la OrderBy clausola in modo che gli album vengano ordinati in base al prezzo. Di seguito è riportato il codice completo.

public ViewResult Index()
{

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

        .OrderBy(a => a.Price);

    return View(albums.ToList());

}

L'ordinamento in base al prezzo consentirà di testare più facilmente le modifiche apportate al database. Quando si testano i metodi di modifica e creazione, è possibile usare un prezzo basso in modo che i dati salvati vengano visualizzati per primi.

Aprire il file StoreManager\Edit.cshtml . Aggiungere la riga seguente subito dopo il tag legenda.

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

Il codice seguente illustra il contesto di questa modifica:

@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. -->

}

È AlbumId necessario per apportare modifiche a un record album.

Premere CTRL+F5 per eseguire l'applicazione. Selezionare il collegamento Admin (Amministratore ) e quindi selezionare il collegamento Create New (Crea nuovo ) per creare un nuovo album. Verificare che le informazioni sull'album siano state salvate. Modificare un album e verificare che le modifiche apportate siano persistenti.

The Album Schema

Il StoreManager controller creato dal meccanismo di scaffolding MVC consente l'accesso CRUD (Create, Read, Update, Delete) agli album nel database music store. Lo schema per le informazioni sugli album è illustrato di seguito:

Immagine dello schema album

La Albums tabella non archivia il genere dell'album e la descrizione, archivia una chiave esterna nella Genres tabella. La Genres tabella contiene il nome e la descrizione del genere. Analogamente, la Albums tabella non contiene il nome degli artisti dell'album, ma una chiave esterna per la Artists tabella. La Artists tabella contiene il nome dell'artista. Se si esaminano i dati nella Albums tabella, ogni riga contiene una chiave esterna per la Genres tabella e una chiave esterna nella Artists tabella. L'immagine seguente mostra alcuni dati di tabella della Albums tabella.

Immagine di alcuni dati della tabella Albums

Tag di selezione HTML

L'elemento HTML (creato dall'helper DropDownList HTML <select> ) viene usato per visualizzare un elenco completo di valori (ad esempio l'elenco di generi). Per modificare i moduli, quando il valore corrente è noto, l'elenco di selezione può visualizzare il valore corrente. Abbiamo visto questo in precedenza quando impostiamo il valore selezionato su Comedy. L'elenco di selezione è ideale per la visualizzazione di dati di chiave esterna o di categoria. L'elemento <select> per la chiave esterna Genre visualizza l'elenco dei possibili nomi di genere, ma quando si salva la maschera la proprietà Genre viene aggiornata con il valore di chiave esterna Genre, non con il nome del genere visualizzato. Nell'immagine seguente il genere selezionato è Disco e l'artista è Donna Summer.

Immagine del genere disco selezionato

Esame del codice Scaffolding MVC ASP.NET

Aprire il file Controllers\StoreManagerController.cs e trovare il HTTP GET Create metodo .

public ActionResult Create()

{

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

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

    return View();

}

Il Create metodo aggiunge due oggetti SelectList a ViewBag, uno per contenere le informazioni sul genere e uno per contenere le informazioni sull'artista. L'overload del costruttore SelectList usato in precedenza accetta tre argomenti:

public SelectList(

    IEnumerable items,

    string dataValueField,

    string dataTextField

)
  1. items: IEnumerable contenente gli elementi nell'elenco. Nell'esempio precedente l'elenco dei generi restituiti da db.Genres.
  2. dataValueField: nome della proprietà nell'elenco IEnumerable che contiene il valore della chiave. Nell'esempio precedente e GenreId ArtistId.
  3. dataTextField: nome della proprietà nell'elenco IEnumerable che contiene le informazioni da visualizzare. Sia nell'artista che nella tabella di genere, il name campo viene usato.

Aprire il file Views\StoreManager\Create.cshtml ed esaminare il Html.DropDownList markup helper per il campo genere.

@model MvcMusicStore.Models.Album

@*        Markup removed for clarity.*@

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

La prima riga mostra che la visualizzazione di creazione accetta un Album modello. Create Nel metodo illustrato in precedenza non è stato passato alcun modello, quindi la vista ottiene un modello NullAlbum. A questo punto stiamo creando un nuovo album in modo da non avere dati Album per esso.

L'overload Html.DropDownList illustrato sopra accetta il nome del campo da associare al modello. Usa anche questo nome per cercare un oggetto ViewBag contenente un oggetto SelectList . Usando questo overload, è necessario denominare l'oggetto GenreIdViewBag SelectList . Il secondo parametro (String.Empty) è il testo da visualizzare quando non è selezionato alcun elemento. Questo è esattamente ciò che vogliamo quando creiamo un nuovo album. Se è stato rimosso il secondo parametro e si è usato il codice seguente:

@Html.DropDownList("GenreId")

Per impostazione predefinita, l'elenco di selezione corrisponde al primo elemento o a Rock nell'esempio.

Immagine del primo elemento predefinito

Esame del HTTP POST Create metodo .

//

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

}

Questo overload del Create metodo accetta un album oggetto creato dal sistema di associazione di modelli MVC ASP.NET dai valori del modulo pubblicati. Quando si invia un nuovo album, se lo stato del modello è valido e non sono presenti errori di database, il nuovo album viene aggiunto al database. L'immagine seguente mostra la creazione di un nuovo album.

Immagine che mostra la creazione di un nuovo album

È possibile usare lo strumento fiddler per esaminare i valori del modulo pubblicati che ASP.NET binding di modelli MVC usa per creare l'oggetto album.

Immagine dello strumento Fiddler.

Refactoring della creazione di ViewBag SelectList

Sia i Edit metodi che il HTTP POST Create metodo hanno codice identico per configurare SelectList in ViewBag. Nello spirito di DRY, il refactoring di questo codice verrà refactoring. Questo codice sottoposto a refactoring verrà usato in un secondo momento.

Creare un nuovo metodo per aggiungere un genere e un artista SelectList a 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);

}

Sostituire le due righe impostando in ViewBag ognuno dei Create metodi e Edit con una chiamata al SetGenreArtistViewBag metodo . Il codice completo è illustrato di seguito.

//

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

}

Creare un nuovo album e modificare un album per verificare il lavoro delle modifiche.

Passaggio esplicito di SelectList all'elenco a discesa

La creazione e la modifica delle visualizzazioni create dal ASP.NET scaffolding MVC usano l'overload DropDownList seguente:

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"

)

Il DropDownList markup per la visualizzazione di creazione è illustrato di seguito.

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

Poiché la ViewBag proprietà per SelectList è denominata GenreId, l'helper DropDownList userà GenreIdSelectList in ViewBag. Nell'overload DropDownList seguente l'oggetto SelectList viene passato in modo esplicito.

public static MvcHtmlString DropDownList(

    this HtmlHelper htmlHelper,

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

    IEnumerable selectList  // The SelectList

)

Aprire il file Views\StoreManager\Edit.cshtml e modificare la chiamata DropDownList in modo da passare in modo esplicito selectList usando l'overload precedente. Eseguire questa operazione per la categoria Genere. Il codice completato è illustrato di seguito:

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

Eseguire l'applicazione e fare clic sul collegamento Amministratore , quindi passare a un album Jazz e selezionare il collegamento Modifica .

Immagine della selezione dell'album Jazz da modificare

Invece di visualizzare Jazz come genere attualmente selezionato, viene visualizzato Rock. Quando l'argomento stringa (la proprietà da associare) e l'oggetto SelectList hanno lo stesso nome, il valore selezionato non viene utilizzato. Quando non è specificato alcun valore selezionato, per impostazione predefinita il browser corrisponde al primo elemento di SelectList(che è Rock nell'esempio precedente). Si tratta di una limitazione nota dell'helper DropDownList .

Aprire il file Controllers\StoreManagerController.cs e modificare i nomi degli oggetti SelectList in Genres e Artists. Il codice completato è illustrato di seguito:

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

}

I nomi Generi e Artisti sono nomi migliori per le categorie, in quanto contengono più che solo l'ID di ogni categoria. Il refactoring eseguito in precedenza è stato pagato. Invece di modificare ViewBag in quattro metodi, le modifiche sono state isolate nel SetGenreArtistViewBag metodo .

Modificare la chiamata DropDownList nelle visualizzazioni di creazione e modifica per usare i nuovi nomi SelectList . Di seguito è riportato il nuovo markup per la visualizzazione di modifica:

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

La visualizzazione Crea richiede una stringa vuota per impedire la visualizzazione del primo elemento nell'oggetto 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>

Creare un nuovo album e modificare un album per verificare il lavoro delle modifiche. Testare il codice di modifica selezionando un album con un genere diverso da Rock.

Uso di un modello di visualizzazione con l'helper DropDownList

Creare una nuova classe nella cartella ViewModels denominata AlbumSelectListViewModel. Sostituire il codice nella AlbumSelectListViewModel classe con quanto segue:

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

        }

    }

}

Il AlbumSelectListViewModel costruttore accetta un album, un elenco di artisti e generi e crea un oggetto contenente l'album e un per SelectList generi e artisti.

Compilare il progetto in modo che AlbumSelectListViewModel sia disponibile quando si crea una visualizzazione nel passaggio successivo.

Aggiungere un EditVM metodo all'oggetto StoreManagerController. Il codice completo è illustrato di seguito.

//

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

}

Fare clic con il pulsante destro del mouse su AlbumSelectListViewModel, selezionare Risolvi, quindi usare MvcMusicStore.ViewModels;.

Immagine che seleziona risolvi

In alternativa, è possibile aggiungere l'istruzione using seguente:

using MvcMusicStore.ViewModels;

Fare clic con il pulsante destro del mouse EditVM e selezionare Aggiungi visualizzazione. Usare le opzioni illustrate di seguito.

Immagine che mostra la finestra di dialogo Aggiungi visualizzazione

Selezionare Aggiungi, quindi sostituire il contenuto del file Views\StoreManager\EditVM.cshtml con quanto segue:

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

Il EditVM markup è molto simile al markup originale Edit con le eccezioni seguenti.

  • Le proprietà del modello nella Edit visualizzazione sono del modulo model.property, ad esempio model.Title . Le proprietà del modello nella EditVm visualizzazione sono del modulo model.Album.property, ad esempio model.Album.Title. Ciò è dovuto al fatto che la EditVM vista viene passata a un contenitore per un Albumoggetto , non come Album nella Edit visualizzazione.
  • Il secondo parametro DropDownList proviene dal modello di visualizzazione, non dal ViewBag.
  • L'helper BeginForm nella EditVM visualizzazione esegue in modo esplicito il postback al Edit metodo di azione. Pubblicando nuovamente l'azione Edit , non è necessario scrivere un'azione HTTP POST EditVM e riutilizzare l'azione HTTP POST Edit .

Eseguire l'applicazione e modificare un album. Modificare l'URL per usare EditVM. Modificare un campo e premere il pulsante Salva per verificare che il codice funzioni.

Immagine con modifica di U R L in Modifica V M

Quale approccio è consigliabile usare?

Tutti e tre gli approcci illustrati sono accettabili. Molti sviluppatori preferiscono passare in modo esplicito all'oggetto SelectList DropDownList usando .ViewBag Questo approccio offre il vantaggio di offrire la flessibilità di usare un nome più appropriato per la raccolta. L'unica avvertenza è che non è possibile denominare l'oggetto ViewBag SelectList con lo stesso nome della proprietà del modello.

Alcuni sviluppatori preferiscono l'approccio ViewModel. Altri considerano il markup più dettagliato e il codice HTML generato dell'approccio ViewModel uno svantaggio.

In questa sezione sono stati appresi tre approcci all'uso di DropDownList con i dati delle categorie. Nella sezione successiva verrà illustrato come aggiungere una nuova categoria.