Examinando os métodos de ação e exibições para o controlador de filme
por Rick Anderson
Observação
Uma versão atualizada deste tutorial está disponível aqui que usa ASP.NET MVC 5 e Visual Studio 2013. É mais seguro, muito mais simples de seguir e demonstra mais recursos.
Nesta seção, você examinará os métodos de ação e exibições gerados para o controlador de filme. Em seguida, você adicionará uma página de pesquisa personalizada.
Execute o aplicativo e navegue até o Movies
controlador anexando /Movies à URL na barra de endereços do navegador. Mantenha o ponteiro do mouse sobre um link Editar para ver o URL ao qual ele está vinculado.
O link Editar foi gerado pelo Html.ActionLink
método no modo de exibição Views\Movies\Index.cshtml :
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
O Html
objeto é um auxiliar exposto usando uma propriedade na classe base System.Web.Mvc.WebViewPage . O ActionLink
método do auxiliar facilita a geração dinâmica de hiperlinks HTML vinculados a métodos de ação em controladores. O primeiro argumento para o ActionLink
método é o texto do link a ser renderizado (por exemplo, <a>Edit Me</a>
). O segundo argumento é o nome do método de ação a ser invocado. O argumento final é um objeto anônimo que gera os dados da rota (nesse caso, a ID de 4).
O link gerado mostrado na imagem anterior é http://localhost:xxxxx/Movies/Edit/4
. A rota padrão (estabelecida em App_Start\RouteConfig.cs) usa o padrão de {controller}/{action}/{id}
URL. Portanto, ASP.NET se http://localhost:xxxxx/Movies/Edit/4
traduz em uma solicitação ao Edit
método de ação do Movies
controlador com o parâmetro ID
igual a 4. Examine o código a seguir no arquivo App_Start\RouteConfig.cs .
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
Você também pode passar parâmetros de método de ação usando uma cadeia de caracteres de consulta. Por exemplo, a URL http://localhost:xxxxx/Movies/Edit?ID=4
também passa o parâmetro ID
4 para o Edit
método de ação do Movies
controlador.
Abra o Movies
controlador. Os dois Edit
métodos de ação são mostrados abaixo.
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Observe se o segundo método de ação Edit
é precedido pelo atributo HttpPost
. Esse atributo especifica que a Edit
sobrecarga do método pode ser invocada somente para solicitações POST. Você pode aplicar o HttpGet
atributo ao primeiro método de edição, mas isso não é necessário porque é o padrão. (Vamos nos referir aos métodos de ação que são atribuídos implicitamente ao HttpGet
atributo como HttpGet
métodos.)
O HttpGet
Edit
método usa o parâmetro ID do filme, pesquisa o filme usando o método Entity Framework Find
e retorna o filme selecionado para o modo de exibição Editar. O parâmetro ID especifica um valor padrão de zero se o Edit
método for chamado sem um parâmetro. Se um filme não puder ser encontrado, HttpNotFound será retornado. Quando o sistema de scaffolding criou a exibição de Edição, ele examinou a classe Movie
e o código criado para renderizar os elementos <label>
e <input>
de cada propriedade da classe. O exemplo a seguir mostra a visualização Editar que foi gerada:
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Observe como o modelo de exibição tem uma @model MvcMovie.Models.Movie
instrução na parte superior do arquivo — isso especifica que a exibição espera que o modelo para o modelo de exibição seja do tipo Movie
.
O código scaffolded usa vários métodos auxiliares para simplificar a marcação HTML. O Html.LabelFor
auxiliar exibe o nome do campo ("Título", "Data de lançamento", "Gênero" ou "Preço"). O Html.EditorFor
auxiliar renderiza um elemento HTML <input>
. O Html.ValidationMessageFor
auxiliar exibe todas as mensagens de validação associadas a essa propriedade.
Execute o aplicativo e navegue até a URL / Movies . Clique em um link Editar. No navegador, exiba a origem da página. O HTML do elemento de formulário é mostrado abaixo.
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="2.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
Os <input>
elementos estão em um elemento HTML <form>
cujo action
atributo está definido para postar na URL /Movies/Edit . Os dados do formulário serão postados no servidor quando o botão Editar for clicado.
Processando a solicitação POST
A lista a seguir mostra a versão HttpPost
do método de ação Edit
.
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
O associador de modelo MVC ASP.NET usa os valores de formulário postados e cria um Movie
objeto que é passado como o movie
parâmetro. O método ModelState.IsValid
verifica se os dados enviados no formulário podem ser usados para modificar (editar ou atualizar) um objeto Movie
. Se os dados forem válidos, os dados do filme serão salvos na Movies
coleção da db(MovieDBContext
instância). Os novos dados do filme são salvos no banco de dados chamando o SaveChanges
método de MovieDBContext
. Depois de salvar os dados, o código redireciona o usuário para o Index
método de ação da MoviesController
classe, que exibe a coleção de filmes, incluindo as alterações feitas.
Se os valores lançados não forem válidos, eles serão exibidos novamente no formulário. Os Html.ValidationMessageFor
auxiliares no modelo de exibição Edit.cshtml cuidam da exibição de mensagens de erro apropriadas.
Observação
para oferecer suporte à validação do jQuery para localidades diferentes do inglês que usam uma vírgula (",") para um ponto decimal, você deve incluir globalize.js e suas culturas/globalize.cultures.js arquivo específico (de https://github.com/jquery/globalize ) e JavaScript para usar Globalize.parseFloat
. O código a seguir mostra as modificações no arquivo Views\Movies\Edit.cshtml para trabalhar com a cultura "fr-FR":
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
O campo decimal pode exigir uma vírgula, não um ponto decimal. Como uma correção temporária, você pode adicionar o elemento de globalização ao arquivo web.config raiz dos projetos. O código a seguir mostra o elemento de globalização com a cultura definida como inglês dos Estados Unidos.
<system.web>
<globalization culture ="en-US" />
<!--elements removed for clarity-->
</system.web>
Todos os HttpGet
métodos seguem um padrão semelhante. Eles obtêm um objeto de filme (ou lista de objetos, no caso de Index
) e passam o modelo para a exibição. O Create
método passa um objeto de filme vazio para o modo de exibição Create. Todos os métodos que criam, editam, excluem ou, de outro modo, modificam dados fazem isso na sobrecarga HttpPost
do método. Modificar dados em um método HTTP GET é um risco de segurança. A modificação de dados em um método GET também viola as práticas recomendadas de HTTP e o padrão REST de arquitetura, que especifica que as solicitações GET não devem alterar o estado do aplicativo. Em outras palavras, a execução de uma operação GET deve ser uma operação segura que não tem efeitos colaterais e não modifica os dados persistentes.
Adicionando um método de pesquisa e um modo de exibição de pesquisa
Nesta seção, você adicionará um SearchIndex
método de ação que permite pesquisar filmes por gênero ou nome. Isso estará disponível usando a URL /Movies/SearchIndex . A solicitação exibirá um formulário HTML que contém elementos de entrada que um usuário pode inserir para pesquisar um filme. Quando um usuário envia o formulário, o método de ação obtém os valores de pesquisa postados pelo usuário e usa os valores para pesquisar o banco de dados.
Exibindo o formulário SearchIndex
Comece adicionando um SearchIndex
método de ação à classe existente MoviesController
. O método retornará uma exibição que contém um formulário HTML. O código é o seguinte:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
A primeira linha do SearchIndex
método cria a seguinte consulta LINQ para selecionar os filmes:
var movies = from m in db.Movies
select m;
A consulta está definida neste ponto, mas ainda não foi executada no armazenamento de dados.
Se o searchString
parâmetro contiver uma cadeia de caracteres, a consulta movies será modificada para filtrar o valor da cadeia de caracteres de pesquisa, usando o seguinte código:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
O código s => s.Title
acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos de operador de consulta padrão, como o método Where usado no código acima. As consultas LINQ não são executadas quando são definidas ou quando são modificadas chamando um método como Where
ou OrderBy
. Em vez disso, a execução da consulta é adiada, o que significa que a avaliação de uma expressão é atrasada até que seu valor realizado seja realmente iterado ou o ToList
método seja chamado. SearchIndex
No exemplo, a consulta é executada no modo de exibição SearchIndex. Para obter mais informações sobre a execução de consulta adiada, consulte Execução da consulta.
Agora você pode implementar a SearchIndex
visualização que exibirá o formulário para o usuário. Clique com o botão direito do mouse dentro do SearchIndex
método e clique em Adicionar Exibição. Na caixa de diálogo Adicionar Modo de Exibição , especifique que você passará um Movie
objeto para o modelo de exibição como sua classe de modelo. Na lista Modelo de scaffold, escolha Lista e clique em Adicionar.
Quando você clica no botão Adicionar , o modelo de exibição Views\Movies\SearchIndex.cshtml é criado. Como você selecionou Lista na lista de modelos de scaffold, o Visual Studio gerou automaticamente (scaffolded) alguma marcação padrão no modo de exibição. O andaime criou um formulário HTML. Ele examinou a Movie
classe e criou código para renderizar <label>
elementos para cada propriedade da classe. A listagem abaixo mostra a visualização Criar que foi gerada:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
Execute o aplicativo e navegue até /Movies/SearchIndex. Acrescente uma cadeia de consulta, como ?searchString=ghost
, à URL. Os filmes filtrados são exibidos.
Se você alterar a SearchIndex
assinatura do método para ter um parâmetro chamado id
, o id
parâmetro corresponderá ao espaço reservado {id}
para as rotas padrão definidas no arquivo Global.asax .
{controller}/{action}/{id}
O método original SearchIndex
é assim:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
O método modificado SearchIndex
teria a seguinte aparência:
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Agora você pode passar o título de pesquisa como dados de rota (um segmento de URL), em vez de como um valor de cadeia de consulta.
No entanto, você não pode esperar que os usuários modifiquem a URL sempre que desejarem pesquisar um filme. Então, agora você adicionará a interface do usuário para ajudá-los a filtrar filmes. Se você alterou a assinatura do SearchIndex
método para testar como passar o parâmetro de ID associado à rota, altere-o novamente para que seu SearchIndex
método use um parâmetro de string chamado searchString
:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Abra o arquivo Views\Movies\SearchIndex.cshtml e, logo após @Html.ActionLink("Create New", "Create")
, adicione o seguinte:
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" /></p>
}
O exemplo a seguir mostra uma parte do arquivo Views\Movies\SearchIndex.cshtml com a marcação de filtragem adicionada.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
O Html.BeginForm
auxiliar cria uma marca de abertura <form>
. O Html.BeginForm
auxiliar faz com que o formulário seja postado em si mesmo quando o usuário envia o formulário clicando no botão Filtrar .
Execute o aplicativo e tente procurar um filme.
Não há HttpPost
sobrecarga do SearchIndex
método. Você não precisa dele, porque o método não está alterando o estado do aplicativo, apenas filtrando dados.
Você poderá adicionar o método HttpPost SearchIndex
a seguir. Nesse caso, o invocador de ação corresponderia ao HttpPost SearchIndex
método e o HttpPost SearchIndex
método seria executado conforme mostrado na imagem abaixo.
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
No entanto, mesmo se você adicionar esta versão HttpPost
do método SearchIndex
, haverá uma limitação na forma de como isso tudo foi implementado. Imagine que você deseja adicionar uma pesquisa específica como Favoritos ou enviar um link para seus amigos para que eles possam clicar para ver a mesma lista filtrada de filmes. Observe que a URL da solicitação HTTP POST é a mesma da URL da solicitação GET (localhost:xxxxx/Movies/SearchIndex) – não há informações de pesquisa na própria URL. No momento, as informações da string de pesquisa são enviadas ao servidor como um valor de campo de formulário. Isso significa que você não pode capturar essas informações de pesquisa para marcar ou enviar a amigos em um URL.
A solução é usar uma sobrecarga que BeginForm
especifica que a solicitação POST deve adicionar as informações de pesquisa à URL e que ela deve ser roteada para a versão HttpGet do SearchIndex
método. Substitua o método sem BeginForm
parâmetros existente pelo seguinte:
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
Agora, quando você envia uma pesquisa, a URL contém uma string de consulta de pesquisa. A pesquisa também irá para o método de ação HttpGet SearchIndex
, mesmo se você tiver um método HttpPost SearchIndex
.
Adicionando pesquisa por gênero
Se você adicionou a HttpPost
versão do método, exclua-a SearchIndex
agora.
Em seguida, você adicionará um recurso para permitir que os usuários pesquisem filmes por gênero. Substitua o método SearchIndex
pelo seguinte código:
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
Esta versão do SearchIndex
método recebe um parâmetro adicional, a saber movieGenre
. As primeiras linhas de código criam um List
objeto para manter gêneros de filmes do banco de dados.
O código a seguir é uma consulta LINQ que recupera todos os gêneros do banco de dados.
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
O código usa o AddRange
método da coleção genérica List
para adicionar todos os gêneros distintos à lista. (Sem o Distinct
modificador, gêneros duplicados seriam adicionados - por exemplo, comédia seria adicionada duas vezes em nossa amostra). Em seguida, o código armazena a lista de gêneros no ViewBag
objeto.
O código a seguir mostra como verificar o movieGenre
parâmetro. Se não estiver vazio, o código restringirá ainda mais a consulta movies para limitar os filmes selecionados ao gênero especificado.
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
Adicionando marcação ao modo de exibição SearchIndex para dar suporte à pesquisa por gênero
Adicione um Html.DropDownList
auxiliar ao arquivo Views\Movies\SearchIndex.cshtml , logo antes do TextBox
auxiliar. A marcação concluída é mostrada abaixo:
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
Execute o aplicativo e navegue até /Movies/SearchIndex. Tente uma pesquisa por gênero, por nome de filme e por ambos os critérios.
Nesta seção, você examinou os métodos de ação CRUD e as exibições geradas pela estrutura. Você criou um método de ação de pesquisa e uma exibição que permitem que os usuários pesquisem por título e gênero do filme. Na próxima seção, você verá como adicionar uma propriedade ao Movie
modelo e como adicionar um inicializador que criará automaticamente um banco de dados de teste.