Partilhar via


Parte 7, adicionar pesquisa a um aplicativo MVC ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Por Rick Anderson

Nesta seção, você adiciona o recurso 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 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 seguinte linha no 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 está definida apenas neste momento, não foi executada no banco de dados.

Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta movies será modificada para filtrar o valor da cadeia 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. Os lambdas são usados em consultas de LINQ baseadas em método como argumentos para métodos de operador 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 quando são modificadas chamando um método como Where, Containsou OrderBy. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é adiada até que seu valor realizado seja realmente iterado ou o método ToListAsync seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Query Execution.

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 mapeia para SQL LIKE, que não faz distinção entre maiúsculas e minúsculas. SQLite com o agrupamento padrão é uma mistura de diferenciação sensível e insensível a maiúsculas e minúsculas IN, dependendo da consulta. Para obter informações sobre como realizar consultas SQLite sem diferenciar maiúsculas de minúsculas, consulte o seguinte:

Navegue até /Movies/Index. Anexe uma cadeia de caracteres de consulta, como ?searchString=Ghost, à URL. Os filmes filtrados são exibidos.

Visualização de índice

Se você alterar a assinatura do método Index para ter um parâmetro chamado id, o parâmetro id corresponderá ao espaço reservado {id} opcional para 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 altere 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 da pesquisa como dados de rota (um segmento de URL) em vez de como um valor de cadeia de caracteres de consulta.

Visualização de índice com a palavra fantasma adicionada à URL e uma lista de dois filmes retornados: Ghostbusters e Ghostbusters 2

No entanto, não pode esperar que os utilizadores modifiquem o URL sempre que quiserem procurar um filme. Então, agora você adicionará elementos da interface do usuário para ajudá-los a filtrar filmes. Se você alterou a assinatura do método Index para testar como passar o parâmetro ID vinculado à rota, altere-o novamente para que ele pegue 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 tag HTML <form> usa o Form Tag Helper, portanto, quando você envia o formulário, a cadeia de caracteres do filtro é postada na ação Index do controlador de filmes. Guarde as alterações e, em seguida, teste o filtro.

vista de índice com a palavra fantasma digitada no campo de filtro do título

Não há sobrecarga de [HttpPost] do método Index como esperado. Você não precisa disso, porque o método não está alterando o estado do aplicativo, apenas filtrando dados.

Você pode adicionar o seguinte método [HttpPost] Index.

[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 invocador de ação corresponderia ao método [HttpPost] Index e o método [HttpPost] Index seria executado como mostrado na imagem abaixo.

Janela do navegador com a resposta da aplicação do índice HttpPost: filtro em fantasma

No entanto, mesmo se você adicionar essa versão [HttpPost] do método Index, há uma limitação em como tudo isso foi implementado. Imagine que você deseja marcar uma pesquisa específica ou enviar um link para amigos que eles podem 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á informações de pesquisa na URL. As informações da cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário . Você pode verificar isso com as ferramentas de desenvolvedor do navegador ou a excelente ferramenta Fiddler.

A imagem seguinte mostra as ferramentas de programador do navegador Chrome com os separadores Network e Cabeçalhos selecionados:

separadores Rede e Cabeçalhos das Ferramentas de Desenvolvimento do navegador Chrome mostrando um corpo de solicitação com um valor de searchString fantasma

As guias Network e Payload são selecionadas para exibir dados de formulário:

separadores de Rede e Payload das Ferramentas de Programador do navegador Chrome que mostram as informações do formulário

Você pode ver o parâmetro de pesquisa e token de XSRF no corpo da solicitação. Observe que, como mencionado no tutorial anterior, o Form Tag Helper gera um token antifalsificação XSRF. Não estamos modificando dados, portanto, não precisamos validar o token no método 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 marcar ou compartilhar com outras pessoas. Corrija 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ê envia uma pesquisa, o URL contém a cadeia de caracteres de consulta de pesquisa. A pesquisa também irá para o método de ação HttpGet Index, mesmo que você tenha um método HttpPost Index.

janela do navegador mostrando o searchString=ghost na URL e os filmes retornados, Ghostbusters e Ghostbusters 2, contêm a palavra ghost

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 visualização de gênero de filme conterá:

  • Uma lista de filmes.
  • Um SelectList contendo a lista de géneros. Isso permite que o usuário selecione um gênero da lista.
  • MovieGenre, que contém o gênero selecionado.
  • SearchString, que contém o texto que os usuários digitam na caixa de texto de pesquisa.

Substitua o método Index no MoviesController.cs com o 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 projetando os gêneros distintos (não queremos que nossa lista de seleção tenha gêneros duplicados).

Quando o usuário procura o item, o valor da pesquisa é retido na caixa de pesquisa.

Adicionar pesquisa por género na visualização Índice

Atualização Index.cshtml encontrada em Views/Movies/ da seguinte forma:

@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 seguinte HTML Helper:

@Html.DisplayNameFor(model => model.Movies![0].Title)

No código anterior, o DisplayNameFor HTML Helper inspeciona a propriedade Title referenciada na expressão lambda para determinar o nome para exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model, model.Moviesou model.Movies[0] estão null ou vazias. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)), os valores de propriedade do modelo são avaliados. O ! após model.Movies é o operador de perdão nulo , que é usado para declarar que Movies não é nulo.

Teste o aplicativo pesquisando por gênero, por título do filme e por ambos:

Janela do navegador mostrando resultados de https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

Nesta seção, adiciona-se a capacidade de pesquisa ao método de ação Index que permite pesquisar filmes por gênero ou por nome .

Atualize o método Index encontrado dentro 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 seguinte linha no 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 está apenas definida neste momento, não foi ainda executada no banco de dados.

Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta movies será modificada para filtrar o valor da cadeia 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. Os lambdas são usados em consultas LINQ baseadas em método como argumentos para métodos padrão do operador de consulta, como os métodos Where ou Contains (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, Containsou OrderBy. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é adiada até que seu valor realizado seja realmente iterado ou o método ToListAsync seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Query Execution.

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 do agrupamento. No SQL Server, Contains mapeia para SQL LIKE, que não é sensível a maiúsculas. SQLite com o agrupamento padrão é uma mistura de sensível a maiúsculas e minúsculas e parcialmente sensível a maiúsculas IN, dependendo da consulta. Para obter informações sobre como efetuar consultas SQLite sem distinguir maiúsculas de minúsculas, consulte o seguinte:

Navegue até /Movies/Index. Anexe uma cadeia de caracteres de consulta, como ?searchString=Ghost, à URL. Os filmes filtrados são exibidos.

Visualização de índice

Se você alterar a assinatura do método Index para ter um parâmetro chamado id, o parâmetro id corresponderá ao espaço reservado {id} opcional para 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 altere 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 atualizado Index 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 da pesquisa como dados de rota (um segmento de URL) em vez de como um valor de cadeia de caracteres de consulta.

Visualização de índice com a palavra ghost adicionada à URL e uma lista retornada de dois filmes, Ghostbusters e Ghostbusters 2

No entanto, não pode esperar que os utilizadores modifiquem o URL sempre que quiserem procurar um filme. Então, agora você adicionará elementos da interface do usuário para ajudá-los a filtrar filmes. Se você alterou a assinatura do método Index para testar como passar o parâmetro ID vinculado à rota, altere-o novamente para que ele pegue 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 tag HTML <form> usa o Form Tag Helper, portanto, quando você envia o formulário, a cadeia de caracteres do filtro é postada na ação Index do controlador de filmes. Guarde as alterações e, em seguida, teste o filtro.

Vista do Índice com a palavra fantasma digitada no filtro de título

Não há [HttpPost] sobrecarga do método Index como seria de esperar. Você não precisa disso, porque o método não está alterando o estado do aplicativo, apenas filtrando dados.

Você pode adicionar o seguinte método [HttpPost] Index.

[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 invocador de ação corresponderia ao método [HttpPost] Index e o método [HttpPost] Index seria executado como mostrado na imagem abaixo.

janela do navegador com resposta da aplicação de HttpPost Index: filtro em fantasma

No entanto, mesmo se você adicionar essa versão [HttpPost] do método Index, há uma limitação em como tudo isso foi implementado. Imagine que você deseja marcar uma pesquisa específica ou enviar um link para amigos que eles podem 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á informações de pesquisa na URL. As informações da string de pesquisa são enviadas ao servidor como um campo de formulário . Você pode verificar isso com as ferramentas de desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas de desenvolvedor do navegador Chrome:

separador Rede das Ferramentas de Desenvolvimento do Microsoft Edge mostrando um corpo de solicitação com um valor de searchString

Você pode ver o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Observe que, como mencionado no tutorial anterior, o Form Tag Helper gera um token antifalsificação XSRF. Não estamos modificando dados, portanto, não precisamos validar o token no método 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 marcar ou compartilhar com outras pessoas. Corrija 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ê envia uma pesquisa, o URL contém a cadeia de caracteres de consulta de pesquisa. A pesquisa também irá para o método de ação HttpGet Index, mesmo que você tenha um método HttpPost Index.

janela do navegador mostrando o searchString=ghost na URL e os filmes retornados, Ghostbusters e Ghostbusters 2, contêm a palavra ghost

A marcação a seguir mostra a alteração na tag 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 visualização para géneros de cinema conterá:

  • Uma lista de filmes.
  • Um SelectList contendo a lista de géneros. Isso permite que o usuário selecione um gênero da lista.
  • MovieGenre, que contém o gênero selecionado.
  • SearchString, que contém o texto que os usuários digitam na caixa de texto de pesquisa.

Substitua o método Index no MoviesController.cs com o 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 projetando os gêneros distintos (não queremos que nossa lista de seleção tenha gêneros duplicados).

Quando o usuário procura o item, o valor da pesquisa é retido na caixa de pesquisa.

Adicionar pesquisa por género à vista de Índice

Atualização Index.cshtml encontrada 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 seguinte HTML Helper:

@Html.DisplayNameFor(model => model.Movies![0].Title)

No código anterior, o DisplayNameFor HTML Helper inspeciona a propriedade Title referenciada na expressão lambda para determinar o nome para exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model, model.Moviesou model.Movies[0] estão null ou vazias. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)), os valores de propriedade do modelo são avaliados. O ! após model.Movies é o operador de perdão nulo , que é usado para declarar que Movies não é nulo.

Teste o aplicativo pesquisando por gênero, por título do filme e por ambos:

Janela do navegador mostrando resultados de https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

Nesta seção, adiciona-se a capacidade 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 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 seguinte linha no 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 está definida apenas neste momento, não foi executada no banco de dados.

Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta movies será modificada para filtrar o valor da cadeia 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. Os lambdas são usados em consultas de LINQ baseadas em método como argumentos para métodos de operador 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 quando são modificadas chamando um método como Where, Containsou OrderBy. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é adiada até que seu valor realizado seja realmente iterado ou o método ToListAsync seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Query Execution.

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 do agrupamento. No SQL Server, Contains mapeia para SQL LIKE, que é insensível a maiúsculas e minúsculas. SQLite com a ordenação padrão é uma mistura de sensibilidade a maiúsculas e minúsculas, dependendo da consulta, onde em alguns casos é sensível a maiúsculas (IN). Para obter informações sobre como fazer consultas SQLite (sem diferenciar maiúsculas de minúsculas), consulte o seguinte:

Navegue até /Movies/Index. Anexe uma cadeia de caracteres de consulta, como ?searchString=Ghost, à URL. Os filmes filtrados são exibidos.

Visualização de índice

Se você alterar a assinatura do método Index para ter um parâmetro chamado id, o parâmetro id corresponderá ao espaço reservado {id} opcional para 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 altere 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 da pesquisa como dados de rota (um segmento de URL) em vez de como um valor de cadeia de caracteres de consulta.

Visualização de índice com a palavra fantasma adicionada à URL e uma lista de dois filmes retornados, Ghostbusters e Ghostbusters 2

No entanto, não pode esperar que os utilizadores modifiquem o URL sempre que quiserem procurar um filme. Então, agora você adicionará elementos da interface do usuário para ajudá-los a filtrar filmes. Se você alterou a assinatura do método Index para testar como passar o parâmetro ID vinculado à rota, altere-o novamente para que ele pegue 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 tag HTML <form> usa o Form Tag Helper, portanto, quando submeteres o formulário, a cadeia de caracteres do filtro é enviada para a ação Index do controlador de filmes. Guarde as alterações e, em seguida, teste o filtro.

visão de índice com a palavra

Como seria de esperar, não há sobrecarga [HttpPost] do método Index. Você não precisa disso, porque o método não está alterando o estado do aplicativo, apenas filtrando dados.

Você pode adicionar o seguinte método [HttpPost] Index.

[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 invocador de ação corresponderia ao método [HttpPost] Index e o método [HttpPost] Index seria executado como mostrado na imagem abaixo.

Janela do navegador com resposta do aplicativo para o HttpPost Index: filtro fantasma em

No entanto, mesmo se você adicionar essa versão [HttpPost] do método Index, há uma limitação em como tudo isso foi implementado. Imagine que você deseja marcar uma pesquisa específica ou enviar um link para amigos que eles podem 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á informações de pesquisa na URL. As informações da cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Você pode verificar isso com as ferramentas de desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas de desenvolvedor do navegador Chrome:

guia Rede das Ferramentas de Desenvolvedor do Microsoft Edge mostrando um corpo de solicitação com um valor de fantasma searchString

Você pode ver o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Observe que, como mencionado no tutorial anterior, o Form Tag Helper gera um token antifalsificação XSRF. Não estamos modificando dados, portanto, não precisamos validar o token no método 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 marcar ou compartilhar com outras pessoas. Corrija 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ê envia uma pesquisa, o URL contém a cadeia de caracteres de consulta de pesquisa. A pesquisa será direcionada para o método de ação HttpGet Index, mesmo que você tenha um método HttpPost Index.

janela do navegador mostrando o searchString=ghost na URL e os filmes retornados, Ghostbusters e Ghostbusters 2, contêm a palavra ghost

A marcação a seguir mostra a alteração na tag 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 perspetiva dos géneros de filmes conterá:

  • Uma lista de filmes.
  • Um SelectList contendo a lista de géneros. Isso permite que o usuário selecione um gênero da lista.
  • MovieGenre, que contém o gênero selecionado.
  • SearchString, que contém o texto que os usuários digitam na caixa de texto de pesquisa.

Substitua o método Index no MoviesController.cs com o 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;

O SelectList dos géneros é criado ao projetar os géneros distintos (não queremos que a nossa lista de seleção tenha géneros duplicados).

Quando o usuário procura o item, o valor da pesquisa é retido na caixa de pesquisa.

Adicionar pesquisa por género na vista de Índice

A atualização Index.cshtml encontrada em Views/Movies/ da seguinte forma:

@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 seguinte HTML Helper:

@Html.DisplayNameFor(model => model.Movies![0].Title)

No código anterior, o DisplayNameFor HTML Helper inspeciona a propriedade Title referenciada na expressão lambda para determinar o nome para exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model, model.Moviesou model.Movies[0] estão null ou vazias. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)), os valores de propriedade do modelo são avaliados. O ! após model.Movies é o operador de perdão nulo , que é usado para declarar que Movies não é nulo.

Teste o aplicativo pesquisando por gênero, por título do filme e por ambos:

Janela do navegador mostrando resultados de https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

Nesta seção, adiciona-se o recurso de pesquisa ao método de ação Index que permite pesquisar filmes por gênero ou pelo nome .

Atualize o método Index encontrado dentro 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 está definida apenas neste momento, ainda não foi executada no banco de dados.

Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta movies será modificada para filtrar o valor da cadeia 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. Os lambdas são usados em consultas de LINQ baseadas em método como argumentos para métodos de operador 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 quando são modificadas chamando um método como Where, Containsou OrderBy. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é adiada até que seu valor realizado seja realmente iterado ou o método ToListAsync seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Query Execution.

Nota: 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 mapeia para SQL LIKE, que é insensível a maiúsculas e minúsculas. No SQLite, com a ordenação padrão, diferencia maiúsculas de minúsculas.

Navegue até /Movies/Index. Anexe uma cadeia de caracteres de consulta, como ?searchString=Ghost, à URL. Os filmes filtrados são exibidos.

Visualização de índice

Se você alterar a assinatura do método Index para ter um parâmetro chamado id, o parâmetro id corresponderá ao espaço reservado {id} opcional para 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 altere 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 id parâmetro:

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 da pesquisa como dados de rota (um segmento de URL) em vez de como um valor de cadeia de caracteres de consulta.

Visualização do índice com a palavra fantasma adicionada à Url e uma lista com dois filmes, Ghostbusters e Ghostbusters 2

No entanto, não pode esperar que os utilizadores modifiquem o URL sempre que quiserem procurar um filme. Então, agora você adicionará elementos da interface do usuário para ajudá-los a filtrar filmes. Se você alterou a assinatura do método Index para testar como passar o parâmetro ID vinculado à rota, altere-o novamente para que ele pegue 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 tag HTML <form> usa o Form Tag Helper, portanto, quando o utilizador envia o formulário, a cadeia de caracteres do filtro é enviada para a ação Index do controlador de filmes. Guarde as alterações e, em seguida, teste o filtro.

vista de índice com a palavra fantasma digitada na caixa de texto de filtro de título

Não há sobrecarga de [HttpPost] no método Index, como seria de esperar. Você não precisa disso, porque o método não está alterando o estado do aplicativo, apenas filtrando dados.

Você pode adicionar o seguinte método [HttpPost] Index.

[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 invocador de ação corresponderia ao método [HttpPost] Index e o método [HttpPost] Index seria executado como mostrado na imagem abaixo.

janela do navegador com a resposta da aplicação de HttpPost Index: filtro no fantasma

No entanto, mesmo se você adicionar essa versão [HttpPost] do método Index, há uma limitação em como tudo isso foi implementado. Imagine que você deseja marcar uma pesquisa específica ou enviar um link para amigos que eles podem 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á informações de pesquisa na URL. As informações da cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Você pode verificar isso com as ferramentas de desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas de desenvolvedor do navegador Chrome:

guia Rede das Ferramentas de Desenvolvedor do Microsoft Edge mostrando um corpo de solicitação com um valor searchString de fantasma

Você pode ver o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Observe que, como mencionado no tutorial anterior, o Form Tag Helper gera um token antifalsificação XSRF. Não estamos modificando dados, portanto, não precisamos validar o token no método 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 marcar ou compartilhar com outras pessoas. Corrija 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ê envia uma pesquisa, o URL contém a cadeia de caracteres de consulta de pesquisa. Mesmo que você tenha um método HttpPost Index, a pesquisa também será direcionada para o método de ação HttpGet Index.

janela do navegador mostrando o searchString=ghost na URL e os filmes retornados, Ghostbusters e Ghostbusters 2, contêm a palavra ghost

A marcação a seguir mostra a alteração na tag 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 visualização de géneros de filmes irá conter:

  • Uma lista de filmes.
  • Um SelectList contendo a lista de géneros. Isso permite que o usuário selecione um gênero da lista.
  • MovieGenre, que contém o gênero selecionado.
  • SearchString, que contém o texto que os usuários digitam na caixa de texto de pesquisa.

Substitua o método Index no MoviesController.cs com o 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 projetando os gêneros distintos (não queremos que nossa lista de seleção tenha gêneros duplicados).

Quando o usuário procura o item, o valor da pesquisa é retido na caixa de pesquisa.

Adicionar pesquisa por género à visualização Índice

Update 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 seguinte HTML Helper:

@Html.DisplayNameFor(model => model.Movies[0].Title)

No código anterior, o DisplayNameFor HTML Helper inspeciona a propriedade Title referenciada na expressão lambda para determinar o nome para exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model, model.Moviesou model.Movies[0] estão null ou vazias. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)), os valores de propriedade do modelo são avaliados.

Teste o aplicativo pesquisando por gênero, por título do filme e por ambos:

Janela do navegador mostrando resultados de https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

Nesta seção, adicionas a capacidade 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 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 é apenas definida neste momento, ela não foi executada no banco de dados.

Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta movies será modificada para filtrar o valor da cadeia de pesquisa:

if (!String.IsNullOrEmpty(searchString))
{
    movies = movies.Where(s => s.Title.Contains(searchString));
}

O código s => s.Title.Contains() acima é um Lambda Expression. Os lambdas são usados em consultas de LINQ baseadas em método como argumentos para métodos de operador 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 quando são modificadas chamando um método como Where, Containsou OrderBy. Em vez disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é adiada até que seu valor realizado seja realmente iterado ou o método ToListAsync seja chamado. Para obter mais informações sobre a execução de consulta adiada, consulte Query Execution.

Nota: 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 do agrupamento. No SQL Server, Contains mapeia para SQL LIKE, que não diferencia maiúsculas de minúsculas. Em SQLite, com o ordenamento padrão, diferencia maiúsculas de minúsculas.

Navegue até /Movies/Index. Anexe uma cadeia de caracteres de consulta, como ?searchString=Ghost, à URL. Os filmes filtrados são exibidos.

Visualização de índice

Se você alterar a assinatura do método Index para ter um parâmetro chamado id, o parâmetro id corresponderá ao espaço reservado {id} opcional para 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 mudam 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 atualizado Index 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 da pesquisa como dados de rota (um segmento de URL) em vez de como um valor de cadeia de caracteres de consulta.

Visualização de índice com a palavra fantasma adicionada à URL e uma lista de dois filmes retornada, Ghostbusters e Ghostbusters 2

No entanto, não pode esperar que os utilizadores modifiquem o URL sempre que quiserem procurar um filme. Então, agora você adicionará elementos da interface do usuário para ajudá-los a filtrar filmes. Se você alterou a assinatura do método Index para testar como passar o parâmetro ID vinculado à rota, altere-o novamente para que ele pegue 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 tag HTML <form> usa o Form Tag Helper, portanto, quando você envia o formulário, a cadeia de caracteres do filtro é postada na ação Index do controlador de filmes. Guarde as alterações e, em seguida, teste o filtro.

vista de índice com a palavra fantasma digitada na caixa de texto do filtro de título

Não há [HttpPost] sobrecarga do método Index como seria de esperar. Você não precisa disso, porque o método não está alterando o estado do aplicativo, apenas filtrando dados.

Você pode adicionar o seguinte método [HttpPost] Index.

[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 invocador de ação corresponderia ao método [HttpPost] Index e o método [HttpPost] Index seria executado como mostrado na imagem abaixo.

janela do navegador com resposta do aplicativo de HttpPost Index: filtro em fantasma

No entanto, mesmo se você adicionar essa versão [HttpPost] do método Index, há uma limitação em como tudo isso foi implementado. Imagine que você deseja marcar uma pesquisa específica ou enviar um link para amigos que eles podem 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á informações de pesquisa na URL. As informações da cadeia de caracteres de pesquisa são enviadas ao servidor como um valor de campo de formulário. Você pode verificar isso com as ferramentas de desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas de desenvolvedor do navegador Chrome:

separador de Rede das Ferramentas de Programação do Microsoft Edge mostrando um corpo de pedido com um valor searchString de fantasma

Você pode ver o parâmetro de pesquisa e o token XSRF no corpo do pedido. Observe que, como mencionado no tutorial anterior, o Form Tag Helper gera um token antifalsificação XSRF. Não estamos modificando dados, portanto, não precisamos validar o token no método 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 marcar ou compartilhar com outras pessoas. Corrija 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ê envia uma pesquisa, o URL contém a cadeia de caracteres de consulta de pesquisa. A pesquisa também direcionará para o método de ação HttpGet Index, mesmo que tenha um método HttpPost Index.

janela do navegador mostrando o searchString=ghost na URL e os filmes retornados, Ghostbusters e Ghostbusters 2, contêm a palavra ghost

A marcação a seguir mostra a alteração na tag 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 visualização do género cinematográfico conterá:

  • Uma lista de filmes.
  • Um SelectList contendo a lista de géneros. Isso permite que o usuário selecione um gênero da lista.
  • MovieGenre, que contém o gênero selecionado.
  • SearchString, que contém o texto que os usuários digitam na caixa de texto de pesquisa.

Substitua o método Index no MoviesController.cs com o 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 projetando os gêneros distintos (não queremos que nossa lista de seleção tenha gêneros duplicados).

Quando o usuário procura o item, o valor da pesquisa é retido na caixa de pesquisa.

Adicionar pesquisa por género à vista de Índice

Atualização Index.cshtml encontrada em Views/Movies/ da seguinte forma:

@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 seguinte HTML Helper:

@Html.DisplayNameFor(model => model.Movies[0].Title)

No código anterior, o DisplayNameFor HTML Helper inspeciona a propriedade Title referenciada na expressão lambda para determinar o nome para exibição. Como a expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de acesso quando model, model.Moviesou model.Movies[0] estão null ou vazias. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title)), os valores de propriedade do modelo são avaliados.

Teste o aplicativo pesquisando por gênero, por título do filme e por ambos:

Janela do navegador mostrando resultados de https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2