Parte 5: Formulários de edição e modelagem
por Jon Galloway
O MVC Music Store é um aplicativo de tutorial que apresenta e explica passo a passo como usar ASP.NET MVC e Visual Studio para desenvolvimento na Web.
A MVC Music Store é uma implementação leve de loja de exemplo que vende álbuns de música online e implementa a administração básica do site, a entrada do usuário e a funcionalidade do carrinho de compras.
Esta série de tutoriais detalha todas as etapas executadas para criar o aplicativo de exemplo ASP.NET MVC Music Store. A parte 5 abrange Editar Formulários e Modelagem.
No capítulo anterior, estávamos carregando dados de nosso banco de dados e exibindo-os. Neste capítulo, também habilitaremos a edição dos dados.
Criando o StoreManagerController
Começaremos criando um novo controlador chamado StoreManagerController. Para esse controlador, aproveitaremos os recursos de Scaffolding disponíveis na atualização de ferramentas do ASP.NET MVC 3. Defina as opções para a caixa de diálogo Adicionar Controlador, conforme mostrado abaixo.
Ao clicar no botão Adicionar, você verá que o mecanismo de scaffolding ASP.NET MVC 3 faz uma boa quantidade de trabalho para você:
- Ele cria o novo StoreManagerController com uma variável local do Entity Framework
- Ele adiciona uma pasta StoreManager à pasta Exibições do projeto
- Ele adiciona a exibição Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml e Index.cshtml, fortemente tipada para a classe Album
A nova classe de controlador StoreManager inclui ações de controlador CRUD (criar, ler, atualizar, excluir) que sabem como trabalhar com a classe de modelo Album e usar nosso contexto da Estrutura de Entidade para acesso ao banco de dados.
Modificando uma exibição scaffolded
É importante lembrar que, embora esse código tenha sido gerado para nós, ele é padrão ASP.NET código MVC, assim como escrevemos ao longo deste tutorial. A intenção é economizar o tempo que você gastaria ao escrever código do controlador clichê e criar as exibições fortemente tipadas manualmente, mas esse não é o tipo de código gerado que você pode ter visto precedido por avisos terríveis nos comentários sobre como você não deve alterar o código. Esse é o seu código e espera-se que você o altere.
Portanto, vamos começar com uma edição rápida na exibição Índice StoreManager (/Views/StoreManager/Index.cshtml). Essa exibição exibirá uma tabela que lista os Álbuns em nossa loja com links Editar/Detalhes/Excluir e inclui as propriedades públicas do Álbum. Removeremos o campo AlbumArtUrl, pois ele não é muito útil nesta exibição. Na <seção de tabela> do código de exibição, remova os <elementos th> e <td> em torno das referências AlbumArtUrl, conforme indicado pelas linhas realçadas abaixo:
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th>
AlbumArtUrl
</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.DisplayFor(modelItem => item.AlbumArtUrl)
</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>
O código de exibição modificado será exibido da seguinte maneira:
@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>
Uma primeira olhada no Gerenciador de Lojas
Agora execute o aplicativo e navegue até /StoreManager/. Isso exibe o Índice do Gerenciador de Lojas que acabamos de modificar, mostrando uma lista dos álbuns na loja com links para Editar, Detalhes e Excluir.
Clicar no link Editar exibe um formulário de edição com campos para o Álbum, incluindo listas suspensas para Gênero e Artista.
Clique no link "Voltar à Lista" na parte inferior e clique no link Detalhes de um Álbum. Isso exibe as informações detalhadas de um Álbum individual.
Novamente, clique no link Voltar à Lista e, em seguida, clique em um link Excluir. Isso exibe uma caixa de diálogo de confirmação, mostrando os detalhes do álbum e perguntando se temos certeza de que queremos excluí-lo.
Clicar no botão Excluir na parte inferior excluirá o álbum e retornará você para a página Índice, que mostra o álbum excluído.
Ainda não terminamos com o Gerenciador de Lojas, mas temos o controlador de trabalho e o código de exibição para as operações CRUD a serem iniciadas.
Examinando o código do Controlador do Gerenciador de Lojas
O Controlador do Gerenciador de Lojas contém uma boa quantidade de código. Vamos passar por isso de cima para baixo. O controlador inclui alguns namespaces padrão para um controlador MVC, bem como uma referência ao namespace Modelos. O controlador tem uma instância privada de MusicStoreEntities, usada por cada uma das ações do controlador para acesso a dados.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
namespace MvcMusicStore.Controllers
{
public class StoreManagerController : Controller
{
private MusicStoreEntities db = new MusicStoreEntities();
Ações de Índice e Detalhes do Gerenciador de Lojas
A exibição de índice recupera uma lista de Álbuns, incluindo as informações referenciadas de Gênero e Artista de cada álbum, como vimos anteriormente ao trabalhar no método Store Browse. O modo de exibição Index está seguindo as referências aos objetos vinculados para que ele possa exibir o nome de gênero e o nome do artista de cada álbum, de modo que o controlador esteja sendo eficiente e consultando essas informações na solicitação original.
//
// GET: /StoreManager/
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
return View(albums.ToList());
}
A ação do controlador Detalhes do Controlador StoreManager funciona exatamente da mesma forma que a ação Detalhes do Controlador da Loja que escrevemos anteriormente – ela consulta o Álbum por ID usando o método Find() e, em seguida, retorna-o para o modo de exibição.
//
// GET: /StoreManager/Details/5
public ViewResult Details(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
Os métodos criar ação
Os métodos de ação Criar são um pouco diferentes dos que vimos até agora, porque eles lidam com a entrada de formulário. Quando um usuário visita pela primeira vez /StoreManager/Create/ ele será mostrado um formulário vazio. Esta página HTML conterá um <elemento de formulário> que contém elementos de entrada suspensos e de caixa de texto em que eles podem inserir os detalhes do álbum.
Depois que o usuário preencher os valores do formulário Álbum, ele poderá pressionar o botão "Salvar" para enviar essas alterações de volta ao nosso aplicativo para salvar no banco de dados. Quando o usuário pressionar o botão "salvar", o <formulário> executará um HTTP-POST de volta à URL /StoreManager/Create/ e enviará os <valores de formulário> como parte do HTTP-POST.
ASP.NET MVC nos permite dividir facilmente a lógica desses dois cenários de invocação de URL, permitindo-nos implementar dois métodos de ação "Criar" separados em nossa classe StoreManagerController – um para lidar com a navegação HTTP-GET inicial para a URL /StoreManager/Create/ e o outro para lidar com o HTTP-POST das alterações enviadas.
Passando informações para uma exibição usando ViewBag
Usamos o ViewBag anteriormente neste tutorial, mas não falamos muito sobre isso. O ViewBag nos permite passar informações para a exibição sem usar um objeto de modelo fortemente tipado. Nesse caso, nossa ação editar controlador HTTP-GET precisa passar uma lista de Gêneros e Artistas para o formulário para preencher as listas suspensas e a maneira mais simples de fazer isso é devolvê-los como itens ViewBag.
O ViewBag é um objeto dinâmico, o que significa que você pode digitar ViewBag.Foo ou ViewBag.YourNameHere sem escrever código para definir essas propriedades. Nesse caso, o código do controlador usa ViewBag.GenreId e ViewBag.ArtistId para que os valores suspensos enviados com o formulário sejam GenreId e ArtistId, que são as propriedades do Álbum que serão definidas.
Esses valores suspensos são retornados ao formulário usando o objeto SelectList, que é criado apenas para essa finalidade. Isso é feito usando um código como este:
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
Como você pode ver no código do método de ação, três parâmetros estão sendo usados para criar este objeto:
- A lista de itens que a lista suspensa exibirá. Observe que isso não é apenas uma cadeia de caracteres - estamos passando uma lista de Gêneros.
- O próximo parâmetro que está sendo passado para SelectList é o Valor Selecionado. Assim, o SelectList sabe como pré-selecionar um item na lista. Isso será mais fácil de entender quando examinarmos o formulário Editar, que é bastante semelhante.
- O parâmetro final é a propriedade a ser exibida. Nesse caso, isso indica que a propriedade Genre.Name é o que será mostrado ao usuário.
Com isso em mente, então, a ação CRIAR HTTP-GET é bastante simples – duas SelectLists são adicionadas ao ViewBag e nenhum objeto de modelo é passado para o formulário (já que ele ainda não foi criado).
//
// GET: /StoreManager/Create
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");
return View();
}
Auxiliares html para exibir as listas suspensas no modo de exibição Criar
Como falamos sobre como os valores suspensos são passados para o modo de exibição, vamos dar uma olhada rápida na exibição para ver como esses valores são exibidos. No código de exibição (/Views/StoreManager/Create.cshtml), você verá que a chamada a seguir é feita para exibir a lista suspensa Gênero.
@Html.DropDownList("GenreId",
String.Empty)
Isso é conhecido como auxiliar html – um método utilitário que executa uma tarefa de exibição comum. Auxiliares HTML são muito úteis para manter nosso código de exibição conciso e legível. O auxiliar Html.DropDownList é fornecido pelo ASP.NET MVC, mas como veremos mais tarde, é possível criar nossos próprios auxiliares para exibir o código que reutilizaremos em nosso aplicativo.
A chamada Html.DropDownList só precisa ser informada de duas coisas: onde exibir a lista e qual valor (se houver) deve ser pré-selecionado. O primeiro parâmetro, GenreId, informa ao DropDownList para procurar um valor chamado GenreId no modelo ou ViewBag. O segundo parâmetro é usado para indicar o valor a ser mostrado como selecionado inicialmente na lista suspensa. Como esse formulário é um formulário Create, não há nenhum valor a ser pré-selecionado e String.Empty é passado.
Manipulando os valores de Formulário Postado
Como discutimos antes, há dois métodos de ação associados a cada formulário. O primeiro manipula a solicitação HTTP-GET e exibe o formulário. O segundo manipula a solicitação HTTP-POST, que contém os valores de formulário enviados. Observe que a ação do controlador tem um atributo [HttpPost], que informa ASP.NET MVC que ele só deve responder a solicitações HTTP-POST.
//
// 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);
}
Esta ação tem quatro responsabilidades:
-
- Ler os valores do formulário
-
- Verificar se os valores do formulário passam por regras de validação
-
- Se o envio do formulário for válido, salve os dados e exiba a lista atualizada
-
- Se o envio do formulário não for válido, reproduza o formulário com erros de validação
Lendo valores de formulário com associação de modelo
A ação do controlador está processando um envio de formulário que inclui valores para GenreId e ArtistId (na lista suspensa) e valores de caixa de texto para Title, Price e AlbumArtUrl. Embora seja possível acessar diretamente os valores do formulário, uma abordagem melhor é usar os recursos de Associação de Modelo integrados ASP.NET MVC. Quando uma ação do controlador usa um tipo de modelo como um parâmetro, ASP.NET MVC tentará preencher um objeto desse tipo usando entradas de formulário (bem como valores de rota e querystring). Ele faz isso procurando valores cujos nomes correspondem às propriedades do objeto modelo, por exemplo, ao definir o valor GenreId do novo objeto Album, ele procura uma entrada com o nome GenreId. Quando você cria exibições usando os métodos padrão em ASP.NET MVC, os formulários sempre serão renderizados usando nomes de propriedade como nomes de campo de entrada, portanto, os nomes dos campos serão apenas correspondentes.
Validando o modelo
O modelo é validado com uma chamada simples para ModelState.IsValid. Ainda não adicionamos nenhuma regra de validação à nossa classe album - faremos isso em um pouco - então agora esse marcar não tem muito o que fazer. O importante é que esse marcar ModelStat.IsValid se adapte às regras de validação que colocamos em nosso modelo, portanto, alterações futuras nas regras de validação não exigirão atualizações no código de ação do controlador.
Salvando os valores enviados
Se o envio do formulário for aprovado na validação, será hora de salvar os valores no banco de dados. Com o Entity Framework, isso requer apenas adicionar o modelo à coleção Albums e chamar SaveChanges.
db.Albums.Add(album);
db.SaveChanges();
O Entity Framework gera os comandos SQL apropriados para persistir o valor. Depois de salvar os dados, redirecionamos de volta para a lista de Álbuns para que possamos ver nossa atualização. Isso é feito retornando RedirectToAction com o nome da ação do controlador que desejamos exibir. Nesse caso, esse é o método Index.
Exibindo envios de formulário inválidos com erros de validação
No caso de entrada de formulário inválida, os valores suspensos são adicionados ao ViewBag (como no caso HTTP-GET) e os valores do modelo associado são passados de volta para o modo de exibição para exibição. Os erros de validação são exibidos automaticamente usando o @Html.ValidationMessageFor Auxiliar HTML.
Testando o Formulário de Criação
Para testar isso, execute o aplicativo e navegue até /StoreManager/Create/ – isso mostrará o formulário em branco que foi retornado pelo método StoreController Create HTTP-GET.
Preencha alguns valores e clique no botão Criar para enviar o formulário.
Manipulando edições
O par de ação Editar (HTTP-GET e HTTP-POST) é muito semelhante aos métodos de ação Criar que acabamos de examinar. Como o cenário de edição envolve trabalhar com um álbum existente, o método Editar HTTP-GET carrega o Álbum com base no parâmetro "id", passado por meio da rota. Esse código para recuperar um álbum de AlbumId é o mesmo que examinamos anteriormente na ação Controlador de detalhes. Assim como acontece com o método Create/HTTP-GET, os valores suspensos são retornados por meio do ViewBag. Isso nos permite retornar um Álbum como nosso objeto modelo para a exibição (que é fortemente tipada para a classe Album) ao passar dados adicionais (por exemplo, uma lista de Gêneros) por meio do ViewBag.
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
A ação Editar HTTP-POST é muito semelhante à ação Criar HTTP-POST. A única diferença é que, em vez de adicionar um novo álbum ao banco de dados. Coleção Albums, estamos encontrando a instância atual do Álbum usando db. Entry(album) e definindo seu estado como Modificado. Isso informa ao Entity Framework que estamos modificando um álbum existente em vez de criar um novo.
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
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);
}
Podemos testar isso executando o aplicativo e navegando até /StoreManger/, em seguida, clicando no link Editar para um álbum.
Isso exibe o formulário Editar mostrado pelo método Editar HTTP-GET. Preencha alguns valores e clique no botão Salvar.
Isso posta o formulário, salva os valores e nos retorna à lista Álbum, mostrando que os valores foram atualizados.
Manipulando a exclusão
A exclusão segue o mesmo padrão que Editar e Criar, usando uma ação de controlador para exibir o formulário de confirmação e outra ação do controlador para lidar com o envio do formulário.
A ação do controlador de exclusão HTTP-GET é exatamente a mesma que a ação anterior do controlador de Detalhes do Gerenciador de Lojas.
//
// GET: /StoreManager/Delete/5
public ActionResult Delete(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
Exibimos um formulário fortemente tipado para um tipo de Álbum, usando o modelo excluir conteúdo de exibição.
O modelo Excluir mostra todos os campos para o modelo, mas podemos simplificar isso um pouco. Altere o código de exibição em /Views/StoreManager/Delete.cshtml para o seguinte.
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Delete";
}
<h2>Delete Confirmation</h2>
<p>Are you sure you want to delete the album titled
<strong>@Model.Title</strong>?
</p>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" />
</p>
<p>
@Html.ActionLink("Back to
List", "Index")
</p>
}
Isso exibe uma confirmação simplificada de Exclusão.
Clicar no botão Excluir faz com que o formulário seja postado novamente no servidor, que executa a ação DeleteConfirmed.
//
// POST: /StoreManager/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Album album = db.Albums.Find(id);
db.Albums.Remove(album);
db.SaveChanges();
return RedirectToAction("Index");
}
Nossa ação do controlador de exclusão HTTP-POST executa as seguintes ações:
-
- Carrega o álbum por ID
-
- Exclui o álbum e salva as alterações
-
- Redireciona para o Índice, mostrando que o Álbum foi removido da lista
Para testar isso, execute o aplicativo e navegue até /StoreManager. Selecione um álbum na lista e clique no link Excluir.
Isso exibe nossa tela de confirmação Excluir.
Clicar no botão Excluir remove o álbum e nos retorna à página Índice do Gerenciador da Loja, que mostra que o álbum foi excluído.
Usando um auxiliar HTML personalizado para truncar texto
Temos um problema potencial com nossa página Índice do Gerenciador de Lojas. Nossas propriedades Título do Álbum e Nome do Artista podem ser longas o suficiente para que possam jogar fora nossa formatação de tabela. Criaremos um Auxiliar html personalizado para nos permitir truncar facilmente essas e outras propriedades em nossas Exibições.
A sintaxe do @helper Razor tornou muito fácil criar suas próprias funções auxiliares para uso em seus modos de exibição. Abra a exibição /Views/StoreManager/Index.cshtml e adicione o código a seguir diretamente após a @model linha.
@helper Truncate(string
input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
Esse método auxiliar usa uma cadeia de caracteres e um comprimento máximo para permitir. Se o texto fornecido for menor que o comprimento especificado, o auxiliar o gerará como está. Se for mais longo, ele truncará o texto e renderizará "..." para o restante.
Agora podemos usar nosso auxiliar Truncate para garantir que as propriedades Título do Álbum e Nome do Artista sejam inferiores a 25 caracteres. O código de exibição completo usando nosso novo auxiliar Truncate aparece abaixo.
@model IEnumerable<MvcMusicStore.Models.Album>
@helper Truncate(string input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
@{
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>
@Truncate(item.Artist.Name, 25)
</td>
<td>
@Truncate(item.Title, 25)
</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>
Agora, quando navegamos na URL /StoreManager/, os álbuns e títulos são mantidos abaixo de nossos comprimentos máximos.
Observação: isso mostra o caso simples de criar e usar um auxiliar em uma exibição. Para saber mais sobre como criar auxiliares que você pode usar em todo o seu site, consulte minha postagem no blog: http://bit.ly/mvc3-helper-options