Examinar como o ASP.NET MVC faz scaffold do auxiliar DropDownList
por Rick Anderson
No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controladores e selecione Adicionar Controlador. Nomeie o controlador StoreManagerController. Defina as opções para a caixa de diálogo Adicionar controlador, conforme mostrado na imagem abaixo.
Edite a exibição StoreManager\Index.cshtml e remova AlbumArtUrl
o . A remoção AlbumArtUrl
tornará a apresentação mais legível. O código completo é mostrado abaixo.
@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>
Abra o arquivo Controllers\StoreManagerController.cs e localize o Index
método. Adicione a OrderBy
cláusula para que os álbuns sejam classificados por preço. O código completo é mostrado abaixo.
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist)
.OrderBy(a => a.Price);
return View(albums.ToList());
}
A classificação por preço facilitará o teste de alterações no banco de dados. Ao testar os métodos de edição e criação, você pode usar um preço baixo para que os dados salvos apareçam primeiro.
Abra o arquivo StoreManager\Edit.cshtml . Adicione a seguinte linha logo após a tag legenda.
@Html.HiddenFor(model => model.AlbumId)
O código a seguir mostra o contexto dessa alteração:
@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. -->
}
O AlbumId
é necessário para fazer alterações em um registro de álbum.
Pressione CTRL+F5 para executar o aplicativo. Selecione o link Admin e, em seguida, selecione o link Criar novo para criar um novo álbum. Verifique se as informações do álbum foram salvas. Edite um álbum e verifique se as alterações feitas são mantidas.
O esquema do álbum
O StoreManager
controlador criado pelo mecanismo de scaffolding MVC permite acesso CRUD (Criar, Ler, Atualizar, Excluir) aos álbuns no banco de dados da loja de música. O esquema para informações do álbum é mostrado abaixo:
A Albums
tabela não armazena o gênero e a descrição do álbum, ela armazena uma chave estrangeira para a Genres
tabela. A Genres
tabela contém o nome e a descrição do gênero. Da mesma forma, a Albums
tabela não contém o nome dos artistas do álbum, mas uma chave estrangeira para a Artists
tabela. A Artists
tabela contém o nome do artista. Se você examinar os dados na Albums
tabela, poderá ver que cada linha contém uma chave estrangeira para a Genres
tabela e uma chave estrangeira para a Artists
tabela. A imagem abaixo mostra alguns dados da Albums
tabela.
A tag HTML Select
O elemento HTML <select>
(criado pelo auxiliar HTML DropDownList ) é usado para exibir uma lista completa de valores (como a lista de gêneros). Para formulários de edição, quando o valor atual é conhecido, a lista de seleção pode exibir o valor atual. Vimos isso anteriormente quando definimos o valor selecionado como Comédia. A lista de seleção é ideal para exibir dados de categoria ou chave estrangeira. O <select>
elemento da chave estrangeira Genre exibe a lista de possíveis nomes de gênero, mas quando você salva o formulário, a propriedade Genre é atualizada com o valor da chave estrangeira Genre, não com o nome do gênero exibido. Na imagem abaixo, o gênero selecionado é Disco e a artista é Donna Summer.
Examinando o código de scaffolded do MVC ASP.NET
Abra o arquivo Controllers\StoreManagerController.cs e localize o HTTP GET Create
método.
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");
return View();
}
O Create
método adiciona dois objetos SelectList ao ViewBag
, um para conter as informações de gênero e outro para conter as informações do artista. A sobrecarga do construtor SelectList usada acima usa três argumentos:
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField
)
- items: um IEnumerable que contém os itens na lista. No exemplo acima, a lista de gêneros retornada por
db.Genres
. - dataValueField: o nome da propriedade na lista IEnumerable que contém o valor da chave. No exemplo acima,
GenreId
eArtistId
. - dataTextField: o nome da propriedade na lista IEnumerable que contém as informações a serem exibidas. Tanto na tabela de artistas quanto na de gênero, o
name
campo é usado.
Abra o arquivo Views\StoreManager\Create.cshtml e examine a Html.DropDownList
marcação auxiliar para o campo de gênero.
@model MvcMusicStore.Models.Album
@* Markup removed for clarity.*@
@Html.DropDownList("GenreId", String.Empty)
A primeira linha mostra que a visualização de criação usa um Album
modelo. Create
No método mostrado acima, nenhum modelo foi passado, portanto, a exibição obtém um modelo nuloAlbum
. Neste ponto, estamos criando um novo álbum, então não temos dados Album
para ele.
A sobrecarga Html.DropDownList mostrada acima usa o nome do campo a ser associado ao modelo. Ele também usa esse nome para procurar um objeto ViewBag que contém um objeto SelectList . Usando essa sobrecarga, você deve nomear o objeto GenreId
ViewBag SelectList . O segundo parâmetro (String.Empty
) é o texto a ser exibido quando nenhum item for selecionado. Isso é exatamente o que queremos ao criar um novo álbum. Se você removeu o segundo parâmetro e usou o seguinte código:
@Html.DropDownList("GenreId")
A lista de seleção seria padronizada para o primeiro elemento, ou Rock em nosso exemplo.
Examinando o HTTP POST Create
método.
//
// 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);
}
Essa sobrecarga do Create
método usa um album
objeto, criado pelo sistema de associação de modelo MVC ASP.NET dos valores de formulário postados. Quando você envia um novo álbum, se o estado do modelo for válido e não houver erros de banco de dados, o novo álbum será adicionado ao banco de dados. A imagem a seguir mostra a criação de um novo álbum.
Você pode usar a ferramenta fiddler para examinar os valores de formulário postados que ASP.NET associação de modelo MVC usa para criar o objeto de álbum.
.
Refatoração da criação de ViewBag SelectList
Edit
Os métodos e o HTTP POST Create
método têm código idêntico para configurar o SelectList no ViewBag. No espírito do DRY, vamos refatorar esse código. Usaremos esse código refatorado posteriormente.
Crie um novo método para adicionar um gênero e um artista SelectList ao 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);
}
Substitua as duas linhas que definem o ViewBag
em cada um dos Create
métodos e Edit
por uma chamada para o SetGenreArtistViewBag
método. O código completo é mostrado abaixo.
//
// 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);
}
Crie um novo álbum e edite-o para verificar se as alterações funcionam.
Passando explicitamente o SelectList para o DropDownList
As exibições de criação e edição criadas pelo scaffolding MVC ASP.NET usam a seguinte sobrecarga 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"
)
A DropDownList
marcação para a visualização de criação é mostrada abaixo.
@Html.DropDownList("GenreId", String.Empty)
Como a ViewBag
propriedade do SelectList
é nomeada GenreId
, o auxiliar DropDownList usará oGenreId
SelectList no ViewBag. Na sobrecarga DropDownList a seguir, o SelectList
é explicitamente passado.
public static MvcHtmlString DropDownList(
this HtmlHelper htmlHelper,
string name, // The name of the ViewModel property to bind.
IEnumerable selectList // The SelectList
)
Abra o arquivo Views\StoreManager\Edit.cshtml e altere a chamada DropDownList para passar explicitamente o SelectList, usando a sobrecarga acima. Faça isso para a categoria Gênero. O código completo é mostrado abaixo:
@Html.DropDownList("GenreId", ViewBag.GenreId as SelectList)
Execute o aplicativo e clique no link Admin , navegue até um álbum do Jazz e selecione o link Editar .
Em vez de mostrar Jazz como o gênero selecionado no momento, Rock é exibido. Quando o argumento de cadeia de caracteres (a propriedade a ser associada) e o objeto SelectList têm o mesmo nome, o valor selecionado não é usado. Quando não há nenhum valor selecionado fornecido, os navegadores usam como padrão o primeiro elemento no SelectList (que é Rock no exemplo acima). Essa é uma limitação conhecida do auxiliar DropDownList .
Abra o arquivo Controllers\StoreManagerController.cs e altere os nomes dos objetos SelectList para Genres
e Artists
. O código completo é mostrado abaixo:
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);
}
Os nomes Gêneros e Artistas são nomes melhores para as categorias, pois contêm mais do que apenas o ID de cada categoria. A refatoração que fizemos anteriormente valeu a pena. Em vez de alterar o ViewBag em quatro métodos, nossas alterações foram isoladas no SetGenreArtistViewBag
método.
Altere a chamada DropDownList nos modos de exibição de criação e edição para usar os novos nomes SelectList . A nova marcação para a visualização de edição é mostrada abaixo:
<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>
O modo de exibição Criar requer uma cadeia de caracteres vazia para impedir que o primeiro item na SelectList seja exibido.
<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>
Crie um novo álbum e edite-o para verificar se as alterações funcionam. Teste o código de edição selecionando um álbum com um gênero diferente de Rock.
Usando um modelo de exibição com o auxiliar DropDownList
Crie uma nova classe na pasta ViewModels chamada AlbumSelectListViewModel
. Substitua o AlbumSelectListViewModel
código na classe pelo seguinte:
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);
}
}
}
O AlbumSelectListViewModel
construtor pega um álbum, uma lista de artistas e gêneros e cria um objeto contendo o álbum e um SelectList
para gêneros e artistas.
Construa o projeto para que ele AlbumSelectListViewModel
esteja disponível quando criarmos uma exibição na próxima etapa.
Adicione um EditVM
método ao StoreManagerController
. O código completo é mostrado abaixo.
//
// 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);
}
Clique com o botão direito do mouse AlbumSelectListViewModel
, selecione Resolver e, em seguida , usando MvcMusicStore.ViewModels;.
Como alternativa, você pode adicionar a seguinte instrução using:
using MvcMusicStore.ViewModels;
Clique com o botão direito do mouse EditVM
e selecione Adicionar exibição. Use as opções mostradas abaixo.
Selecione Adicionar e substitua o conteúdo do arquivo Views\StoreManager\EditVM.cshtml pelo seguinte:
@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>
A EditVM
marcação é muito semelhante à marcação original Edit
, com as seguintes exceções.
- As propriedades do
Edit
modelo na vista são do formatomodel.property
(por exemplo,model.Title
). As propriedades doEditVm
modelo na vista são do formatomodel.Album.property
(por exemplo,model.Album.Title
). Isso ocorre porque aEditVM
exibição recebe um contêiner para umAlbum
, não umAlbum
como naEdit
exibição. - O segundo parâmetro DropDownList vem do modelo de exibição, não do ViewBag.
- O auxiliar BeginForm no
EditVM
modo de exibição posta explicitamente de volta para oEdit
método de ação. Ao postar de volta naEdit
ação, não precisamos escrever umaHTTP POST EditVM
ação e podemos reutilizá-laHTTP POST
Edit
.
Execute o aplicativo e edite um álbum. Altere o URL para usar EditVM
. Altere um campo e clique no botão Salvar para verificar se o código está funcionando.
Qual abordagem você deve usar?
Todas as três abordagens mostradas são aceitáveis. Muitos desenvolvedores preferem passar explicitamente o SelectList
para o DropDownList
usando o ViewBag
. Essa abordagem tem a vantagem adicional de oferecer a flexibilidade de usar um nome mais apropriado para a coleção. A única ressalva é que você não pode nomear o ViewBag SelectList
objeto com o mesmo nome que a propriedade model.
Alguns desenvolvedores preferem a abordagem ViewModel. Outros consideram a marcação mais detalhada e o HTML gerado da abordagem ViewModel uma desvantagem.
Nesta seção, aprendemos três abordagens para usar o DropDownList com dados de categoria. Na próxima seção, mostraremos como adicionar uma nova categoria.