Parte 7, aggiungere la ricerca a un'app MVC core ASP.NET
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
In questa sezione si aggiunge la funzionalità di ricerca al metodo di azione Index
che consente di cercare film in base al genere o al nome.
Aggiornare il Index
metodo trovato all'interno Controllers/MoviesController.cs
con il codice seguente:
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 riga seguente nel Index
metodo di azione crea una query LINQ per selezionare i film:
var movies = from m in _context.Movie
select m;
La query viene solo definita in questo punto, ma non viene eseguita nel database.
Se il parametro searchString
contiene una stringa, la query dei film viene modificata per filtrare in base al valore della stringa di ricerca:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Il codice s => s.Title!.ToUpper().Contains(searchString.ToUpper())
precedente è un'espressione lambda. Le espressioni lambda vengono usate nelle query LINQ basate su metodo come argomenti per metodi dell'operatore di query standard, ad esempio il Where metodo o Contains
(usato nel codice precedente). Le query LINQ non vengono eseguite al momento della definizione o della modifica chiamando un metodo quale Where
, Contains
o OrderBy
. L'esecuzione della query viene invece posticipata. Per esecuzione posticipata si intende che la valutazione di un'espressione viene ritardata finché non viene eseguita l'iterazione del valore o non viene chiamato il metodo ToListAsync
. Per altre informazioni sull'esecuzione posticipata di query, vedere Esecuzione di query.
Nota
Il Contains metodo viene eseguito nel database, non nel codice C#. La distinzione tra maiuscole/minuscole nella query dipende dal database e dalle regole di confronto. In SQL Server Contains
esegue il mapping a SQL LIKE che fa distinzione tra maiuscole e minuscole. SQLite con le regole di confronto predefinite è una combinazione di distinzione tra maiuscole e minuscole e di distinzione tra maiuscole e minuscole, a seconda della query. Per informazioni sull'esecuzione di query SQLite senza distinzione tra maiuscole e minuscole, vedere quanto segue:
Accedere a /Movies/Index
. Accodare una stringa di query, ad esempio ?searchString=Ghost
, all'URL. Vengono visualizzati i film filtrati.
Se si modifica la firma del Index
metodo in modo che abbia un parametro denominato id
, il id
parametro corrisponderà al segnaposto facoltativo per le route predefinite {id}
impostate in Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Modificare il parametro in id
e modificare tutte le occorrenze di searchString
in id
.
Il metodo Index
precedente:
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());
}
Il metodo Index
aggiornato con il parametro 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());
}
È ora possibile passare il titolo della ricerca come dati di route (un segmento di URL), anziché come valore della stringa di query.
Tuttavia, non è possibile supporre che gli utenti modifichino l'URL ogni volta che desiderano cercare un film. A questo punto si aggiungeranno elementi dell'interfaccia utente per filtrare i film. Se è stata modificata la firma del metodo Index
per testare come passare il parametro ID
associato alla route, impostarlo di nuovo in modo che accetti un parametro denominato 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());
}
Aprire il Views/Movies/Index.cshtml
file e aggiungere il <form>
markup evidenziato di seguito:
@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">
Il tag <form>
HTML usa l'helper tag del modulo e quindi quando si invia il modulo, la stringa di filtro viene registrata nell'azione Index
del controller di film. Salvare le modifiche e quindi testare il filtro.
Non è presente alcun overload del metodo [HttpPost]
Index
come previsto. Non è necessario perché il metodo non modifica lo stato dell'app, ma filtra solo i dati.
È possibile aggiungere il metodo [HttpPost] Index
seguente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Il parametro notUsed
viene usato per creare un overload per il metodo Index
. Questo aspetto verrà trattato in una fase successiva dell'esercitazione.
Se si aggiunge questo metodo, l'invoker di azione trova la corrispondenza con il metodo [HttpPost] Index
e il metodo [HttpPost] Index
viene eseguito come illustrato nell'immagine seguente.
Tuttavia, anche se si aggiunge questa versione [HttpPost]
del metodo Index
, esiste una limitazione sul modo sulla relativa implementazione. Si supponga che si desideri usare come segnalibro una ricerca specifica o inviare un collegamento agli amici su cui possono fare clic per visualizzare lo stesso elenco filtrato di film. Si noti che l'URL per la richiesta HTTP POST è lo stesso URL per la richiesta GET (localhost:{PORTA}/Movies/Index). Le informazioni sulla ricerca non sono disponibili nell'URL. Le informazioni sulla stringa di ricerca vengono inviate al server come un valore del campo modulo. È possibile eseguire una verifica con gli strumenti di sviluppo del browser o l'eccellente strumento Fiddler.
L'immagine seguente mostra il browser Chrome Strumenti di sviluppo con le schede Network e Headers selezionate:
Le schede Rete e Payload sono selezionate per visualizzare i dati del modulo:
È possibile esaminare il parametro di ricerca e il token XSRF nel corpo della richiesta. Come indicato nell'esercitazione precedente, l'helper tag form genera un token antiforgery XSRF. Poiché non si stanno modificando i dati, non è necessario convalidare il token nel metodo del controller.
Poiché il parametro di ricerca si trova nel corpo della richiesta e non nell'URL, non è possibile acquisire queste informazioni sulla ricerca da usare come segnalibro o condividerle con altri utenti. Risolvere questo problema specificando che la richiesta deve trovarsi HTTP GET
nel form
tag trovato nel Views/Movies/Index.cshtml
file.
@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">
A questo punto quando si invia una ricerca, l'URL contiene la stringa di query della ricerca. La ricerca passerà anche al metodo di azione HttpGet Index
, anche se si dispone di un metodo HttpPost Index
.
Aggiungere la funzionalità di ricerca in base al genere
Aggiungere la classe MovieGenreViewModel
seguente alla cartella 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; }
}
Il modello di vista movie-genre conterrà:
- Un elenco di film.
SelectList
contiene l'elenco dei generi. Consente all'utente di selezionare un genere dall'elenco.MovieGenre
che contiene il genere selezionato.SearchString
, che contiene il testo immesso dagli utenti nella casella di testo di ricerca.
Sostituire il metodo Index
in MoviesController.cs
con il codice seguente:
// 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);
}
Il codice seguente è una query LINQ
che recupera tutti i generi dal database.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
L'elenco SelectList
di generi viene creato presentando generi distinti per evitare che l'elenco di selezione includa generi duplicati.
Quando l'utente cerca l'elemento, viene mantenuto il valore di ricerca nella casella di ricerca.
Aggiungere la funzionalità di ricerca in base al genere alla vista Index
Aggiornare Index.cshtml
in Views/Movies/ come segue:
@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>
Esaminare l'espressione lambda usata nell'helper HTML seguente:
@Html.DisplayNameFor(model => model.Movies![0].Title)
Nel codice precedente, l'helper HTML DisplayNameFor
controlla la proprietà Title
a cui si fa riferimento nell'espressione lambda per determinare il nome visualizzato. Poiché l'espressione lambda viene controllata e anziché essere valutata, non viene generata una violazione di accesso quando model
, model.Movies
o model.Movies[0]
sono null
o vuoti. Quando invece l'espressione lambda viene valutata, ad esempio @Html.DisplayFor(modelItem => item.Title)
, vengono valutati i valori delle proprietà del modello. Il !
valore after model.Movies
è l'operatore null-forgiving, che viene usato per dichiarare che Movies
non è Null.
Eseguire il test dell'app effettuando una ricerca per genere, titolo del film ed entrambi:
In questa sezione si aggiunge la funzionalità di ricerca al metodo di azione Index
che consente di cercare film in base al genere o al nome.
Aggiornare il Index
metodo trovato all'interno Controllers/MoviesController.cs
con il codice seguente:
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 riga seguente nel Index
metodo di azione crea una query LINQ per selezionare i film:
var movies = from m in _context.Movie
select m;
La query viene solo definita in questo punto, ma non viene eseguita nel database.
Se il parametro searchString
contiene una stringa, la query dei film viene modificata per filtrare in base al valore della stringa di ricerca:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Il codice s => s.Title!.ToUpper().Contains(searchString.ToUpper())
precedente è un'espressione lambda. Le espressioni lambda vengono usate nelle query LINQ basate su metodo come argomenti per metodi dell'operatore di query standard, ad esempio il Where metodo o Contains
(usato nel codice precedente). Le query LINQ non vengono eseguite al momento della definizione o della modifica chiamando un metodo quale Where
, Contains
o OrderBy
. L'esecuzione della query viene invece posticipata. Per esecuzione posticipata si intende che la valutazione di un'espressione viene ritardata finché non viene eseguita l'iterazione del valore o non viene chiamato il metodo ToListAsync
. Per altre informazioni sull'esecuzione posticipata di query, vedere Esecuzione di query.
Nota
Il Contains metodo viene eseguito nel database, non nel codice C#. La distinzione tra maiuscole/minuscole nella query dipende dal database e dalle regole di confronto. In SQL Server Contains
esegue il mapping a SQL LIKE che fa distinzione tra maiuscole e minuscole. SQLite con le regole di confronto predefinite è una combinazione di distinzione tra maiuscole e minuscole e di distinzione tra maiuscole e minuscole, a seconda della query. Per informazioni sull'esecuzione di query SQLite senza distinzione tra maiuscole e minuscole, vedere quanto segue:
Accedere a /Movies/Index
. Accodare una stringa di query, ad esempio ?searchString=Ghost
, all'URL. Vengono visualizzati i film filtrati.
Se si modifica la firma del Index
metodo in modo che abbia un parametro denominato id
, il id
parametro corrisponderà al segnaposto facoltativo per le route predefinite {id}
impostate in Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Modificare il parametro in id
e modificare tutte le occorrenze di searchString
in id
.
Il metodo Index
precedente:
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());
}
Il metodo Index
aggiornato con il parametro 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());
}
È ora possibile passare il titolo della ricerca come dati di route (un segmento di URL), anziché come valore della stringa di query.
Tuttavia, non è possibile supporre che gli utenti modifichino l'URL ogni volta che desiderano cercare un film. A questo punto si aggiungeranno elementi dell'interfaccia utente per filtrare i film. Se è stata modificata la firma del metodo Index
per testare come passare il parametro ID
associato alla route, impostarlo di nuovo in modo che accetti un parametro denominato 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());
}
Aprire il Views/Movies/Index.cshtml
file e aggiungere il <form>
markup evidenziato di seguito:
@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">
Il tag <form>
HTML usa l'helper tag del modulo e quindi quando si invia il modulo, la stringa di filtro viene registrata nell'azione Index
del controller di film. Salvare le modifiche e quindi testare il filtro.
Non è presente alcun overload del metodo [HttpPost]
Index
come previsto. Non è necessario perché il metodo non modifica lo stato dell'app, ma filtra solo i dati.
È possibile aggiungere il metodo [HttpPost] Index
seguente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Il parametro notUsed
viene usato per creare un overload per il metodo Index
. Questo aspetto verrà trattato in una fase successiva dell'esercitazione.
Se si aggiunge questo metodo, l'invoker di azione trova la corrispondenza con il metodo [HttpPost] Index
e il metodo [HttpPost] Index
viene eseguito come illustrato nell'immagine seguente.
Tuttavia, anche se si aggiunge questa versione [HttpPost]
del metodo Index
, esiste una limitazione sul modo sulla relativa implementazione. Si supponga che si desideri usare come segnalibro una ricerca specifica o inviare un collegamento agli amici su cui possono fare clic per visualizzare lo stesso elenco filtrato di film. Si noti che l'URL per la richiesta HTTP POST è lo stesso URL per la richiesta GET (localhost:{PORTA}/Movies/Index). Le informazioni sulla ricerca non sono disponibili nell'URL. Le informazioni sulla stringa di ricerca vengono inviate al server come un valore del campo modulo. È possibile eseguire una verifica con gli strumenti di sviluppo del browser o l'eccellente strumento Fiddler. L'immagine seguente mostra gli strumenti di sviluppo del browser Chrome:
È possibile esaminare il parametro di ricerca e il token XSRF nel corpo della richiesta. Come indicato nell'esercitazione precedente, l'helper tag form genera un token antiforgery XSRF. Poiché non si stanno modificando i dati, non è necessario convalidare il token nel metodo del controller.
Poiché il parametro di ricerca si trova nel corpo della richiesta e non nell'URL, non è possibile acquisire queste informazioni sulla ricerca da usare come segnalibro o condividerle con altri utenti. Risolvere questo problema specificando che la richiesta deve essere HTTP GET
trovata nel Views/Movies/Index.cshtml
file.
@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">
A questo punto quando si invia una ricerca, l'URL contiene la stringa di query della ricerca. La ricerca passerà anche al metodo di azione HttpGet Index
, anche se si dispone di un metodo HttpPost Index
.
Il markup seguente mostra la modifica al tag form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Aggiungere la funzionalità di ricerca in base al genere
Aggiungere la classe MovieGenreViewModel
seguente alla cartella 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; }
}
Il modello di vista movie-genre conterrà:
- Un elenco di film.
SelectList
contiene l'elenco dei generi. Consente all'utente di selezionare un genere dall'elenco.MovieGenre
che contiene il genere selezionato.SearchString
, che contiene il testo immesso dagli utenti nella casella di testo di ricerca.
Sostituire il metodo Index
in MoviesController.cs
con il codice seguente:
// 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);
}
Il codice seguente è una query LINQ
che recupera tutti i generi dal database.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
L'elenco SelectList
di generi viene creato presentando generi distinti per evitare che l'elenco di selezione includa generi duplicati.
Quando l'utente cerca l'elemento, viene mantenuto il valore di ricerca nella casella di ricerca.
Aggiungere la funzionalità di ricerca in base al genere alla vista Index
Aggiornare Index.cshtml
in Views/Movies/ come segue:
@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>
Esaminare l'espressione lambda usata nell'helper HTML seguente:
@Html.DisplayNameFor(model => model.Movies![0].Title)
Nel codice precedente, l'helper HTML DisplayNameFor
controlla la proprietà Title
a cui si fa riferimento nell'espressione lambda per determinare il nome visualizzato. Poiché l'espressione lambda viene controllata e anziché essere valutata, non viene generata una violazione di accesso quando model
, model.Movies
o model.Movies[0]
sono null
o vuoti. Quando invece l'espressione lambda viene valutata, ad esempio @Html.DisplayFor(modelItem => item.Title)
, vengono valutati i valori delle proprietà del modello. Il !
valore after model.Movies
è l'operatore null-forgiving, che viene usato per dichiarare che Movies
non è Null.
Eseguire il test dell'app effettuando una ricerca per genere, titolo del film ed entrambi:
In questa sezione si aggiunge la funzionalità di ricerca al metodo di azione Index
che consente di cercare film in base al genere o al nome.
Aggiornare il Index
metodo trovato all'interno Controllers/MoviesController.cs
con il codice seguente:
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 riga seguente nel Index
metodo di azione crea una query LINQ per selezionare i film:
var movies = from m in _context.Movie
select m;
La query viene solo definita in questo punto, ma non viene eseguita nel database.
Se il parametro searchString
contiene una stringa, la query dei film viene modificata per filtrare in base al valore della stringa di ricerca:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Il codice s => s.Title!.ToUpper().Contains(searchString.ToUpper())
precedente è un'espressione lambda. Le espressioni lambda vengono usate nelle query LINQ basate su metodo come argomenti per metodi dell'operatore di query standard, ad esempio il Where metodo o Contains
(usato nel codice precedente). Le query LINQ non vengono eseguite al momento della definizione o della modifica chiamando un metodo quale Where
, Contains
o OrderBy
. L'esecuzione della query viene invece posticipata. Per esecuzione posticipata si intende che la valutazione di un'espressione viene ritardata finché non viene eseguita l'iterazione del valore o non viene chiamato il metodo ToListAsync
. Per altre informazioni sull'esecuzione posticipata di query, vedere Esecuzione di query.
Nota
Il Contains metodo viene eseguito nel database, non nel codice C#. La distinzione tra maiuscole/minuscole nella query dipende dal database e dalle regole di confronto. In SQL Server Contains
esegue il mapping a SQL LIKE che fa distinzione tra maiuscole e minuscole. SQLite con le regole di confronto predefinite è una combinazione di distinzione tra maiuscole e minuscole e di distinzione tra maiuscole e minuscole, a seconda della query. Per informazioni sull'esecuzione di query SQLite senza distinzione tra maiuscole e minuscole, vedere quanto segue:
Accedere a /Movies/Index
. Accodare una stringa di query, ad esempio ?searchString=Ghost
, all'URL. Vengono visualizzati i film filtrati.
Se si modifica la firma del Index
metodo in modo che abbia un parametro denominato id
, il id
parametro corrisponderà al segnaposto facoltativo per le route predefinite {id}
impostate in Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Modificare il parametro in id
e modificare tutte le occorrenze di searchString
in id
.
Il metodo Index
precedente:
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());
}
Il metodo Index
aggiornato con il parametro 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());
}
È ora possibile passare il titolo della ricerca come dati di route (un segmento di URL), anziché come valore della stringa di query.
Tuttavia, non è possibile supporre che gli utenti modifichino l'URL ogni volta che desiderano cercare un film. A questo punto si aggiungeranno elementi dell'interfaccia utente per filtrare i film. Se è stata modificata la firma del metodo Index
per testare come passare il parametro ID
associato alla route, impostarlo di nuovo in modo che accetti un parametro denominato 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());
}
Aprire il Views/Movies/Index.cshtml
file e aggiungere il <form>
markup evidenziato di seguito:
@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">
Il tag <form>
HTML usa l'helper tag del modulo e quindi quando si invia il modulo, la stringa di filtro viene registrata nell'azione Index
del controller di film. Salvare le modifiche e quindi testare il filtro.
Non è presente alcun overload del metodo [HttpPost]
Index
come previsto. Non è necessario perché il metodo non modifica lo stato dell'app, ma filtra solo i dati.
È possibile aggiungere il metodo [HttpPost] Index
seguente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Il parametro notUsed
viene usato per creare un overload per il metodo Index
. Questo aspetto verrà trattato in una fase successiva dell'esercitazione.
Se si aggiunge questo metodo, l'invoker di azione trova la corrispondenza con il metodo [HttpPost] Index
e il metodo [HttpPost] Index
viene eseguito come illustrato nell'immagine seguente.
Tuttavia, anche se si aggiunge questa versione [HttpPost]
del metodo Index
, esiste una limitazione sul modo sulla relativa implementazione. Si supponga che si desideri usare come segnalibro una ricerca specifica o inviare un collegamento agli amici su cui possono fare clic per visualizzare lo stesso elenco filtrato di film. Si noti che l'URL per la richiesta HTTP POST è lo stesso URL per la richiesta GET (localhost:{PORTA}/Movies/Index). Le informazioni sulla ricerca non sono disponibili nell'URL. Le informazioni sulla stringa di ricerca vengono inviate al server come un valore del campo modulo. È possibile eseguire una verifica con gli strumenti di sviluppo del browser o l'eccellente strumento Fiddler. L'immagine seguente mostra gli strumenti di sviluppo del browser Chrome:
È possibile esaminare il parametro di ricerca e il token XSRF nel corpo della richiesta. Come indicato nell'esercitazione precedente, l'helper tag form genera un token antiforgery XSRF. Poiché non si stanno modificando i dati, non è necessario convalidare il token nel metodo del controller.
Poiché il parametro di ricerca si trova nel corpo della richiesta e non nell'URL, non è possibile acquisire queste informazioni sulla ricerca da usare come segnalibro o condividerle con altri utenti. Risolvere questo problema specificando che la richiesta deve essere HTTP GET
trovata nel Views/Movies/Index.cshtml
file.
@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">
A questo punto quando si invia una ricerca, l'URL contiene la stringa di query della ricerca. La ricerca passerà anche al metodo di azione HttpGet Index
, anche se si dispone di un metodo HttpPost Index
.
Il markup seguente mostra la modifica al tag form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Aggiungere la funzionalità di ricerca in base al genere
Aggiungere la classe MovieGenreViewModel
seguente alla cartella 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; }
}
Il modello di vista movie-genre conterrà:
- Un elenco di film.
SelectList
contiene l'elenco dei generi. Consente all'utente di selezionare un genere dall'elenco.MovieGenre
che contiene il genere selezionato.SearchString
, che contiene il testo immesso dagli utenti nella casella di testo di ricerca.
Sostituire il metodo Index
in MoviesController.cs
con il codice seguente:
// 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);
}
Il codice seguente è una query LINQ
che recupera tutti i generi dal database.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
L'elenco SelectList
di generi viene creato presentando generi distinti per evitare che l'elenco di selezione includa generi duplicati.
Quando l'utente cerca l'elemento, viene mantenuto il valore di ricerca nella casella di ricerca.
Aggiungere la funzionalità di ricerca in base al genere alla vista Index
Aggiornare Index.cshtml
in Views/Movies/ come segue:
@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>
Esaminare l'espressione lambda usata nell'helper HTML seguente:
@Html.DisplayNameFor(model => model.Movies![0].Title)
Nel codice precedente, l'helper HTML DisplayNameFor
controlla la proprietà Title
a cui si fa riferimento nell'espressione lambda per determinare il nome visualizzato. Poiché l'espressione lambda viene controllata e anziché essere valutata, non viene generata una violazione di accesso quando model
, model.Movies
o model.Movies[0]
sono null
o vuoti. Quando invece l'espressione lambda viene valutata, ad esempio @Html.DisplayFor(modelItem => item.Title)
, vengono valutati i valori delle proprietà del modello. Il !
valore after model.Movies
è l'operatore null-forgiving, che viene usato per dichiarare che Movies
non è Null.
Eseguire il test dell'app effettuando una ricerca per genere, titolo del film ed entrambi:
In questa sezione si aggiunge la funzionalità di ricerca al metodo di azione Index
che consente di cercare film in base al genere o al nome.
Aggiornare il Index
metodo trovato all'interno Controllers/MoviesController.cs
con il codice seguente:
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 prima riga del metodo di azione Index
crea una query LINQ per selezionare i film:
var movies = from m in _context.Movie
select m;
La query viene solo definita in questo punto, ma non viene eseguita nel database.
Se il parametro searchString
contiene una stringa, la query dei film viene modificata per filtrare in base al valore della stringa di ricerca:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
Il codice s => s.Title!.Contains(searchString)
precedente è un'espressione lambda. Le espressioni lambda vengono usate nelle query LINQ basate su metodo come argomenti per metodi dell'operatore di query standard, ad esempio il Where metodo o Contains
(usato nel codice precedente). Le query LINQ non vengono eseguite al momento della definizione o della modifica chiamando un metodo quale Where
, Contains
o OrderBy
. L'esecuzione della query viene invece posticipata. Per esecuzione posticipata si intende che la valutazione di un'espressione viene ritardata finché non viene eseguita l'iterazione del valore o non viene chiamato il metodo ToListAsync
. Per altre informazioni sull'esecuzione posticipata di query, vedere Esecuzione di query.
Nota: il Contains metodo viene eseguito nel database, non nel codice c# illustrato in precedenza. La distinzione tra maiuscole/minuscole nella query dipende dal database e dalle regole di confronto. In SQL Server Contains
esegue il mapping a SQL LIKE che fa distinzione tra maiuscole e minuscole. In SQLite, con le regole di confronto predefinite si fa distinzione tra maiuscole e minuscole.
Accedere a /Movies/Index
. Accodare una stringa di query, ad esempio ?searchString=Ghost
, all'URL. Vengono visualizzati i film filtrati.
Se si modifica la firma del Index
metodo in modo che abbia un parametro denominato id
, il id
parametro corrisponderà al segnaposto facoltativo per le route predefinite {id}
impostate in Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Modificare il parametro in id
e modificare tutte le occorrenze di searchString
in id
.
Il metodo Index
precedente:
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());
}
Il metodo Index
aggiornato con il parametro 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());
}
È ora possibile passare il titolo della ricerca come dati di route (un segmento di URL), anziché come valore della stringa di query.
Tuttavia, non è possibile supporre che gli utenti modifichino l'URL ogni volta che desiderano cercare un film. A questo punto si aggiungeranno elementi dell'interfaccia utente per filtrare i film. Se è stata modificata la firma del metodo Index
per testare come passare il parametro ID
associato alla route, impostarlo di nuovo in modo che accetti un parametro denominato 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());
}
Aprire il Views/Movies/Index.cshtml
file e aggiungere il <form>
markup evidenziato di seguito:
@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>
Il tag <form>
HTML usa l'helper tag del modulo e quindi quando si invia il modulo, la stringa di filtro viene registrata nell'azione Index
del controller di film. Salvare le modifiche e quindi testare il filtro.
Non è presente alcun overload del metodo [HttpPost]
Index
come previsto. Non è necessario perché il metodo non modifica lo stato dell'app, ma filtra solo i dati.
È possibile aggiungere il metodo [HttpPost] Index
seguente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Il parametro notUsed
viene usato per creare un overload per il metodo Index
. Questo aspetto verrà trattato in una fase successiva dell'esercitazione.
Se si aggiunge questo metodo, l'invoker di azione trova la corrispondenza con il metodo [HttpPost] Index
e il metodo [HttpPost] Index
viene eseguito come illustrato nell'immagine seguente.
Tuttavia, anche se si aggiunge questa versione [HttpPost]
del metodo Index
, esiste una limitazione sul modo sulla relativa implementazione. Si supponga che si desideri usare come segnalibro una ricerca specifica o inviare un collegamento agli amici su cui possono fare clic per visualizzare lo stesso elenco filtrato di film. Si noti che l'URL per la richiesta HTTP POST è lo stesso URL per la richiesta GET (localhost:{PORTA}/Movies/Index). Le informazioni sulla ricerca non sono disponibili nell'URL. Le informazioni sulla stringa di ricerca vengono inviate al server come un valore del campo modulo. È possibile eseguire una verifica con gli strumenti di sviluppo del browser o l'eccellente strumento Fiddler. L'immagine seguente mostra gli strumenti di sviluppo del browser Chrome:
È possibile esaminare il parametro di ricerca e il token XSRF nel corpo della richiesta. Come indicato nell'esercitazione precedente, l'helper tag form genera un token antiforgery XSRF. Poiché non si stanno modificando i dati, non è necessario convalidare il token nel metodo del controller.
Poiché il parametro di ricerca si trova nel corpo della richiesta e non nell'URL, non è possibile acquisire queste informazioni sulla ricerca da usare come segnalibro o condividerle con altri utenti. Risolvere questo problema specificando che la richiesta deve essere HTTP GET
trovata nel Views/Movies/Index.cshtml
file.
@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">
A questo punto quando si invia una ricerca, l'URL contiene la stringa di query della ricerca. La ricerca passerà anche al metodo di azione HttpGet Index
, anche se si dispone di un metodo HttpPost Index
.
Il markup seguente mostra la modifica al tag form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Aggiungere la funzionalità di ricerca in base al genere
Aggiungere la classe MovieGenreViewModel
seguente alla cartella 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; }
}
}
Il modello di vista movie-genre conterrà:
- Un elenco di film.
SelectList
contiene l'elenco dei generi. Consente all'utente di selezionare un genere dall'elenco.MovieGenre
che contiene il genere selezionato.SearchString
, che contiene il testo immesso dagli utenti nella casella di testo di ricerca.
Sostituire il metodo Index
in MoviesController.cs
con il codice seguente:
// 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);
}
Il codice seguente è una query LINQ
che recupera tutti i generi dal database.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
L'elenco SelectList
di generi viene creato presentando generi distinti per evitare che l'elenco di selezione includa generi duplicati.
Quando l'utente cerca l'elemento, viene mantenuto il valore di ricerca nella casella di ricerca.
Aggiungere la funzionalità di ricerca in base al genere alla vista Index
Aggiornare Index.cshtml
in Views/Movies/ come segue:
@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>
Esaminare l'espressione lambda usata nell'helper HTML seguente:
@Html.DisplayNameFor(model => model.Movies[0].Title)
Nel codice precedente, l'helper HTML DisplayNameFor
controlla la proprietà Title
a cui si fa riferimento nell'espressione lambda per determinare il nome visualizzato. Poiché l'espressione lambda viene controllata e anziché essere valutata, non viene generata una violazione di accesso quando model
, model.Movies
o model.Movies[0]
sono null
o vuoti. Quando invece l'espressione lambda viene valutata, ad esempio @Html.DisplayFor(modelItem => item.Title)
, vengono valutati i valori delle proprietà del modello.
Eseguire il test dell'app effettuando una ricerca per genere, titolo del film ed entrambi:
In questa sezione si aggiunge la funzionalità di ricerca al metodo di azione Index
che consente di cercare film in base al genere o al nome.
Aggiornare il Index
metodo trovato all'interno Controllers/MoviesController.cs
con il codice seguente:
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 prima riga del metodo di azione Index
crea una query LINQ per selezionare i film:
var movies = from m in _context.Movie
select m;
La query viene solo definita in questo punto, ma non viene eseguita nel database.
Se il parametro searchString
contiene una stringa, la query dei film viene modificata per filtrare in base al valore della stringa di ricerca:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
Il codice s => s.Title.Contains()
precedente è un'espressione lambda. Le espressioni lambda vengono usate nelle query LINQ basate su metodo come argomenti per metodi dell'operatore di query standard, ad esempio il Where metodo o Contains
(usato nel codice precedente). Le query LINQ non vengono eseguite al momento della definizione o della modifica chiamando un metodo quale Where
, Contains
o OrderBy
. L'esecuzione della query viene invece posticipata. Per esecuzione posticipata si intende che la valutazione di un'espressione viene ritardata finché non viene eseguita l'iterazione del valore o non viene chiamato il metodo ToListAsync
. Per altre informazioni sull'esecuzione posticipata di query, vedere Esecuzione di query.
Nota: il Contains metodo viene eseguito nel database, non nel codice c# illustrato in precedenza. La distinzione tra maiuscole/minuscole nella query dipende dal database e dalle regole di confronto. In SQL Server Contains esegue il mapping a SQL LIKE che fa distinzione tra maiuscole e minuscole. In SQLite, con le regole di confronto predefinite si fa distinzione tra maiuscole e minuscole.
Accedere a /Movies/Index
. Accodare una stringa di query, ad esempio ?searchString=Ghost
, all'URL. Vengono visualizzati i film filtrati.
Se si modifica la firma del Index
metodo in modo che abbia un parametro denominato id
, il id
parametro corrisponderà al segnaposto facoltativo per le route predefinite {id}
impostate in Startup.cs
.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Modificare il parametro in id
e tutte le occorrenze di searchString
cambiano in id
.
Il metodo Index
precedente:
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());
}
Il metodo Index
aggiornato con il parametro 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());
}
È ora possibile passare il titolo della ricerca come dati di route (un segmento di URL), anziché come valore della stringa di query.
Tuttavia, non è possibile supporre che gli utenti modifichino l'URL ogni volta che desiderano cercare un film. A questo punto si aggiungeranno elementi dell'interfaccia utente per filtrare i film. Se è stata modificata la firma del metodo Index
per testare come passare il parametro ID
associato alla route, impostarlo di nuovo in modo che accetti un parametro denominato 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());
}
Aprire il Views/Movies/Index.cshtml
file e aggiungere il <form>
markup evidenziato di seguito:
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>
Il tag <form>
HTML usa l'helper tag del modulo e quindi quando si invia il modulo, la stringa di filtro viene registrata nell'azione Index
del controller di film. Salvare le modifiche e quindi testare il filtro.
Non è presente alcun overload del metodo [HttpPost]
Index
come previsto. Non è necessario perché il metodo non modifica lo stato dell'app, ma filtra solo i dati.
È possibile aggiungere il metodo [HttpPost] Index
seguente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Il parametro notUsed
viene usato per creare un overload per il metodo Index
. Questo aspetto verrà trattato in una fase successiva dell'esercitazione.
Se si aggiunge questo metodo, l'invoker di azione trova la corrispondenza con il metodo [HttpPost] Index
e il metodo [HttpPost] Index
viene eseguito come illustrato nell'immagine seguente.
Tuttavia, anche se si aggiunge questa versione [HttpPost]
del metodo Index
, esiste una limitazione sul modo sulla relativa implementazione. Si supponga che si desideri usare come segnalibro una ricerca specifica o inviare un collegamento agli amici su cui possono fare clic per visualizzare lo stesso elenco filtrato di film. Si noti che l'URL per la richiesta HTTP POST è lo stesso URL per la richiesta GET (localhost:{PORTA}/Movies/Index). Le informazioni sulla ricerca non sono disponibili nell'URL. Le informazioni sulla stringa di ricerca vengono inviate al server come un valore del campo modulo. È possibile eseguire una verifica con gli strumenti di sviluppo del browser o l'eccellente strumento Fiddler. L'immagine seguente mostra gli strumenti di sviluppo del browser Chrome:
È possibile esaminare il parametro di ricerca e il token XSRF nel corpo della richiesta. Come indicato nell'esercitazione precedente, l'helper tag form genera un token antiforgery XSRF. Poiché non si stanno modificando i dati, non è necessario convalidare il token nel metodo del controller.
Poiché il parametro di ricerca si trova nel corpo della richiesta e non nell'URL, non è possibile acquisire queste informazioni sulla ricerca da usare come segnalibro o condividerle con altri utenti. Risolvere questo problema specificando che la richiesta deve essere HTTP GET
trovata nel Views/Movies/Index.cshtml
file.
@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)
A questo punto quando si invia una ricerca, l'URL contiene la stringa di query della ricerca. La ricerca passerà anche al metodo di azione HttpGet Index
, anche se si dispone di un metodo HttpPost Index
.
Il markup seguente mostra la modifica al tag form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Aggiungere la funzionalità di ricerca in base al genere
Aggiungere la classe MovieGenreViewModel
seguente alla cartella 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; }
}
}
Il modello di vista movie-genre conterrà:
- Un elenco di film.
SelectList
contiene l'elenco dei generi. Consente all'utente di selezionare un genere dall'elenco.MovieGenre
che contiene il genere selezionato.SearchString
, che contiene il testo immesso dagli utenti nella casella di testo di ricerca.
Sostituire il metodo Index
in MoviesController.cs
con il codice seguente:
// 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);
}
Il codice seguente è una query LINQ
che recupera tutti i generi dal database.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
L'elenco SelectList
di generi viene creato presentando generi distinti per evitare che l'elenco di selezione includa generi duplicati.
Quando l'utente cerca l'elemento, viene mantenuto il valore di ricerca nella casella di ricerca.
Aggiungere la funzionalità di ricerca in base al genere alla vista Index
Aggiornare Index.cshtml
in Views/Movies/ come segue:
@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>
Esaminare l'espressione lambda usata nell'helper HTML seguente:
@Html.DisplayNameFor(model => model.Movies[0].Title)
Nel codice precedente, l'helper HTML DisplayNameFor
controlla la proprietà Title
a cui si fa riferimento nell'espressione lambda per determinare il nome visualizzato. Poiché l'espressione lambda viene controllata e anziché essere valutata, non viene generata una violazione di accesso quando model
, model.Movies
o model.Movies[0]
sono null
o vuoti. Quando invece l'espressione lambda viene valutata, ad esempio @Html.DisplayFor(modelItem => item.Title)
, vengono valutati i valori delle proprietà del modello.
Eseguire il test dell'app effettuando una ricerca per genere, titolo del film ed entrambi: