Часть 7. Добавление поиска в приложение MVC ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
Автор: Рик Андерсон (Rick Anderson)
В этом разделе вы добавите в метод действия Index
возможности поиска, которые позволяют выполнять поиск фильмов по жанру или названию.
Обновите метод, найденный Index
внутри Controllers/MoviesController.cs
, с помощью следующего кода:
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());
}
Следующая строка в методе Index
действия создает запрос LINQ для выбора фильмов:
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если параметр searchString
содержит строку, запрос фильмов изменяется для фильтрации по значению в строке поиска:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Приведенный выше код s => s.Title!.ToUpper().Contains(searchString.ToUpper())
представляет собой лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как метод Where или Contains
(используется в приведенном выше коде). Запросы LINQ не выполняются, если они определяются или изменяются путем вызова метода, например Where
, Contains
или OrderBy
. Вместо этого выполнение запроса откладывается. Это означает, что вычисление выражения откладывается до тех пор, пока не будет выполнена итерация его реализованного значения или пока не будет вызван метод ToListAsync
. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.
Примечание.
Метод Contains выполняется в базе данных, а не в коде C#. Регистр символов запроса учитывается в зависимости от параметров базы данных и сортировки. В SQL Server метод Contains
сопоставляется с SQL LIKE, в котором регистр символов не учитывается. SQLite с параметрами сортировки по умолчанию — это смесь параметров с учетом регистра и параметров, которые НЕ учитывают регистр, в зависимости от запроса. Сведения о том, как выполнять запросы SQLite, которые не учитывают регистр, см. в следующих статьях:
Перейдите к /Movies/Index
. Добавьте в URL-адрес строку запроса, например ?searchString=Ghost
. Отображаются отфильтрованные фильмы.
Если изменить сигнатуру Index
метода с именем id
параметра, id
параметр будет соответствовать необязательному {id}
заполнителю для заданных по умолчанию маршрутов Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Измените параметр на id
, а все вхождения searchString
— на id
.
Предыдущий метод 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());
}
Обновленный метод Index
с параметром 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());
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Итак, теперь вам необходимо добавить элементы пользовательского интерфейса для удобства фильтрации фильмов. Если вы изменили сигнатуру метода Index
для тестирования передачи параметра ID
с привязкой к маршруту, измените ее снова, чтобы она снова принимала параметр 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
Откройте файл и добавьте разметку, выделенную <form>
ниже:
@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">
Тег HTML <form>
использует вспомогательную функцию тега Form, чтобы при отправке формы строка фильтра передавалась в действие Index
контроллера movies. Сохраните изменения и протестируйте фильтр.
Вопреки ожиданиям, перегрузка [HttpPost]
для метода Index
отсутствует. Она не нужна, поскольку метод не изменяет состояние приложения и просто выполняет фильтрацию данных.
Можно добавить следующий метод [HttpPost] Index
.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Параметр notUsed
используется для создания перегрузки метода Index
. Это мы обсудим далее в этом учебнике.
При добавлении этого метода вызывающий метод действия будет сопоставлять метод [HttpPost] Index
, а метод [HttpPost] Index
будет выполняться, как показано на рисунке ниже.
Тем не менее при добавлении этой версии [HttpPost]
метода Index
существует ограничение на общую реализацию. Допустим, вам необходимо добавить в закладки конкретный поиск или отправить друзьям ссылку, по которой они могут просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание, что URL-адрес запроса HTTP POST совпадает с URL-адресом запроса GET (localhost:{ПОРТ}/Movies/Index) — в URL-адресе отсутствуют сведения о поиске. Данные строки поиска отправляются на сервер в виде значения поля формы. Вы можете проверить это с помощью средств разработчика для браузера или инструмента Fiddler.
На следующем рисунке показан браузер Chrome Средства разработчика с выбранными вкладками "Сеть и заголовки":
Вкладки "Сеть и полезные данные " выбраны для просмотра данных формы:
В теле запроса отображается параметр поиска и маркер XSRF. Обратите внимание, что, как упоминалось в предыдущем руководстве, вспомогательный элемент тега формы создает маркер антифоргерии XSRF . Поскольку мы не изменяем данные, проверять маркер безопасности в методе контроллера не нужно.
Так как параметр поиска находится в теле запроса, а не в URL-адресе, эти сведения о поиске нельзя добавить в закладки или открыть для общего доступа. Исправьте это, указав, что запрос должен находиться HTTP GET
в form
теге, найденном Views/Movies/Index.cshtml
в файле.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
После отправки поиска URL-адрес содержит строку поискового запроса. Поиск также переносится в метод HttpGet Index
, даже если у вас определен метод HttpPost Index
.
Добавление поиска по жанру
Добавьте следующий класс MovieGenreViewModel
в папку 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; }
}
Модель представления фильмов по жанру будет содержать:
- Список фильмов.
- Объект
SelectList
со списком жанров. В этом списке пользователь может выбрать жанр фильма. - Объект
MovieGenre
, содержащий выбранный жанр. -
SearchString
, содержащий текст, который пользователи вводят в поле поиска.
Замените метод Index
в файле MoviesController.cs
следующим кодом:
// 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);
}
Следующий код определяет запрос LINQ
, который извлекает все жанры из базы данных.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Объект SelectList
со списком жанров создается путем проецирования отдельных жанров (это необходимо, чтобы исключить повторяющиеся жанры).
Когда пользователь выполняет поиск элемента, значение поиска сохраняется в поле поиска.
Добавление поиска по жанру в представление индекса
Обновите файл Index.cshtml
, находящийся в папке Views/Movies/, следующим образом:
@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>
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
В предыдущем коде вспомогательный метод HTML DisplayNameFor
проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Поскольку лямбда-выражение проверяется, а не вычисляется, в том случае, если model
, model.Movies
или model.Movies[0]
имеют значение null
или пусты, не происходит нарушение прав доступа. При вычислении лямбда-выражения (например, @Html.DisplayFor(modelItem => item.Title)
) вычисляются значения для свойств модели.
!
После model.Movies
этого используется оператор, допускающий значение NULL, который используется для объявления, что Movies
не равно null.
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и по обоим этим параметрам:
В этом разделе вы добавите в метод действия Index
возможности поиска, которые позволяют выполнять поиск фильмов по жанру или названию.
Обновите метод, найденный Index
внутри Controllers/MoviesController.cs
, с помощью следующего кода:
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());
}
Следующая строка в методе Index
действия создает запрос LINQ для выбора фильмов:
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если параметр searchString
содержит строку, запрос фильмов изменяется для фильтрации по значению в строке поиска:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Приведенный выше код s => s.Title!.ToUpper().Contains(searchString.ToUpper())
представляет собой лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как метод Where или Contains
(используется в приведенном выше коде). Запросы LINQ не выполняются, если они определяются или изменяются путем вызова метода, например Where
, Contains
или OrderBy
. Вместо этого выполнение запроса откладывается. Это означает, что вычисление выражения откладывается до тех пор, пока не будет выполнена итерация его реализованного значения или пока не будет вызван метод ToListAsync
. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.
Примечание.
Метод Contains выполняется в базе данных, а не в коде C#. Регистр символов запроса учитывается в зависимости от параметров базы данных и сортировки. В SQL Server метод Contains
сопоставляется с SQL LIKE, в котором регистр символов не учитывается. SQLite с параметрами сортировки по умолчанию — это смесь параметров с учетом регистра и параметров, которые НЕ учитывают регистр, в зависимости от запроса. Сведения о том, как выполнять запросы SQLite, которые не учитывают регистр, см. в следующих статьях:
Перейдите к /Movies/Index
. Добавьте в URL-адрес строку запроса, например ?searchString=Ghost
. Отображаются отфильтрованные фильмы.
Если изменить сигнатуру Index
метода с именем id
параметра, id
параметр будет соответствовать необязательному {id}
заполнителю для заданных по умолчанию маршрутов Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Измените параметр на id
, а все вхождения searchString
— на id
.
Предыдущий метод 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());
}
Обновленный метод Index
с параметром 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());
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Итак, теперь вам необходимо добавить элементы пользовательского интерфейса для удобства фильтрации фильмов. Если вы изменили сигнатуру метода Index
для тестирования передачи параметра ID
с привязкой к маршруту, измените ее снова, чтобы она снова принимала параметр 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
Откройте файл и добавьте разметку, выделенную <form>
ниже:
@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">
Тег HTML <form>
использует вспомогательную функцию тега Form, чтобы при отправке формы строка фильтра передавалась в действие Index
контроллера movies. Сохраните изменения и протестируйте фильтр.
Вопреки ожиданиям, перегрузка [HttpPost]
для метода Index
отсутствует. Она не нужна, поскольку метод не изменяет состояние приложения и просто выполняет фильтрацию данных.
Можно добавить следующий метод [HttpPost] Index
.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Параметр notUsed
используется для создания перегрузки метода Index
. Это мы обсудим далее в этом учебнике.
При добавлении этого метода вызывающий метод действия будет сопоставлять метод [HttpPost] Index
, а метод [HttpPost] Index
будет выполняться, как показано на рисунке ниже.
Тем не менее при добавлении этой версии [HttpPost]
метода Index
существует ограничение на общую реализацию. Допустим, вам необходимо добавить в закладки конкретный поиск или отправить друзьям ссылку, по которой они могут просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание, что URL-адрес запроса HTTP POST совпадает с URL-адресом запроса GET (localhost:{ПОРТ}/Movies/Index) — в URL-адресе отсутствуют сведения о поиске. Данные строки поиска отправляются на сервер в виде значения поля формы. Вы можете проверить это с помощью средств разработчика для браузера или инструмента Fiddler. На рисунке ниже показаны средства разработчика для браузера Chrome:
В теле запроса отображается параметр поиска и маркер XSRF. Обратите внимание, что, как упоминалось в предыдущем руководстве, вспомогательный элемент тега формы создает маркер антифоргерии XSRF . Поскольку мы не изменяем данные, проверять маркер безопасности в методе контроллера не нужно.
Так как параметр поиска находится в теле запроса, а не в URL-адресе, эти сведения о поиске нельзя добавить в закладки или открыть для общего доступа. Исправьте это, указав запрос, который должен находиться HTTP GET
в Views/Movies/Index.cshtml
файле.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
После отправки поиска URL-адрес содержит строку поискового запроса. Поиск также переносится в метод HttpGet Index
, даже если у вас определен метод HttpPost Index
.
В следующем примере разметки показано изменение тега form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Добавление поиска по жанру
Добавьте следующий класс MovieGenreViewModel
в папку 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; }
}
Модель представления фильмов по жанру будет содержать:
- Список фильмов.
- Объект
SelectList
со списком жанров. В этом списке пользователь может выбрать жанр фильма. - Объект
MovieGenre
, содержащий выбранный жанр. -
SearchString
, содержащий текст, который пользователи вводят в поле поиска.
Замените метод Index
в файле MoviesController.cs
следующим кодом:
// 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);
}
Следующий код определяет запрос LINQ
, который извлекает все жанры из базы данных.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Объект SelectList
со списком жанров создается путем проецирования отдельных жанров (это необходимо, чтобы исключить повторяющиеся жанры).
Когда пользователь выполняет поиск элемента, значение поиска сохраняется в поле поиска.
Добавление поиска по жанру в представление индекса
Обновите файл Index.cshtml
, находящийся в папке Views/Movies/, следующим образом:
@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>
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
В предыдущем коде вспомогательный метод HTML DisplayNameFor
проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Поскольку лямбда-выражение проверяется, а не вычисляется, в том случае, если model
, model.Movies
или model.Movies[0]
имеют значение null
или пусты, не происходит нарушение прав доступа. При вычислении лямбда-выражения (например, @Html.DisplayFor(modelItem => item.Title)
) вычисляются значения для свойств модели.
!
После model.Movies
этого используется оператор, допускающий значение NULL, который используется для объявления, что Movies
не равно null.
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и по обоим этим параметрам:
В этом разделе вы добавите в метод действия Index
возможности поиска, которые позволяют выполнять поиск фильмов по жанру или названию.
Обновите метод, найденный Index
внутри Controllers/MoviesController.cs
, с помощью следующего кода:
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());
}
Следующая строка в методе Index
действия создает запрос LINQ для выбора фильмов:
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если параметр searchString
содержит строку, запрос фильмов изменяется для фильтрации по значению в строке поиска:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.ToUpper().Contains(searchString.ToUpper()));
}
Приведенный выше код s => s.Title!.ToUpper().Contains(searchString.ToUpper())
представляет собой лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как метод Where или Contains
(используется в приведенном выше коде). Запросы LINQ не выполняются, если они определяются или изменяются путем вызова метода, например Where
, Contains
или OrderBy
. Вместо этого выполнение запроса откладывается. Это означает, что вычисление выражения откладывается до тех пор, пока не будет выполнена итерация его реализованного значения или пока не будет вызван метод ToListAsync
. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.
Примечание.
Метод Contains выполняется в базе данных, а не в коде C#. Регистр символов запроса учитывается в зависимости от параметров базы данных и сортировки. В SQL Server метод Contains
сопоставляется с SQL LIKE, в котором регистр символов не учитывается. SQLite с параметрами сортировки по умолчанию — это смесь параметров с учетом регистра и параметров, которые НЕ учитывают регистр, в зависимости от запроса. Сведения о том, как выполнять запросы SQLite, которые не учитывают регистр, см. в следующих статьях:
Перейдите к /Movies/Index
. Добавьте в URL-адрес строку запроса, например ?searchString=Ghost
. Отображаются отфильтрованные фильмы.
Если изменить сигнатуру Index
метода с именем id
параметра, id
параметр будет соответствовать необязательному {id}
заполнителю для заданных по умолчанию маршрутов Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Измените параметр на id
, а все вхождения searchString
— на id
.
Предыдущий метод 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());
}
Обновленный метод Index
с параметром 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());
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Итак, теперь вам необходимо добавить элементы пользовательского интерфейса для удобства фильтрации фильмов. Если вы изменили сигнатуру метода Index
для тестирования передачи параметра ID
с привязкой к маршруту, измените ее снова, чтобы она снова принимала параметр 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
Откройте файл и добавьте разметку, выделенную <form>
ниже:
@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">
Тег HTML <form>
использует вспомогательную функцию тега Form, чтобы при отправке формы строка фильтра передавалась в действие Index
контроллера movies. Сохраните изменения и протестируйте фильтр.
Вопреки ожиданиям, перегрузка [HttpPost]
для метода Index
отсутствует. Она не нужна, поскольку метод не изменяет состояние приложения и просто выполняет фильтрацию данных.
Можно добавить следующий метод [HttpPost] Index
.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Параметр notUsed
используется для создания перегрузки метода Index
. Это мы обсудим далее в этом учебнике.
При добавлении этого метода вызывающий метод действия будет сопоставлять метод [HttpPost] Index
, а метод [HttpPost] Index
будет выполняться, как показано на рисунке ниже.
Тем не менее при добавлении этой версии [HttpPost]
метода Index
существует ограничение на общую реализацию. Допустим, вам необходимо добавить в закладки конкретный поиск или отправить друзьям ссылку, по которой они могут просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание, что URL-адрес запроса HTTP POST совпадает с URL-адресом запроса GET (localhost:{ПОРТ}/Movies/Index) — в URL-адресе отсутствуют сведения о поиске. Данные строки поиска отправляются на сервер в виде значения поля формы. Вы можете проверить это с помощью средств разработчика для браузера или инструмента Fiddler. На рисунке ниже показаны средства разработчика для браузера Chrome:
В теле запроса отображается параметр поиска и маркер XSRF. Обратите внимание, что, как упоминалось в предыдущем руководстве, вспомогательный элемент тега формы создает маркер антифоргерии XSRF . Поскольку мы не изменяем данные, проверять маркер безопасности в методе контроллера не нужно.
Так как параметр поиска находится в теле запроса, а не в URL-адресе, эти сведения о поиске нельзя добавить в закладки или открыть для общего доступа. Исправьте это, указав запрос, который должен находиться HTTP GET
в Views/Movies/Index.cshtml
файле.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
После отправки поиска URL-адрес содержит строку поискового запроса. Поиск также переносится в метод HttpGet Index
, даже если у вас определен метод HttpPost Index
.
В следующем примере разметки показано изменение тега form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Добавление поиска по жанру
Добавьте следующий класс MovieGenreViewModel
в папку 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; }
}
Модель представления фильмов по жанру будет содержать:
- Список фильмов.
- Объект
SelectList
со списком жанров. В этом списке пользователь может выбрать жанр фильма. - Объект
MovieGenre
, содержащий выбранный жанр. -
SearchString
, содержащий текст, который пользователи вводят в поле поиска.
Замените метод Index
в файле MoviesController.cs
следующим кодом:
// 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);
}
Следующий код определяет запрос LINQ
, который извлекает все жанры из базы данных.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Объект SelectList
со списком жанров создается путем проецирования отдельных жанров (это необходимо, чтобы исключить повторяющиеся жанры).
Когда пользователь выполняет поиск элемента, значение поиска сохраняется в поле поиска.
Добавление поиска по жанру в представление индекса
Обновите файл Index.cshtml
, находящийся в папке Views/Movies/, следующим образом:
@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>
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
В предыдущем коде вспомогательный метод HTML DisplayNameFor
проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Поскольку лямбда-выражение проверяется, а не вычисляется, в том случае, если model
, model.Movies
или model.Movies[0]
имеют значение null
или пусты, не происходит нарушение прав доступа. При вычислении лямбда-выражения (например, @Html.DisplayFor(modelItem => item.Title)
) вычисляются значения для свойств модели.
!
После model.Movies
этого используется оператор, допускающий значение NULL, который используется для объявления, что Movies
не равно null.
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и по обоим этим параметрам:
В этом разделе вы добавите в метод действия Index
возможности поиска, которые позволяют выполнять поиск фильмов по жанру или названию.
Обновите метод, найденный Index
внутри Controllers/MoviesController.cs
, с помощью следующего кода:
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());
}
В первой строке метода действия Index
создается запрос LINQ для выбора фильмов:
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если параметр searchString
содержит строку, запрос фильмов изменяется для фильтрации по значению в строке поиска:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
Приведенный выше код s => s.Title!.Contains(searchString)
представляет собой лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как метод Where или Contains
(используется в приведенном выше коде). Запросы LINQ не выполняются, если они определяются или изменяются путем вызова метода, например Where
, Contains
или OrderBy
. Вместо этого выполнение запроса откладывается. Это означает, что вычисление выражения откладывается до тех пор, пока не будет выполнена итерация его реализованного значения или пока не будет вызван метод ToListAsync
. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.
Примечание. Метод Contains выполняется в базе данных, а не в приведенном выше коде c#. Регистр символов запроса учитывается в зависимости от параметров базы данных и сортировки. В SQL Server метод Contains
сопоставляется с SQL LIKE, в котором регистр символов не учитывается. В SQLite при параметрах сортировки по умолчанию регистр символов учитывается.
Перейдите к /Movies/Index
. Добавьте в URL-адрес строку запроса, например ?searchString=Ghost
. Отображаются отфильтрованные фильмы.
Если изменить сигнатуру Index
метода с именем id
параметра, id
параметр будет соответствовать необязательному {id}
заполнителю для заданных по умолчанию маршрутов Program.cs
.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Измените параметр на id
, а все вхождения searchString
— на id
.
Предыдущий метод 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());
}
Обновленный метод Index
с параметром 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());
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Итак, теперь вам необходимо добавить элементы пользовательского интерфейса для удобства фильтрации фильмов. Если вы изменили сигнатуру метода Index
для тестирования передачи параметра ID
с привязкой к маршруту, измените ее снова, чтобы она снова принимала параметр 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
Откройте файл и добавьте разметку, выделенную <form>
ниже:
@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>
Тег HTML <form>
использует вспомогательную функцию тега Form, чтобы при отправке формы строка фильтра передавалась в действие Index
контроллера movies. Сохраните изменения и протестируйте фильтр.
Вопреки ожиданиям, перегрузка [HttpPost]
для метода Index
отсутствует. Она не нужна, поскольку метод не изменяет состояние приложения и просто выполняет фильтрацию данных.
Можно добавить следующий метод [HttpPost] Index
.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Параметр notUsed
используется для создания перегрузки метода Index
. Это мы обсудим далее в этом учебнике.
При добавлении этого метода вызывающий метод действия будет сопоставлять метод [HttpPost] Index
, а метод [HttpPost] Index
будет выполняться, как показано на рисунке ниже.
Тем не менее при добавлении этой версии [HttpPost]
метода Index
существует ограничение на общую реализацию. Допустим, вам необходимо добавить в закладки конкретный поиск или отправить друзьям ссылку, по которой они могут просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание, что URL-адрес запроса HTTP POST совпадает с URL-адресом запроса GET (localhost:{ПОРТ}/Movies/Index) — в URL-адресе отсутствуют сведения о поиске. Данные строки поиска отправляются на сервер в виде значения поля формы. Вы можете проверить это с помощью средств разработчика для браузера или инструмента Fiddler. На рисунке ниже показаны средства разработчика для браузера Chrome:
В теле запроса отображается параметр поиска и маркер XSRF. Обратите внимание, что, как упоминалось в предыдущем руководстве, вспомогательный элемент тега формы создает маркер антифоргерии XSRF . Поскольку мы не изменяем данные, проверять маркер безопасности в методе контроллера не нужно.
Так как параметр поиска находится в теле запроса, а не в URL-адресе, эти сведения о поиске нельзя добавить в закладки или открыть для общего доступа. Исправьте это, указав запрос, который должен находиться HTTP GET
в Views/Movies/Index.cshtml
файле.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
После отправки поиска URL-адрес содержит строку поискового запроса. Поиск также переносится в метод HttpGet Index
, даже если у вас определен метод HttpPost Index
.
В следующем примере разметки показано изменение тега form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Добавление поиска по жанру
Добавьте следующий класс MovieGenreViewModel
в папку 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; }
}
}
Модель представления фильмов по жанру будет содержать:
- Список фильмов.
- Объект
SelectList
со списком жанров. В этом списке пользователь может выбрать жанр фильма. - Объект
MovieGenre
, содержащий выбранный жанр. -
SearchString
, содержащий текст, который пользователи вводят в поле поиска.
Замените метод Index
в файле MoviesController.cs
следующим кодом:
// 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);
}
Следующий код определяет запрос LINQ
, который извлекает все жанры из базы данных.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Объект SelectList
со списком жанров создается путем проецирования отдельных жанров (это необходимо, чтобы исключить повторяющиеся жанры).
Когда пользователь выполняет поиск элемента, значение поиска сохраняется в поле поиска.
Добавление поиска по жанру в представление индекса
Обновите файл Index.cshtml
, находящийся в папке Views/Movies/, следующим образом:
@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>
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movies[0].Title)
В предыдущем коде вспомогательный метод HTML DisplayNameFor
проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Поскольку лямбда-выражение проверяется, а не вычисляется, в том случае, если model
, model.Movies
или model.Movies[0]
имеют значение null
или пусты, не происходит нарушение прав доступа. При вычислении лямбда-выражения (например, @Html.DisplayFor(modelItem => item.Title)
) вычисляются значения для свойств модели.
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и по обоим этим параметрам:
В этом разделе вы добавите в метод действия Index
возможности поиска, которые позволяют выполнять поиск фильмов по жанру или названию.
Обновите метод, найденный Index
внутри Controllers/MoviesController.cs
, с помощью следующего кода:
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());
}
В первой строке метода действия Index
создается запрос LINQ для выбора фильмов:
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если параметр searchString
содержит строку, запрос фильмов изменяется для фильтрации по значению в строке поиска:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
Приведенный выше код s => s.Title.Contains()
представляет собой лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как метод Where или Contains
(используется в приведенном выше коде). Запросы LINQ не выполняются, если они определяются или изменяются путем вызова метода, например Where
, Contains
или OrderBy
. Вместо этого выполнение запроса откладывается. Это означает, что вычисление выражения откладывается до тех пор, пока не будет выполнена итерация его реализованного значения или пока не будет вызван метод ToListAsync
. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.
Примечание. Метод Contains выполняется в базе данных, а не в приведенном выше коде c#. Регистр символов запроса учитывается в зависимости от параметров базы данных и сортировки. В SQL Server метод Contains сопоставляется с SQL LIKE, в котором регистр символов не учитывается. В SQLite при параметрах сортировки по умолчанию регистр символов учитывается.
Перейдите к /Movies/Index
. Добавьте в URL-адрес строку запроса, например ?searchString=Ghost
. Отображаются отфильтрованные фильмы.
Если изменить сигнатуру Index
метода с именем id
параметра, id
параметр будет соответствовать необязательному {id}
заполнителю для заданных по умолчанию маршрутов Startup.cs
.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Измените параметр на id
, а все вхождения searchString
— на id
.
Предыдущий метод 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());
}
Обновленный метод Index
с параметром 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());
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Итак, теперь вам необходимо добавить элементы пользовательского интерфейса для удобства фильтрации фильмов. Если вы изменили сигнатуру метода Index
для тестирования передачи параметра ID
с привязкой к маршруту, измените ее снова, чтобы она снова принимала параметр 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
Откройте файл и добавьте разметку, выделенную <form>
ниже:
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>
Тег HTML <form>
использует вспомогательную функцию тега Form, чтобы при отправке формы строка фильтра передавалась в действие Index
контроллера movies. Сохраните изменения и протестируйте фильтр.
Вопреки ожиданиям, перегрузка [HttpPost]
для метода Index
отсутствует. Она не нужна, поскольку метод не изменяет состояние приложения и просто выполняет фильтрацию данных.
Можно добавить следующий метод [HttpPost] Index
.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Параметр notUsed
используется для создания перегрузки метода Index
. Это мы обсудим далее в этом учебнике.
При добавлении этого метода вызывающий метод действия будет сопоставлять метод [HttpPost] Index
, а метод [HttpPost] Index
будет выполняться, как показано на рисунке ниже.
Тем не менее при добавлении этой версии [HttpPost]
метода Index
существует ограничение на общую реализацию. Допустим, вам необходимо добавить в закладки конкретный поиск или отправить друзьям ссылку, по которой они могут просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание, что URL-адрес запроса HTTP POST совпадает с URL-адресом запроса GET (localhost:{ПОРТ}/Movies/Index) — в URL-адресе отсутствуют сведения о поиске. Данные строки поиска отправляются на сервер в виде значения поля формы. Вы можете проверить это с помощью средств разработчика для браузера или инструмента Fiddler. На рисунке ниже показаны средства разработчика для браузера Chrome:
В теле запроса отображается параметр поиска и маркер XSRF. Обратите внимание, что, как упоминалось в предыдущем руководстве, вспомогательный элемент тега формы создает маркер антифоргерии XSRF . Поскольку мы не изменяем данные, проверять маркер безопасности в методе контроллера не нужно.
Так как параметр поиска находится в теле запроса, а не в URL-адресе, эти сведения о поиске нельзя добавить в закладки или открыть для общего доступа. Исправьте это, указав запрос, который должен находиться HTTP GET
в Views/Movies/Index.cshtml
файле.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<label>Title: <input type="text" name="SearchString" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
После отправки поиска URL-адрес содержит строку поискового запроса. Поиск также переносится в метод HttpGet Index
, даже если у вас определен метод HttpPost Index
.
В следующем примере разметки показано изменение тега form
:
<form asp-controller="Movies" asp-action="Index" method="get">
Добавление поиска по жанру
Добавьте следующий класс MovieGenreViewModel
в папку 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; }
}
}
Модель представления фильмов по жанру будет содержать:
- Список фильмов.
- Объект
SelectList
со списком жанров. В этом списке пользователь может выбрать жанр фильма. - Объект
MovieGenre
, содержащий выбранный жанр. -
SearchString
, содержащий текст, который пользователи вводят в поле поиска.
Замените метод Index
в файле MoviesController.cs
следующим кодом:
// 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);
}
Следующий код определяет запрос LINQ
, который извлекает все жанры из базы данных.
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Объект SelectList
со списком жанров создается путем проецирования отдельных жанров (это необходимо, чтобы исключить повторяющиеся жанры).
Когда пользователь выполняет поиск элемента, значение поиска сохраняется в поле поиска.
Добавление поиска по жанру в представление индекса
Обновите файл Index.cshtml
, находящийся в папке Views/Movies/, следующим образом:
@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>
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movies[0].Title)
В предыдущем коде вспомогательный метод HTML DisplayNameFor
проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Поскольку лямбда-выражение проверяется, а не вычисляется, в том случае, если model
, model.Movies
или model.Movies[0]
имеют значение null
или пусты, не происходит нарушение прав доступа. При вычислении лямбда-выражения (например, @Html.DisplayFor(modelItem => item.Title)
) вычисляются значения для свойств модели.
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и по обоим этим параметрам:
ASP.NET Core