Część 7. Dodawanie wyszukiwania do aplikacji MVC platformy ASP.NET Core
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Autor: Rick Anderson
W tej sekcji dodasz możliwość wyszukiwania do Index
metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.
Zaktualizuj metodę znajdującą Index
się wewnątrz Controllers/MoviesController.cs
za pomocą następującego kodu:
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());
}
Poniższy wiersz w metodzie Index
akcji tworzy zapytanie LINQ w celu wybrania filmów:
var movies = from m in _context.Movie
select m;
Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.
searchString
Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Powyższy s => s.Title!.ToUpper().Contains(searchString.ToUpper())
kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains
(używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where
, Contains
lub OrderBy
. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync
metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.
Uwaga
Metoda Contains jest uruchamiana w bazie danych, a nie w kodzie języka C#. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains
mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. SqLite z sortowaniem domyślnym jest kombinacją wielkości liter i wielkości liter w rozróżnianiu w zależności od zapytania. Aby uzyskać informacje na temat tworzenia zapytań SQLite bez uwzględniania wielkości liter, zobacz następujące kwestie:
Przejdź do /Movies/Index
. Dołącz ciąg zapytania, taki jak ?searchString=Ghost
do adresu URL. Zostaną wyświetlone przefiltrowane filmy.
Jeśli zmienisz sygnaturę Index
metody tak, aby parametr miał nazwę id
, id
parametr będzie zgodny z opcjonalnym {id}
symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Zmień parametr na id
i zmień wszystkie wystąpienia elementu searchString
na id
.
Poprzednia metoda Index
:
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());
}
Zaktualizowana Index
metoda z parametrem 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());
}
Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.
Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index
powiązanego ID
trasą, zmień go z powrotem, aby pobierał parametr o nazwie 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());
}
Views/Movies/Index.cshtml
Otwórz plik i dodaj <form>
znacznik wyróżniony poniżej:
@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">
Tag HTML <form>
używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index
akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.
Nie ma [HttpPost]
przeciążenia Index
metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.
Możesz dodać następującą [HttpPost] Index
metodę.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Parametr notUsed
służy do tworzenia przeciążenia dla Index
metody . Omówimy to w dalszej części tego samouczka.
Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index
metodą, a [HttpPost] Index
metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.
Jednak nawet jeśli dodasz tę [HttpPost]
wersję Index
metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler.
Na poniższej ilustracji przedstawiono przeglądarkę Chrome Narzędzia programistyczne z wybraną kartą Sieć i Nagłówki:
Karty Sieć i Ładunek są wybierane do wyświetlania danych formularza:
Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagu formularza generuje token antyforgery XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.
Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie powinno znajdować się HTTP GET
w tagu form
Views/Movies/Index.cshtml
znajdującym się w pliku.
@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">
Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index
metody akcji, nawet jeśli masz metodę HttpPost Index
.
Dodawanie wyszukiwania według gatunku
Dodaj następującą MovieGenreViewModel
klasę do folderu 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; }
}
Model widoku gatunku filmowego będzie zawierać:
- Lista filmów.
- Element
SelectList
zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy. -
MovieGenre
, który zawiera wybrany gatunek. -
SearchString
, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.
Zastąp metodę Index
w MoviesController.cs
pliku następującym kodem:
// 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);
}
Poniższy kod to LINQ
zapytanie, które pobiera wszystkie gatunki z bazy danych.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Gatunek SelectList
jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).
Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.
Dodawanie wyszukiwania według gatunku do widoku indeksu
Aktualizacja Index.cshtml
znaleziona w widokach/filmach/ w następujący sposób:
@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>
Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
W poprzednim kodzie DisplayNameFor
Pomocnik HTML sprawdza Title
właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model
, lub model.Movies
model.Movies[0]
są lub pustenull
. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)
), wartości właściwości modelu są obliczane.
!
Po model.Movies
jest operatorem forgiving o wartości null, który służy do deklarowania, że Movies
nie ma wartości null.
Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:
W tej sekcji dodasz możliwość wyszukiwania do Index
metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.
Zaktualizuj metodę znajdującą Index
się wewnątrz Controllers/MoviesController.cs
za pomocą następującego kodu:
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());
}
Poniższy wiersz w metodzie Index
akcji tworzy zapytanie LINQ w celu wybrania filmów:
var movies = from m in _context.Movie
select m;
Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.
searchString
Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Powyższy s => s.Title!.ToUpper().Contains(searchString.ToUpper())
kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains
(używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where
, Contains
lub OrderBy
. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync
metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.
Uwaga
Metoda Contains jest uruchamiana w bazie danych, a nie w kodzie języka C#. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains
mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. SqLite z sortowaniem domyślnym jest kombinacją wielkości liter i wielkości liter w rozróżnianiu w zależności od zapytania. Aby uzyskać informacje na temat tworzenia zapytań SQLite bez uwzględniania wielkości liter, zobacz następujące kwestie:
Przejdź do /Movies/Index
. Dołącz ciąg zapytania, taki jak ?searchString=Ghost
do adresu URL. Zostaną wyświetlone przefiltrowane filmy.
Jeśli zmienisz sygnaturę Index
metody tak, aby parametr miał nazwę id
, id
parametr będzie zgodny z opcjonalnym {id}
symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Zmień parametr na id
i zmień wszystkie wystąpienia elementu searchString
na id
.
Poprzednia metoda Index
:
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());
}
Zaktualizowana Index
metoda z parametrem 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());
}
Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.
Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index
powiązanego ID
trasą, zmień go z powrotem, aby pobierał parametr o nazwie 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());
}
Views/Movies/Index.cshtml
Otwórz plik i dodaj <form>
znacznik wyróżniony poniżej:
@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">
Tag HTML <form>
używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index
akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.
Nie ma [HttpPost]
przeciążenia Index
metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.
Możesz dodać następującą [HttpPost] Index
metodę.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Parametr notUsed
służy do tworzenia przeciążenia dla Index
metody . Omówimy to w dalszej części tego samouczka.
Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index
metodą, a [HttpPost] Index
metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.
Jednak nawet jeśli dodasz tę [HttpPost]
wersję Index
metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:
Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagu formularza generuje token antyforgery XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.
Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET
które należy znaleźć w Views/Movies/Index.cshtml
pliku.
@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">
Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index
metody akcji, nawet jeśli masz metodę HttpPost Index
.
Poniższy znacznik pokazuje zmianę tagu form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Dodawanie wyszukiwania według gatunku
Dodaj następującą MovieGenreViewModel
klasę do folderu 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; }
}
Model widoku gatunku filmowego będzie zawierać:
- Lista filmów.
- Element
SelectList
zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy. -
MovieGenre
, który zawiera wybrany gatunek. -
SearchString
, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.
Zastąp metodę Index
w MoviesController.cs
pliku następującym kodem:
// 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);
}
Poniższy kod to LINQ
zapytanie, które pobiera wszystkie gatunki z bazy danych.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Gatunek SelectList
jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).
Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.
Dodawanie wyszukiwania według gatunku do widoku indeksu
Aktualizacja Index.cshtml
znaleziona w widokach/filmach/ w następujący sposób:
@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>
Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
W poprzednim kodzie DisplayNameFor
Pomocnik HTML sprawdza Title
właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model
, lub model.Movies
model.Movies[0]
są lub pustenull
. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)
), wartości właściwości modelu są obliczane.
!
Po model.Movies
jest operatorem forgiving o wartości null, który służy do deklarowania, że Movies
nie ma wartości null.
Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:
W tej sekcji dodasz możliwość wyszukiwania do Index
metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.
Zaktualizuj metodę znajdującą Index
się wewnątrz Controllers/MoviesController.cs
za pomocą następującego kodu:
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());
}
Poniższy wiersz w metodzie Index
akcji tworzy zapytanie LINQ w celu wybrania filmów:
var movies = from m in _context.Movie
select m;
Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.
searchString
Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Powyższy s => s.Title!.ToUpper().Contains(searchString.ToUpper())
kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains
(używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where
, Contains
lub OrderBy
. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync
metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.
Uwaga
Metoda Contains jest uruchamiana w bazie danych, a nie w kodzie języka C#. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains
mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. SqLite z sortowaniem domyślnym jest kombinacją wielkości liter i wielkości liter w rozróżnianiu w zależności od zapytania. Aby uzyskać informacje na temat tworzenia zapytań SQLite bez uwzględniania wielkości liter, zobacz następujące kwestie:
Przejdź do /Movies/Index
. Dołącz ciąg zapytania, taki jak ?searchString=Ghost
do adresu URL. Zostaną wyświetlone przefiltrowane filmy.
Jeśli zmienisz sygnaturę Index
metody tak, aby parametr miał nazwę id
, id
parametr będzie zgodny z opcjonalnym {id}
symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Zmień parametr na id
i zmień wszystkie wystąpienia elementu searchString
na id
.
Poprzednia metoda Index
:
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());
}
Zaktualizowana Index
metoda z parametrem 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());
}
Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.
Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index
powiązanego ID
trasą, zmień go z powrotem, aby pobierał parametr o nazwie 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());
}
Views/Movies/Index.cshtml
Otwórz plik i dodaj <form>
znacznik wyróżniony poniżej:
@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">
Tag HTML <form>
używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index
akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.
Nie ma [HttpPost]
przeciążenia Index
metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.
Możesz dodać następującą [HttpPost] Index
metodę.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Parametr notUsed
służy do tworzenia przeciążenia dla Index
metody . Omówimy to w dalszej części tego samouczka.
Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index
metodą, a [HttpPost] Index
metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.
Jednak nawet jeśli dodasz tę [HttpPost]
wersję Index
metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:
Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagu formularza generuje token antyforgery XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.
Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET
które należy znaleźć w Views/Movies/Index.cshtml
pliku.
@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">
Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index
metody akcji, nawet jeśli masz metodę HttpPost Index
.
Poniższy znacznik pokazuje zmianę tagu form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Dodawanie wyszukiwania według gatunku
Dodaj następującą MovieGenreViewModel
klasę do folderu 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; }
}
Model widoku gatunku filmowego będzie zawierać:
- Lista filmów.
- Element
SelectList
zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy. -
MovieGenre
, który zawiera wybrany gatunek. -
SearchString
, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.
Zastąp metodę Index
w MoviesController.cs
pliku następującym kodem:
// 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);
}
Poniższy kod to LINQ
zapytanie, które pobiera wszystkie gatunki z bazy danych.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Gatunek SelectList
jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).
Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.
Dodawanie wyszukiwania według gatunku do widoku indeksu
Aktualizacja Index.cshtml
znaleziona w widokach/filmach/ w następujący sposób:
@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>
Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
W poprzednim kodzie DisplayNameFor
Pomocnik HTML sprawdza Title
właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model
, lub model.Movies
model.Movies[0]
są lub pustenull
. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)
), wartości właściwości modelu są obliczane.
!
Po model.Movies
jest operatorem forgiving o wartości null, który służy do deklarowania, że Movies
nie ma wartości null.
Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:
W tej sekcji dodasz możliwość wyszukiwania do Index
metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.
Zaktualizuj metodę znajdującą Index
się wewnątrz Controllers/MoviesController.cs
za pomocą następującego kodu:
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());
}
Pierwszy wiersz Index
metody akcji tworzy zapytanie LINQ w celu wybrania filmów:
var movies = from m in _context.Movie
select m;
Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.
searchString
Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
Powyższy s => s.Title!.Contains(searchString)
kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains
(używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where
, Contains
lub OrderBy
. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync
metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.
Uwaga: Contains metoda jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains
mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. W programie SQLite z domyślnym sortowaniem jest uwzględniana wielkość liter.
Przejdź do /Movies/Index
. Dołącz ciąg zapytania, taki jak ?searchString=Ghost
do adresu URL. Zostaną wyświetlone przefiltrowane filmy.
Jeśli zmienisz sygnaturę Index
metody tak, aby parametr miał nazwę id
, id
parametr będzie zgodny z opcjonalnym {id}
symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Zmień parametr na id
i zmień wszystkie wystąpienia elementu searchString
na id
.
Poprzednia metoda Index
:
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());
}
Zaktualizowana Index
metoda z parametrem 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());
}
Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.
Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index
powiązanego ID
trasą, zmień go z powrotem, aby pobierał parametr o nazwie 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());
}
Views/Movies/Index.cshtml
Otwórz plik i dodaj <form>
znacznik wyróżniony poniżej:
@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>
Tag HTML <form>
używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index
akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.
Nie ma [HttpPost]
przeciążenia Index
metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.
Możesz dodać następującą [HttpPost] Index
metodę.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Parametr notUsed
służy do tworzenia przeciążenia dla Index
metody . Omówimy to w dalszej części tego samouczka.
Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index
metodą, a [HttpPost] Index
metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.
Jednak nawet jeśli dodasz tę [HttpPost]
wersję Index
metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:
Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagu formularza generuje token antyforgery XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.
Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET
które należy znaleźć w Views/Movies/Index.cshtml
pliku.
@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">
Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index
metody akcji, nawet jeśli masz metodę HttpPost Index
.
Poniższy znacznik pokazuje zmianę tagu form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Dodawanie wyszukiwania według gatunku
Dodaj następującą MovieGenreViewModel
klasę do folderu 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; }
}
}
Model widoku gatunku filmowego będzie zawierać:
- Lista filmów.
- Element
SelectList
zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy. -
MovieGenre
, który zawiera wybrany gatunek. -
SearchString
, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.
Zastąp metodę Index
w MoviesController.cs
pliku następującym kodem:
// 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);
}
Poniższy kod to LINQ
zapytanie, które pobiera wszystkie gatunki z bazy danych.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Gatunek SelectList
jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).
Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.
Dodawanie wyszukiwania według gatunku do widoku indeksu
Aktualizacja Index.cshtml
znaleziona w widokach/filmach/ w następujący sposób:
@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>
Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:
@Html.DisplayNameFor(model => model.Movies[0].Title)
W poprzednim kodzie DisplayNameFor
Pomocnik HTML sprawdza Title
właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model
, lub model.Movies
model.Movies[0]
są lub pustenull
. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)
), wartości właściwości modelu są obliczane.
Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:
W tej sekcji dodasz możliwość wyszukiwania do Index
metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.
Zaktualizuj metodę znajdującą Index
się wewnątrz Controllers/MoviesController.cs
za pomocą następującego kodu:
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());
}
Pierwszy wiersz Index
metody akcji tworzy zapytanie LINQ w celu wybrania filmów:
var movies = from m in _context.Movie
select m;
Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.
searchString
Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
Powyższy s => s.Title.Contains()
kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains
(używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where
, Contains
lub OrderBy
. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync
metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.
Uwaga: Contains metoda jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. W programie SQLite z domyślnym sortowaniem jest uwzględniana wielkość liter.
Przejdź do /Movies/Index
. Dołącz ciąg zapytania, taki jak ?searchString=Ghost
do adresu URL. Zostaną wyświetlone przefiltrowane filmy.
Jeśli zmienisz sygnaturę Index
metody tak, aby parametr miał nazwę id
, id
parametr będzie zgodny z opcjonalnym {id}
symbolem zastępczym dla tras domyślnych ustawionych w pliku Startup.cs
.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Zmień parametr na id
i wszystkie wystąpienia searchString
zmiany na id
.
Poprzednia metoda Index
:
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());
}
Zaktualizowana Index
metoda z parametrem 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());
}
Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.
Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index
powiązanego ID
trasą, zmień go z powrotem, aby pobierał parametr o nazwie 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());
}
Views/Movies/Index.cshtml
Otwórz plik i dodaj <form>
znacznik wyróżniony poniżej:
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>
Tag HTML <form>
używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index
akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.
Nie ma [HttpPost]
przeciążenia Index
metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.
Możesz dodać następującą [HttpPost] Index
metodę.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Parametr notUsed
służy do tworzenia przeciążenia dla Index
metody . Omówimy to w dalszej części tego samouczka.
Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index
metodą, a [HttpPost] Index
metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.
Jednak nawet jeśli dodasz tę [HttpPost]
wersję Index
metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:
Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagu formularza generuje token antyforgery XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.
Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET
które należy znaleźć w Views/Movies/Index.cshtml
pliku.
@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)
Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index
metody akcji, nawet jeśli masz metodę HttpPost Index
.
Poniższy znacznik pokazuje zmianę tagu form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Dodawanie wyszukiwania według gatunku
Dodaj następującą MovieGenreViewModel
klasę do folderu 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; }
}
}
Model widoku gatunku filmowego będzie zawierać:
- Lista filmów.
- Element
SelectList
zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy. -
MovieGenre
, który zawiera wybrany gatunek. -
SearchString
, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.
Zastąp metodę Index
w MoviesController.cs
pliku następującym kodem:
// 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);
}
Poniższy kod to LINQ
zapytanie, które pobiera wszystkie gatunki z bazy danych.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Gatunek SelectList
jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).
Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.
Dodawanie wyszukiwania według gatunku do widoku indeksu
Aktualizacja Index.cshtml
znaleziona w widokach/filmach/ w następujący sposób:
@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>
Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:
@Html.DisplayNameFor(model => model.Movies[0].Title)
W poprzednim kodzie DisplayNameFor
Pomocnik HTML sprawdza Title
właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model
, lub model.Movies
model.Movies[0]
są lub pustenull
. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)
), wartości właściwości modelu są obliczane.
Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba: