Parte 7, adicione a pesquisa a um aplicativo ASP.NET Core MVC
Observação
Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Nesta seção, você adiciona a funcionalidade de pesquisa ao método de ação Index
que permite pesquisar filmes por gênero ou nome.
Atualize o método Index
encontrado dentro de Controllers/MoviesController.cs
com o seguinte código:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
A linha a seguir do método de ação Index
cria uma consulta LINQ para selecionar os filmes:
var movies = from m in _context.Movie
select m;
A consulta é definida apenas neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString
contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar o valor da cadeia de caracteres de pesquisa:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
O código s => s.Title!.ToUpper().Contains(searchString.ToUpper())
acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos de operadores de consulta padrão, como o método Where ou Contains
(usado no código acima). As consultas LINQ não são executadas quando são definidas ou no momento em que são modificadas com uma chamada a um método, como Where
, Contains
ou OrderBy
. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu valor realizado seja, de fato, iterado ou o método ToListAsync
seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Execução da consulta.
Observação
O método Contains é executado no banco de dados, não no código C#. A diferenciação de maiúsculas e minúsculas na consulta depende do banco de dados e da ordenação. No SQL Server, Contains
é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. O SQLite com a ordenação padrão combina instruções que diferenciam ou não maiúsculas e minúsculas, a depender da consulta. Para obter informações sobre como instruir as consultas SQLite a não diferenciar maiúsculas de minúsculas, consulte os seguintes itens:
Navegue até /Movies/Index
. Acrescente uma cadeia de consulta, como ?searchString=Ghost
, à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do método Index
para que ele tenha um parâmetro chamado id
, o parâmetro id
corresponderá o espaço reservado {id}
opcional com as rotas padrão definidas em Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Altere o parâmetro para id
e todas as ocorrências de searchString
para id
.
O método Index
anterior:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
O método Index
atualizado com o parâmetro id
:
public async Task<IActionResult> Index(string id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(id.ToUpper()));
}
return View(await movies.ToListAsync());
}
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. Agora você adicionará os elementos da interface do usuário para ajudá-los a filtrar filmes. Se você tiver alterado a assinatura do método Index
para testar como passar o parâmetro ID
associado à rota, altere-o novamente para que ele use um parâmetro chamado searchString
:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
Abra o arquivo Views/Movies/Index.cshtml
e adicione a marcação <form>
destacada abaixo:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
A marcação <form>
HTML usa o Auxiliar de Marcação de Formulário. Portanto, quando você enviar o formulário, a cadeia de caracteres de filtro será enviada para a ação Index
do controlador de filmes. Salve as alterações e, em seguida, teste o filtro.
Não há nenhuma sobrecarga [HttpPost]
do método Index
que poderia ser esperada. Isso não é necessário, porque o método não está alterando o estado do aplicativo, apenas filtrando os dados.
Você poderá adicionar o método [HttpPost] Index
a seguir.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
O parâmetro notUsed
é usado para criar uma sobrecarga para o método Index
. Falaremos sobre isso mais adiante no tutorial.
Se você adicionar esse método, o chamador de ação fará uma correspondência com o método [HttpPost] Index
e o método [HttpPost] Index
será executado conforme mostrado na imagem abaixo.
No entanto, mesmo se você adicionar esta versão [HttpPost]
do método Index
, 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 que a URL da solicitação GET (localhost:{PORT}/Movies/Index) – não há nenhuma informação de pesquisa na URL. As informações de cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Verifique isso com as ferramentas do Desenvolvedor do navegador ou a excelente ferramenta Fiddler.
A imagem a seguir mostra as Ferramentas de desenvolvedor do navegador Chrome com as guias Rede e Cabeçalhos selecionadas:
As guias Rede e Conteúdo são selecionadas para exibir os dados do formulário:
Veja o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Conforme mencionado no tutorial anterior, o Auxiliar de Marca de Formulário gera um token antifalsificação XSRF. Não modificaremos os dados e, portanto, não precisamos validar o token no método do controlador.
Como o parâmetro de pesquisa está no corpo da solicitação e não na URL, não é possível capturar essas informações de pesquisa para adicionar como Favoritos ou compartilhar com outras pessoas. Corrigimos isso especificando que a solicitação deve ser HTTP GET
na marca form
encontrada no arquivo Views/Movies/Index.cshtml
.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
Agora, quando você enviar uma pesquisa, a URL conterá a cadeia de consulta da pesquisa. A pesquisa também irá para o método de ação HttpGet Index
, mesmo se você tiver um método HttpPost Index
.
Adicionar pesquisa por gênero
Adicione a seguinte classe MovieGenreViewModel
à pasta Models:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models;
public class MovieGenreViewModel
{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}
O modelo de exibição do gênero de filme conterá:
- Uma lista de filmes.
- Uma
SelectList
que contém a lista de gêneros. Isso permite que o usuário selecione um gênero na lista. MovieGenre
, que contém o gênero selecionado.SearchString
, que contém o texto que os usuários inserem na caixa de texto de pesquisa.
Substitua o método Index
em MoviesController.cs
pelo seguinte código:
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ
que recupera todos os gêneros do banco de dados.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
A SelectList
de gêneros é criada com a projeção dos gêneros distintos (não desejamos que nossa lista de seleção tenha gêneros duplicados).
Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.
Adicionar pesquisa por gênero à exibição Índice
Atualize Index.cshtml
, encontrado em Views/Movies/, da seguinte maneira:
@model MvcMovie.Models.MovieGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<label>Title: <input type="text" asp-for="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<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>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine a expressão lambda usada no auxiliar HTML a seguir:
@Html.DisplayNameFor(model => model.Movies![0].Title)
No código anterior, o Auxiliar de HTML DisplayNameFor
inspeciona a propriedade Title
referenciada na expressão lambda para determinar o nome de exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model
, model.Movies
ou model.Movies[0]
é null
ou vazio. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)
), os valores da propriedade do modelo são avaliados. O !
após model.Movies
é o operador que permite valor nulo, que é usado para declarar que Movies
não é nulo.
Teste o aplicativo pesquisando por gênero, título do filme e por ambos:
Nesta seção, você adiciona a funcionalidade de pesquisa ao método de ação Index
que permite pesquisar filmes por gênero ou nome.
Atualize o método Index
encontrado dentro de Controllers/MoviesController.cs
com o seguinte código:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
A linha a seguir do método de ação Index
cria uma consulta LINQ para selecionar os filmes:
var movies = from m in _context.Movie
select m;
A consulta é definida apenas neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString
contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar o valor da cadeia de caracteres de pesquisa:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
O código s => s.Title!.ToUpper().Contains(searchString.ToUpper())
acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos de operadores de consulta padrão, como o método Where ou Contains
(usado no código acima). As consultas LINQ não são executadas quando são definidas ou no momento em que são modificadas com uma chamada a um método, como Where
, Contains
ou OrderBy
. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu valor realizado seja, de fato, iterado ou o método ToListAsync
seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Execução da consulta.
Observação
O método Contains é executado no banco de dados, não no código C#. A diferenciação de maiúsculas e minúsculas na consulta depende do banco de dados e da ordenação. No SQL Server, Contains
é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. O SQLite com a ordenação padrão combina instruções que diferenciam ou não maiúsculas e minúsculas, a depender da consulta. Para obter informações sobre como instruir as consultas SQLite a não diferenciar maiúsculas de minúsculas, consulte os seguintes itens:
Navegue até /Movies/Index
. Acrescente uma cadeia de consulta, como ?searchString=Ghost
, à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do método Index
para que ele tenha um parâmetro chamado id
, o parâmetro id
corresponderá o espaço reservado {id}
opcional com as rotas padrão definidas em Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Altere o parâmetro para id
e todas as ocorrências de searchString
para id
.
O método Index
anterior:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
O método Index
atualizado com o parâmetro id
:
public async Task<IActionResult> Index(string id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(id.ToUpper()));
}
return View(await movies.ToListAsync());
}
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. Agora você adicionará os elementos da interface do usuário para ajudá-los a filtrar filmes. Se você tiver alterado a assinatura do método Index
para testar como passar o parâmetro ID
associado à rota, altere-o novamente para que ele use um parâmetro chamado searchString
:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
Abra o arquivo Views/Movies/Index.cshtml
e adicione a marcação <form>
destacada abaixo:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
A marcação <form>
HTML usa o Auxiliar de Marcação de Formulário. Portanto, quando você enviar o formulário, a cadeia de caracteres de filtro será enviada para a ação Index
do controlador de filmes. Salve as alterações e, em seguida, teste o filtro.
Não há nenhuma sobrecarga [HttpPost]
do método Index
que poderia ser esperada. Isso não é necessário, porque o método não está alterando o estado do aplicativo, apenas filtrando os dados.
Você poderá adicionar o método [HttpPost] Index
a seguir.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
O parâmetro notUsed
é usado para criar uma sobrecarga para o método Index
. Falaremos sobre isso mais adiante no tutorial.
Se você adicionar esse método, o chamador de ação fará uma correspondência com o método [HttpPost] Index
e o método [HttpPost] Index
será executado conforme mostrado na imagem abaixo.
No entanto, mesmo se você adicionar esta versão [HttpPost]
do método Index
, 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 que a URL da solicitação GET (localhost:{PORT}/Movies/Index) – não há nenhuma informação de pesquisa na URL. As informações de cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Verifique isso com as ferramentas do Desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas do Desenvolvedor do navegador Chrome:
Veja o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Conforme mencionado no tutorial anterior, o Auxiliar de Marca de Formulário gera um token antifalsificação XSRF. Não modificaremos os dados e, portanto, não precisamos validar o token no método do controlador.
Como o parâmetro de pesquisa está no corpo da solicitação e não na URL, não é possível capturar essas informações de pesquisa para adicionar como Favoritos ou compartilhar com outras pessoas. Corrigimos isso especificando que a solicitação deve ser HTTP GET
encontrada no arquivo Views/Movies/Index.cshtml
.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
Agora, quando você enviar uma pesquisa, a URL conterá a cadeia de consulta da pesquisa. A pesquisa também irá para o método de ação HttpGet Index
, mesmo se você tiver um método HttpPost Index
.
A seguinte marcação mostra a alteração para a marcação form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adicionar pesquisa por gênero
Adicione a seguinte classe MovieGenreViewModel
à pasta Models:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models;
public class MovieGenreViewModel
{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}
O modelo de exibição do gênero de filme conterá:
- Uma lista de filmes.
- Uma
SelectList
que contém a lista de gêneros. Isso permite que o usuário selecione um gênero na lista. MovieGenre
, que contém o gênero selecionado.SearchString
, que contém o texto que os usuários inserem na caixa de texto de pesquisa.
Substitua o método Index
em MoviesController.cs
pelo seguinte código:
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ
que recupera todos os gêneros do banco de dados.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
A SelectList
de gêneros é criada com a projeção dos gêneros distintos (não desejamos que nossa lista de seleção tenha gêneros duplicados).
Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.
Adicionar pesquisa por gênero à exibição Índice
Atualize Index.cshtml
, encontrado em Views/Movies/, da seguinte maneira:
@model MvcMovie.Models.MovieGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<label>Title: <input type="text" asp-for="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<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>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine a expressão lambda usada no auxiliar HTML a seguir:
@Html.DisplayNameFor(model => model.Movies![0].Title)
No código anterior, o Auxiliar de HTML DisplayNameFor
inspeciona a propriedade Title
referenciada na expressão lambda para determinar o nome de exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model
, model.Movies
ou model.Movies[0]
é null
ou vazio. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)
), os valores da propriedade do modelo são avaliados. O !
após model.Movies
é o operador que permite valor nulo, que é usado para declarar que Movies
não é nulo.
Teste o aplicativo pesquisando por gênero, título do filme e por ambos:
Nesta seção, você adiciona a funcionalidade de pesquisa ao método de ação Index
que permite pesquisar filmes por gênero ou nome.
Atualize o método Index
encontrado dentro de Controllers/MoviesController.cs
com o seguinte código:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
A linha a seguir do método de ação Index
cria uma consulta LINQ para selecionar os filmes:
var movies = from m in _context.Movie
select m;
A consulta é definida apenas neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString
contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar o valor da cadeia de caracteres de pesquisa:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
O código s => s.Title!.ToUpper().Contains(searchString.ToUpper())
acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos de operadores de consulta padrão, como o método Where ou Contains
(usado no código acima). As consultas LINQ não são executadas quando são definidas ou no momento em que são modificadas com uma chamada a um método, como Where
, Contains
ou OrderBy
. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu valor realizado seja, de fato, iterado ou o método ToListAsync
seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Execução da consulta.
Observação
O método Contains é executado no banco de dados, não no código C#. A diferenciação de maiúsculas e minúsculas na consulta depende do banco de dados e da ordenação. No SQL Server, Contains
é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. O SQLite com a ordenação padrão combina instruções que diferenciam ou não maiúsculas e minúsculas, a depender da consulta. Para obter informações sobre como instruir as consultas SQLite a não diferenciar maiúsculas de minúsculas, consulte os seguintes itens:
Navegue até /Movies/Index
. Acrescente uma cadeia de consulta, como ?searchString=Ghost
, à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do método Index
para que ele tenha um parâmetro chamado id
, o parâmetro id
corresponderá o espaço reservado {id}
opcional com as rotas padrão definidas em Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Altere o parâmetro para id
e todas as ocorrências de searchString
para id
.
O método Index
anterior:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
O método Index
atualizado com o parâmetro id
:
public async Task<IActionResult> Index(string id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(id.ToUpper()));
}
return View(await movies.ToListAsync());
}
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. Agora você adicionará os elementos da interface do usuário para ajudá-los a filtrar filmes. Se você tiver alterado a assinatura do método Index
para testar como passar o parâmetro ID
associado à rota, altere-o novamente para que ele use um parâmetro chamado searchString
:
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
return View(await movies.ToListAsync());
}
Abra o arquivo Views/Movies/Index.cshtml
e adicione a marcação <form>
destacada abaixo:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
A marcação <form>
HTML usa o Auxiliar de Marcação de Formulário. Portanto, quando você enviar o formulário, a cadeia de caracteres de filtro será enviada para a ação Index
do controlador de filmes. Salve as alterações e, em seguida, teste o filtro.
Não há nenhuma sobrecarga [HttpPost]
do método Index
que poderia ser esperada. Isso não é necessário, porque o método não está alterando o estado do aplicativo, apenas filtrando os dados.
Você poderá adicionar o método [HttpPost] Index
a seguir.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
O parâmetro notUsed
é usado para criar uma sobrecarga para o método Index
. Falaremos sobre isso mais adiante no tutorial.
Se você adicionar esse método, o chamador de ação fará uma correspondência com o método [HttpPost] Index
e o método [HttpPost] Index
será executado conforme mostrado na imagem abaixo.
No entanto, mesmo se você adicionar esta versão [HttpPost]
do método Index
, 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 que a URL da solicitação GET (localhost:{PORT}/Movies/Index) – não há nenhuma informação de pesquisa na URL. As informações de cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Verifique isso com as ferramentas do Desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas do Desenvolvedor do navegador Chrome:
Veja o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Conforme mencionado no tutorial anterior, o Auxiliar de Marca de Formulário gera um token antifalsificação XSRF. Não modificaremos os dados e, portanto, não precisamos validar o token no método do controlador.
Como o parâmetro de pesquisa está no corpo da solicitação e não na URL, não é possível capturar essas informações de pesquisa para adicionar como Favoritos ou compartilhar com outras pessoas. Corrigimos isso especificando que a solicitação deve ser HTTP GET
encontrada no arquivo Views/Movies/Index.cshtml
.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
Agora, quando você enviar uma pesquisa, a URL conterá a cadeia de consulta da pesquisa. A pesquisa também irá para o método de ação HttpGet Index
, mesmo se você tiver um método HttpPost Index
.
A seguinte marcação mostra a alteração para a marcação form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adicionar pesquisa por gênero
Adicione a seguinte classe MovieGenreViewModel
à pasta Models:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models;
public class MovieGenreViewModel
{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}
O modelo de exibição do gênero de filme conterá:
- Uma lista de filmes.
- Uma
SelectList
que contém a lista de gêneros. Isso permite que o usuário selecione um gênero na lista. MovieGenre
, que contém o gênero selecionado.SearchString
, que contém o texto que os usuários inserem na caixa de texto de pesquisa.
Substitua o método Index
em MoviesController.cs
pelo seguinte código:
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ
que recupera todos os gêneros do banco de dados.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
A SelectList
de gêneros é criada com a projeção dos gêneros distintos (não desejamos que nossa lista de seleção tenha gêneros duplicados).
Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.
Adicionar pesquisa por gênero à exibição Índice
Atualize Index.cshtml
, encontrado em Views/Movies/, da seguinte maneira:
@model MvcMovie.Models.MovieGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<label>Title: <input type="text" asp-for="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<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>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine a expressão lambda usada no auxiliar HTML a seguir:
@Html.DisplayNameFor(model => model.Movies![0].Title)
No código anterior, o Auxiliar de HTML DisplayNameFor
inspeciona a propriedade Title
referenciada na expressão lambda para determinar o nome de exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model
, model.Movies
ou model.Movies[0]
é null
ou vazio. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)
), os valores da propriedade do modelo são avaliados. O !
após model.Movies
é o operador que permite valor nulo, que é usado para declarar que Movies
não é nulo.
Teste o aplicativo pesquisando por gênero, título do filme e por ambos:
Nesta seção, você adiciona a funcionalidade de pesquisa ao método de ação Index
que permite pesquisar filmes por gênero ou nome.
Atualize o método Index
encontrado dentro de Controllers/MoviesController.cs
com o seguinte código:
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
A primeira linha do método de ação Index
cria uma consulta LINQ para selecionar os filmes:
var movies = from m in _context.Movie
select m;
A consulta é somente definida neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString
contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar o valor da cadeia de caracteres de pesquisa:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
O código s => s.Title!.Contains(searchString)
acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos de operadores de consulta padrão, como o método Where ou Contains
(usado no código acima). As consultas LINQ não são executadas quando são definidas ou no momento em que são modificadas com uma chamada a um método, como Where
, Contains
ou OrderBy
. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu valor realizado seja, de fato, iterado ou o método ToListAsync
seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Execução da consulta.
O método Contains é executado no banco de dados, não no código C# mostrado acima. A diferenciação de maiúsculas e minúsculas na consulta depende do banco de dados e da ordenação. No SQL Server, Contains
é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. No SQLite, com a ordenação padrão, ele diferencia maiúsculas de minúsculas.
Navegue até /Movies/Index
. Acrescente uma cadeia de consulta, como ?searchString=Ghost
, à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do método Index
para que ele tenha um parâmetro chamado id
, o parâmetro id
corresponderá o espaço reservado {id}
opcional com as rotas padrão definidas em Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Altere o parâmetro para id
e todas as ocorrências de searchString
para id
.
O método Index
anterior:
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
O método Index
atualizado com o parâmetro id
:
public async Task<IActionResult> Index(string id)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.Contains(id));
}
return View(await movies.ToListAsync());
}
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. Agora você adicionará os elementos da interface do usuário para ajudá-los a filtrar filmes. Se você tiver alterado a assinatura do método Index
para testar como passar o parâmetro ID
associado à rota, altere-o novamente para que ele use um parâmetro chamado searchString
:
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
Abra o arquivo Views/Movies/Index.cshtml
e adicione a marcação <form>
destacada abaixo:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
A marcação <form>
HTML usa o Auxiliar de Marcação de Formulário. Portanto, quando você enviar o formulário, a cadeia de caracteres de filtro será enviada para a ação Index
do controlador de filmes. Salve as alterações e, em seguida, teste o filtro.
Não há nenhuma sobrecarga [HttpPost]
do método Index
que poderia ser esperada. Isso não é necessário, porque o método não está alterando o estado do aplicativo, apenas filtrando os dados.
Você poderá adicionar o método [HttpPost] Index
a seguir.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
O parâmetro notUsed
é usado para criar uma sobrecarga para o método Index
. Falaremos sobre isso mais adiante no tutorial.
Se você adicionar esse método, o chamador de ação fará uma correspondência com o método [HttpPost] Index
e o método [HttpPost] Index
será executado conforme mostrado na imagem abaixo.
No entanto, mesmo se você adicionar esta versão [HttpPost]
do método Index
, 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 que a URL da solicitação GET (localhost:{PORT}/Movies/Index) – não há nenhuma informação de pesquisa na URL. As informações de cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Verifique isso com as ferramentas do Desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas do Desenvolvedor do navegador Chrome:
Veja o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Conforme mencionado no tutorial anterior, o Auxiliar de Marca de Formulário gera um token antifalsificação XSRF. Não modificaremos os dados e, portanto, não precisamos validar o token no método do controlador.
Como o parâmetro de pesquisa está no corpo da solicitação e não na URL, não é possível capturar essas informações de pesquisa para adicionar como Favoritos ou compartilhar com outras pessoas. Corrigimos isso especificando que a solicitação deve ser HTTP GET
encontrada no arquivo Views/Movies/Index.cshtml
.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
Agora, quando você enviar uma pesquisa, a URL conterá a cadeia de consulta da pesquisa. A pesquisa também irá para o método de ação HttpGet Index
, mesmo se você tiver um método HttpPost Index
.
A seguinte marcação mostra a alteração para a marcação form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adicionar pesquisa por gênero
Adicione a seguinte classe MovieGenreViewModel
à pasta Models:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}
}
O modelo de exibição do gênero de filme conterá:
- Uma lista de filmes.
- Uma
SelectList
que contém a lista de gêneros. Isso permite que o usuário selecione um gênero na lista. MovieGenre
, que contém o gênero selecionado.SearchString
, que contém o texto que os usuários inserem na caixa de texto de pesquisa.
Substitua o método Index
em MoviesController.cs
pelo seguinte código:
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ
que recupera todos os gêneros do banco de dados.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
A SelectList
de gêneros é criada com a projeção dos gêneros distintos (não desejamos que nossa lista de seleção tenha gêneros duplicados).
Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.
Adicionar pesquisa por gênero à exibição Índice
Atualize Index.cshtml
, encontrado em Views/Movies/, da seguinte maneira:
@model MvcMovie.Models.MovieGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<label>Title: <input type="text" asp-for="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies)
{
<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>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine a expressão lambda usada no auxiliar HTML a seguir:
@Html.DisplayNameFor(model => model.Movies[0].Title)
No código anterior, o Auxiliar de HTML DisplayNameFor
inspeciona a propriedade Title
referenciada na expressão lambda para determinar o nome de exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model
, model.Movies
ou model.Movies[0]
é null
ou vazio. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)
), os valores da propriedade do modelo são avaliados.
Teste o aplicativo pesquisando por gênero, título do filme e por ambos:
Nesta seção, você adiciona a funcionalidade de pesquisa ao método de ação Index
que permite pesquisar filmes por gênero ou nome.
Atualize o método Index
encontrado dentro de Controllers/MoviesController.cs
com o seguinte código:
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(await movies.ToListAsync());
}
A primeira linha do método de ação Index
cria uma consulta LINQ para selecionar os filmes:
var movies = from m in _context.Movie
select m;
A consulta é somente definida neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString
contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar o valor da cadeia de caracteres de pesquisa:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
O código s => s.Title.Contains()
acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos de operadores de consulta padrão, como o método Where ou Contains
(usado no código acima). As consultas LINQ não são executadas quando são definidas ou no momento em que são modificadas com uma chamada a um método, como Where
, Contains
ou OrderBy
. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu valor realizado seja, de fato, iterado ou o método ToListAsync
seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Execução da consulta.
O método Contains é executado no banco de dados, não no código C# mostrado acima. A diferenciação de maiúsculas e minúsculas na consulta depende do banco de dados e da ordenação. No SQL Server, Contains é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. No SQLite, com a ordenação padrão, ele diferencia maiúsculas de minúsculas.
Navegue até /Movies/Index
. Acrescente uma cadeia de consulta, como ?searchString=Ghost
, à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do método Index
para que ele tenha um parâmetro chamado id
, o parâmetro id
corresponderá o espaço reservado {id}
opcional com as rotas padrão definidas em Startup.cs
.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Altere o parâmetro para id
e todas as ocorrências de searchString
altere para id
.
O método Index
anterior:
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(await movies.ToListAsync());
}
O método Index
atualizado com o parâmetro id
:
public async Task<IActionResult> Index(string id)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
return View(await movies.ToListAsync());
}
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. Agora você adicionará os elementos da interface do usuário para ajudá-los a filtrar filmes. Se você tiver alterado a assinatura do método Index
para testar como passar o parâmetro ID
associado à rota, altere-o novamente para que ele use um parâmetro chamado searchString
:
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(await movies.ToListAsync());
}
Abra o arquivo Views/Movies/Index.cshtml
e adicione a marcação <form>
destacada abaixo:
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
A marcação <form>
HTML usa o Auxiliar de Marcação de Formulário. Portanto, quando você enviar o formulário, a cadeia de caracteres de filtro será enviada para a ação Index
do controlador de filmes. Salve as alterações e, em seguida, teste o filtro.
Não há nenhuma sobrecarga [HttpPost]
do método Index
que poderia ser esperada. Isso não é necessário, porque o método não está alterando o estado do aplicativo, apenas filtrando os dados.
Você poderá adicionar o método [HttpPost] Index
a seguir.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
O parâmetro notUsed
é usado para criar uma sobrecarga para o método Index
. Falaremos sobre isso mais adiante no tutorial.
Se você adicionar esse método, o chamador de ação fará uma correspondência com o método [HttpPost] Index
e o método [HttpPost] Index
será executado conforme mostrado na imagem abaixo.
No entanto, mesmo se você adicionar esta versão [HttpPost]
do método Index
, 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 que a URL da solicitação GET (localhost:{PORT}/Movies/Index) – não há nenhuma informação de pesquisa na URL. As informações de cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Verifique isso com as ferramentas do Desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas do Desenvolvedor do navegador Chrome:
Veja o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Conforme mencionado no tutorial anterior, o Auxiliar de Marca de Formulário gera um token antifalsificação XSRF. Não modificaremos os dados e, portanto, não precisamos validar o token no método do controlador.
Como o parâmetro de pesquisa está no corpo da solicitação e não na URL, não é possível capturar essas informações de pesquisa para adicionar como Favoritos ou compartilhar com outras pessoas. Corrigimos isso especificando que a solicitação deve ser HTTP GET
encontrada no arquivo Views/Movies/Index.cshtml
.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
Agora, quando você enviar uma pesquisa, a URL conterá a cadeia de consulta da pesquisa. A pesquisa também irá para o método de ação HttpGet Index
, mesmo se você tiver um método HttpPost Index
.
A seguinte marcação mostra a alteração para a marcação form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adicionar pesquisa por gênero
Adicione a seguinte classe MovieGenreViewModel
à pasta Models:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie> Movies { get; set; }
public SelectList Genres { get; set; }
public string MovieGenre { get; set; }
public string SearchString { get; set; }
}
}
O modelo de exibição do gênero de filme conterá:
- Uma lista de filmes.
- Uma
SelectList
que contém a lista de gêneros. Isso permite que o usuário selecione um gênero na lista. MovieGenre
, que contém o gênero selecionado.SearchString
, que contém o texto que os usuários inserem na caixa de texto de pesquisa.
Substitua o método Index
em MoviesController.cs
pelo seguinte código:
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ
que recupera todos os gêneros do banco de dados.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
A SelectList
de gêneros é criada com a projeção dos gêneros distintos (não desejamos que nossa lista de seleção tenha gêneros duplicados).
Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.
Adicionar pesquisa por gênero à exibição Índice
Atualize Index.cshtml
, encontrado em Views/Movies/, da seguinte maneira:
@model MvcMovie.Models.MovieGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<label>Title: <input type="text" asp-for="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies)
{
<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>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine a expressão lambda usada no auxiliar HTML a seguir:
@Html.DisplayNameFor(model => model.Movies[0].Title)
No código anterior, o Auxiliar de HTML DisplayNameFor
inspeciona a propriedade Title
referenciada na expressão lambda para determinar o nome de exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model
, model.Movies
ou model.Movies[0]
é null
ou vazio. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)
), os valores da propriedade do modelo são avaliados.
Teste o aplicativo pesquisando por gênero, título do filme e por ambos: