Parte 7. Adición de búsqueda a una aplicación de ASP.NET Core MVC
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
Por Rick Anderson
En esta sección agregará capacidad de búsqueda para el método de acción Index
que permite buscar películas por género o nombre.
Actualice el método Index
, que está en Controllers/MoviesController.cs
, con el código siguiente:
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());
}
La siguiente línea del método de acción de Index
crea una consulta LINQ para seleccionar las películas:
var movies = from m in _context.Movie
select m;
En este momento, solo se define la consulta y no se ejecuta en la base de datos.
Si el parámetro searchString
contiene una cadena, la consulta de películas se modifica para filtrar según el valor de la cadena de búsqueda:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
El código s => s.Title!.ToUpper().Contains(searchString.ToUpper())
anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ basadas en métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(que se usa en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican mediante una llamada a un método como Where
, Contains
u OrderBy
. En su lugar, se aplaza la ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repita realmente o se llame al método ToListAsync
. Para más información sobre la ejecución de consultas en diferido, vea Ejecución de la consulta.
Nota:
El método Contains se ejecuta en la base de datos, no en el código de C#. La distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains
se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. SQLite con la intercalación predeterminada es una combinación que distingue mayúsculas y minúsculas y que no distingue mayúsculas y minúsculas, en función de la consulta. Para obtener información sobre cómo hacer que las consultas SQLite no distingan mayúsculas y minúsculas, vea lo siguiente:
Vaya a /Movies/Index
. Anexe una cadena de consulta como ?searchString=Ghost
a la dirección URL. Se muestran las películas filtradas.
Si se cambia la firma del método Index
para que tenga un parámetro con el nombre id
, el parámetro id
coincidirá con el marcador de posición {id}
opcional para el conjunto de rutas predeterminado en Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Cambie el parámetro por id
y todas las apariciones de searchString
por id
.
El 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());
}
El método Index
actualizado con el 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());
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL) en lugar de como un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las películas. Si cambió la firma del método Index
para probar cómo pasar el parámetro ID
enlazado a una ruta, vuelva a cambiarlo para que tome un parámetro denominado 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 el archivo Views/Movies/Index.cshtml
y agregue el marcado <form>
resaltado a continuación:
@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">
La etiqueta HTML <form>
usa el asistente de etiquetas de formulario, por lo que cuando se envía el formulario, la cadena de filtro se registra en la acción Index
del controlador de películas. Guarde los cambios y después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost]
del método Index
como cabría esperar. No es necesario, porque el método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index
siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed
se usa para crear una sobrecarga para el método Index
. Hablaremos sobre esto más adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index
, mientras que el método [HttpPost] Index
se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost]
al método Index
, hay una limitación en cómo se ha implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET (localhost:{PUERTO}/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas de desarrollo del explorador o con la excelente herramienta Fiddler.
En la siguiente imagen se muestran las herramientas para desarrolladores del explorador Chrome con las pestañas Red y Encabezados seleccionadas:
Las pestañas Red y Carga se seleccionan para ver los datos del formulario:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Ten en cuenta, como se mencionó en el tutorial anterior, que el asistente de etiquetas de formulario genera un token XSRF antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede capturar dicha información para marcarla o compartirla con otros usuarios. Corrija esto especificando que la solicitud debe ser HTTP GET
en la etiqueta form
que se encuentra en el archivo 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">
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también será dirigida al método de acción HttpGet Index
, aunque tenga un método HttpPost Index
.
Adición de búsqueda por género
Agregue la clase MovieGenreViewModel
siguiente a la carpeta 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; }
}
El modelo de vista de película y género contendrá:
- Una lista de películas.
SelectList
, que contiene la lista de géneros. Esto permite al usuario seleccionar un género de la lista.MovieGenre
, que contiene el género seleccionado.SearchString
, que contiene el texto que los usuarios escriben en el cuadro de texto de búsqueda.
Reemplace el método Index
en MoviesController.cs
por el código siguiente:
// 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);
}
El código siguiente es una consulta LINQ
que recupera todos los géneros de la base de datos.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
La SelectList
de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista de selección tenga géneros duplicados).
Cuando el usuario busca el elemento, se conserva el valor de búsqueda en el cuadro de búsqueda.
Adición de búsqueda por género a la vista de índice
Actualice Index.cshtml
, que está en Views/Movies/ , siguiendo estos pasos:
@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 la expresión lambda usada en el siguiente asistente de HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
En el código anterior, el asistente de HTML DisplayNameFor
inspecciona la propiedad Title
a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model
, model.Movies
o model.Movies[0]
sean null
o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo, @Html.DisplayFor(modelItem => item.Title)
), se evalúan los valores de propiedad del modelo. El !
después de model.Movies
es un operador que determina valores null, que se usa para declarar que Movies
no es null.
Pruebe la aplicación buscando por género, por título de la película y por ambos:
En esta sección agregará capacidad de búsqueda para el método de acción Index
que permite buscar películas por género o nombre.
Actualice el método Index
, que está en Controllers/MoviesController.cs
, con el código siguiente:
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());
}
La siguiente línea del método de acción de Index
crea una consulta LINQ para seleccionar las películas:
var movies = from m in _context.Movie
select m;
En este momento, solo se define la consulta y no se ejecuta en la base de datos.
Si el parámetro searchString
contiene una cadena, la consulta de películas se modifica para filtrar según el valor de la cadena de búsqueda:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
El código s => s.Title!.ToUpper().Contains(searchString.ToUpper())
anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ basadas en métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(que se usa en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican mediante una llamada a un método como Where
, Contains
u OrderBy
. En su lugar, se aplaza la ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repita realmente o se llame al método ToListAsync
. Para más información sobre la ejecución de consultas en diferido, vea Ejecución de la consulta.
Nota:
El método Contains se ejecuta en la base de datos, no en el código de C#. La distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains
se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. SQLite con la intercalación predeterminada es una combinación que distingue mayúsculas y minúsculas y que no distingue mayúsculas y minúsculas, en función de la consulta. Para obtener información sobre cómo hacer que las consultas SQLite no distingan mayúsculas y minúsculas, vea lo siguiente:
Vaya a /Movies/Index
. Anexe una cadena de consulta como ?searchString=Ghost
a la dirección URL. Se muestran las películas filtradas.
Si se cambia la firma del método Index
para que tenga un parámetro con el nombre id
, el parámetro id
coincidirá con el marcador de posición {id}
opcional para el conjunto de rutas predeterminado en Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Cambie el parámetro por id
y todas las apariciones de searchString
por id
.
El 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());
}
El método Index
actualizado con el 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());
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL) en lugar de como un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las películas. Si cambió la firma del método Index
para probar cómo pasar el parámetro ID
enlazado a una ruta, vuelva a cambiarlo para que tome un parámetro denominado 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 el archivo Views/Movies/Index.cshtml
y agregue el marcado <form>
resaltado a continuación:
@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">
La etiqueta HTML <form>
usa el asistente de etiquetas de formulario, por lo que cuando se envía el formulario, la cadena de filtro se registra en la acción Index
del controlador de películas. Guarde los cambios y después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost]
del método Index
como cabría esperar. No es necesario, porque el método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index
siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed
se usa para crear una sobrecarga para el método Index
. Hablaremos sobre esto más adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index
, mientras que el método [HttpPost] Index
se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost]
al método Index
, hay una limitación en cómo se ha implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET (localhost:{PUERTO}/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Ten en cuenta, como se mencionó en el tutorial anterior, que el asistente de etiquetas de formulario genera un token XSRF antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede capturar dicha información para marcarla o compartirla con otros usuarios. Para corregir este problema, especifique que la solicitud debe ser HTTP GET
, que está en el archivo 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">
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también será dirigida al método de acción HttpGet Index
, aunque tenga un método HttpPost Index
.
El marcado siguiente muestra el cambio en la etiqueta form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adición de búsqueda por género
Agregue la clase MovieGenreViewModel
siguiente a la carpeta 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; }
}
El modelo de vista de película y género contendrá:
- Una lista de películas.
SelectList
, que contiene la lista de géneros. Esto permite al usuario seleccionar un género de la lista.MovieGenre
, que contiene el género seleccionado.SearchString
, que contiene el texto que los usuarios escriben en el cuadro de texto de búsqueda.
Reemplace el método Index
en MoviesController.cs
por el código siguiente:
// 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);
}
El código siguiente es una consulta LINQ
que recupera todos los géneros de la base de datos.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
La SelectList
de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista de selección tenga géneros duplicados).
Cuando el usuario busca el elemento, se conserva el valor de búsqueda en el cuadro de búsqueda.
Adición de búsqueda por género a la vista de índice
Actualice Index.cshtml
, que está en Views/Movies/ , siguiendo estos pasos:
@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 la expresión lambda usada en el siguiente asistente de HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
En el código anterior, el asistente de HTML DisplayNameFor
inspecciona la propiedad Title
a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model
, model.Movies
o model.Movies[0]
sean null
o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo, @Html.DisplayFor(modelItem => item.Title)
), se evalúan los valores de propiedad del modelo. El !
después de model.Movies
es un operador que determina valores null, que se usa para declarar que Movies
no es null.
Pruebe la aplicación buscando por género, por título de la película y por ambos:
En esta sección agregará capacidad de búsqueda para el método de acción Index
que permite buscar películas por género o nombre.
Actualice el método Index
, que está en Controllers/MoviesController.cs
, con el código siguiente:
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());
}
La siguiente línea del método de acción de Index
crea una consulta LINQ para seleccionar las películas:
var movies = from m in _context.Movie
select m;
En este momento, solo se define la consulta y no se ejecuta en la base de datos.
Si el parámetro searchString
contiene una cadena, la consulta de películas se modifica para filtrar según el valor de la cadena de búsqueda:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
El código s => s.Title!.ToUpper().Contains(searchString.ToUpper())
anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ basadas en métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(que se usa en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican mediante una llamada a un método como Where
, Contains
u OrderBy
. En su lugar, se aplaza la ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repita realmente o se llame al método ToListAsync
. Para más información sobre la ejecución de consultas en diferido, vea Ejecución de la consulta.
Nota:
El método Contains se ejecuta en la base de datos, no en el código de C#. La distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains
se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. SQLite con la intercalación predeterminada es una combinación que distingue mayúsculas y minúsculas y que no distingue mayúsculas y minúsculas, en función de la consulta. Para obtener información sobre cómo hacer que las consultas SQLite no distingan mayúsculas y minúsculas, vea lo siguiente:
Vaya a /Movies/Index
. Anexe una cadena de consulta como ?searchString=Ghost
a la dirección URL. Se muestran las películas filtradas.
Si se cambia la firma del método Index
para que tenga un parámetro con el nombre id
, el parámetro id
coincidirá con el marcador de posición {id}
opcional para el conjunto de rutas predeterminado en Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Cambie el parámetro por id
y todas las apariciones de searchString
por id
.
El 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());
}
El método Index
actualizado con el 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());
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL) en lugar de como un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las películas. Si cambió la firma del método Index
para probar cómo pasar el parámetro ID
enlazado a una ruta, vuelva a cambiarlo para que tome un parámetro denominado 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 el archivo Views/Movies/Index.cshtml
y agregue el marcado <form>
resaltado a continuación:
@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">
La etiqueta HTML <form>
usa el asistente de etiquetas de formulario, por lo que cuando se envía el formulario, la cadena de filtro se registra en la acción Index
del controlador de películas. Guarde los cambios y después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost]
del método Index
como cabría esperar. No es necesario, porque el método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index
siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed
se usa para crear una sobrecarga para el método Index
. Hablaremos sobre esto más adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index
, mientras que el método [HttpPost] Index
se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost]
al método Index
, hay una limitación en cómo se ha implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET (localhost:{PUERTO}/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Ten en cuenta, como se mencionó en el tutorial anterior, que el asistente de etiquetas de formulario genera un token XSRF antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede capturar dicha información para marcarla o compartirla con otros usuarios. Para corregir este problema, especifique que la solicitud debe ser HTTP GET
, que está en el archivo 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">
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también será dirigida al método de acción HttpGet Index
, aunque tenga un método HttpPost Index
.
El marcado siguiente muestra el cambio en la etiqueta form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adición de búsqueda por género
Agregue la clase MovieGenreViewModel
siguiente a la carpeta 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; }
}
El modelo de vista de película y género contendrá:
- Una lista de películas.
SelectList
, que contiene la lista de géneros. Esto permite al usuario seleccionar un género de la lista.MovieGenre
, que contiene el género seleccionado.SearchString
, que contiene el texto que los usuarios escriben en el cuadro de texto de búsqueda.
Reemplace el método Index
en MoviesController.cs
por el código siguiente:
// 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);
}
El código siguiente es una consulta LINQ
que recupera todos los géneros de la base de datos.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
La SelectList
de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista de selección tenga géneros duplicados).
Cuando el usuario busca el elemento, se conserva el valor de búsqueda en el cuadro de búsqueda.
Adición de búsqueda por género a la vista de índice
Actualice Index.cshtml
, que está en Views/Movies/ , siguiendo estos pasos:
@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 la expresión lambda usada en el siguiente asistente de HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
En el código anterior, el asistente de HTML DisplayNameFor
inspecciona la propiedad Title
a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model
, model.Movies
o model.Movies[0]
sean null
o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo, @Html.DisplayFor(modelItem => item.Title)
), se evalúan los valores de propiedad del modelo. El !
después de model.Movies
es un operador que determina valores null, que se usa para declarar que Movies
no es null.
Pruebe la aplicación buscando por género, por título de la película y por ambos:
En esta sección agregará capacidad de búsqueda para el método de acción Index
que permite buscar películas por género o nombre.
Actualice el método Index
, que está en Controllers/MoviesController.cs
, con el código siguiente:
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());
}
La primera línea del método de acción Index
crea una consulta LINQ para seleccionar las películas:
var movies = from m in _context.Movie
select m;
En este momento solo se define la consulta, no se ejecuta en la base de datos.
Si el parámetro searchString
contiene una cadena, la consulta de películas se modifica para filtrar según el valor de la cadena de búsqueda:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
El código s => s.Title!.Contains(searchString)
anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ basadas en métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(que se usa en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican mediante una llamada a un método como Where
, Contains
u OrderBy
. En su lugar, se aplaza la ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repita realmente o se llame al método ToListAsync
. Para más información sobre la ejecución de consultas en diferido, vea Ejecución de la consulta.
Nota: El método Contains se ejecuta en la base de datos, no en el código de c# que se muestra arriba. La distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains
se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la intercalación predeterminada, se distingue entre mayúsculas y minúsculas.
Navegue a /Movies/Index
. Anexe una cadena de consulta como ?searchString=Ghost
a la dirección URL. Se muestran las películas filtradas.
Si se cambia la firma del método Index
para que tenga un parámetro con el nombre id
, el parámetro id
coincidirá con el marcador de posición {id}
opcional para el conjunto de rutas predeterminado en Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Cambie el parámetro por id
y todas las apariciones de searchString
por id
.
El 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());
}
El método Index
actualizado con el 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());
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL) en lugar de como un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las películas. Si cambió la firma del método Index
para probar cómo pasar el parámetro ID
enlazado a una ruta, vuelva a cambiarlo para que tome un parámetro denominado 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 el archivo Views/Movies/Index.cshtml
y agregue el marcado <form>
resaltado a continuación:
@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>
La etiqueta HTML <form>
usa el asistente de etiquetas de formulario, por lo que cuando se envía el formulario, la cadena de filtro se registra en la acción Index
del controlador de películas. Guarde los cambios y después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost]
del método Index
como cabría esperar. No es necesario, porque el método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index
siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed
se usa para crear una sobrecarga para el método Index
. Hablaremos sobre esto más adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index
, mientras que el método [HttpPost] Index
se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost]
al método Index
, hay una limitación en cómo se ha implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET (localhost:{PUERTO}/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Ten en cuenta, como se mencionó en el tutorial anterior, que el asistente de etiquetas de formulario genera un token XSRF antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede capturar dicha información para marcarla o compartirla con otros usuarios. Para corregir este problema, especifique que la solicitud debe ser HTTP GET
, que está en el archivo 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">
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también será dirigida al método de acción HttpGet Index
, aunque tenga un método HttpPost Index
.
El marcado siguiente muestra el cambio en la etiqueta form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adición de búsqueda por género
Agregue la clase MovieGenreViewModel
siguiente a la carpeta 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; }
}
}
El modelo de vista de película y género contendrá:
- Una lista de películas.
SelectList
, que contiene la lista de géneros. Esto permite al usuario seleccionar un género de la lista.MovieGenre
, que contiene el género seleccionado.SearchString
, que contiene el texto que los usuarios escriben en el cuadro de texto de búsqueda.
Reemplace el método Index
en MoviesController.cs
por el código siguiente:
// 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);
}
El código siguiente es una consulta LINQ
que recupera todos los géneros de la base de datos.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
La SelectList
de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista de selección tenga géneros duplicados).
Cuando el usuario busca el elemento, se conserva el valor de búsqueda en el cuadro de búsqueda.
Adición de búsqueda por género a la vista de índice
Actualice Index.cshtml
, que está en Views/Movies/ , siguiendo estos pasos:
@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 la expresión lambda usada en el siguiente asistente de HTML:
@Html.DisplayNameFor(model => model.Movies[0].Title)
En el código anterior, el asistente de HTML DisplayNameFor
inspecciona la propiedad Title
a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model
, model.Movies
o model.Movies[0]
sean null
o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo, @Html.DisplayFor(modelItem => item.Title)
), se evalúan los valores de propiedad del modelo.
Pruebe la aplicación buscando por género, por título de la película y por ambos:
En esta sección agregará capacidad de búsqueda para el método de acción Index
que permite buscar películas por género o nombre.
Actualice el método Index
, que está en Controllers/MoviesController.cs
, con el código siguiente:
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());
}
La primera línea del método de acción Index
crea una consulta LINQ para seleccionar las películas:
var movies = from m in _context.Movie
select m;
En este momento solo se define la consulta, no se ejecuta en la base de datos.
Si el parámetro searchString
contiene una cadena, la consulta de películas se modifica para filtrar según el valor de la cadena de búsqueda:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains()
anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ basadas en métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(que se usa en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican mediante una llamada a un método como Where
, Contains
u OrderBy
. En su lugar, se aplaza la ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repita realmente o se llame al método ToListAsync
. Para más información sobre la ejecución de consultas en diferido, vea Ejecución de la consulta.
Nota: El método Contains se ejecuta en la base de datos, no en el código de c# que se muestra arriba. La distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la intercalación predeterminada, se distingue entre mayúsculas y minúsculas.
Navegue a /Movies/Index
. Anexe una cadena de consulta como ?searchString=Ghost
a la dirección URL. Se muestran las películas filtradas.
Si se cambia la firma del método Index
para que tenga un parámetro con el nombre id
, el parámetro id
coincidirá con el marcador de posición {id}
opcional para el conjunto de rutas predeterminado en Startup.cs
.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Cambie el parámetro por id
y todas las apariciones de searchString
se modificarán por id
.
El 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());
}
El método Index
actualizado con el 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());
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL) en lugar de como un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las películas. Si cambió la firma del método Index
para probar cómo pasar el parámetro ID
enlazado a una ruta, vuelva a cambiarlo para que tome un parámetro denominado 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 el archivo Views/Movies/Index.cshtml
y agregue el marcado <form>
resaltado a continuación:
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>
La etiqueta HTML <form>
usa el asistente de etiquetas de formulario, por lo que cuando se envía el formulario, la cadena de filtro se registra en la acción Index
del controlador de películas. Guarde los cambios y después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost]
del método Index
como cabría esperar. No es necesario, porque el método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index
siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed
se usa para crear una sobrecarga para el método Index
. Hablaremos sobre esto más adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index
, mientras que el método [HttpPost] Index
se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost]
al método Index
, hay una limitación en cómo se ha implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET (localhost:{PUERTO}/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Ten en cuenta, como se mencionó en el tutorial anterior, que el asistente de etiquetas de formulario genera un token XSRF antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede capturar dicha información para marcarla o compartirla con otros usuarios. Para corregir este problema, especifique que la solicitud debe ser HTTP GET
, que está en el archivo 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)
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también será dirigida al método de acción HttpGet Index
, aunque tenga un método HttpPost Index
.
El marcado siguiente muestra el cambio en la etiqueta form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Adición de búsqueda por género
Agregue la clase MovieGenreViewModel
siguiente a la carpeta 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; }
}
}
El modelo de vista de película y género contendrá:
- Una lista de películas.
SelectList
, que contiene la lista de géneros. Esto permite al usuario seleccionar un género de la lista.MovieGenre
, que contiene el género seleccionado.SearchString
, que contiene el texto que los usuarios escriben en el cuadro de texto de búsqueda.
Reemplace el método Index
en MoviesController.cs
por el código siguiente:
// 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);
}
El código siguiente es una consulta LINQ
que recupera todos los géneros de la base de datos.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
La SelectList
de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista de selección tenga géneros duplicados).
Cuando el usuario busca el elemento, se conserva el valor de búsqueda en el cuadro de búsqueda.
Adición de búsqueda por género a la vista de índice
Actualice Index.cshtml
, que está en Views/Movies/ , siguiendo estos pasos:
@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 la expresión lambda usada en el siguiente asistente de HTML:
@Html.DisplayNameFor(model => model.Movies[0].Title)
En el código anterior, el asistente de HTML DisplayNameFor
inspecciona la propiedad Title
a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model
, model.Movies
o model.Movies[0]
sean null
o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo, @Html.DisplayFor(modelItem => item.Title)
), se evalúan los valores de propiedad del modelo.
Pruebe la aplicación buscando por género, por título de la película y por ambos: