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 10 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.

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, 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 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 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 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 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, 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 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 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 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 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, 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)
{
    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, 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 não faz distinção entre 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 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 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)
{
    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 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 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 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 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 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)
{
    // 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 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.

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, 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)
{
    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 é 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 não faz distinção entre 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 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 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 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)
{
    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 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 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 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 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 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 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)
{
    // 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 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.

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