Изучение методов действий и представлений для контроллера фильма
Примечание.
Обновленная версия этого руководства доступна здесь , где используется ASP.NET MVC 5 и Visual Studio 2013. Это более безопасно, гораздо проще следовать и демонстрирует больше функций.
В этом разделе вы изучите созданные методы действий и представления для контроллера фильма. Затем вы добавите настраиваемую страницу поиска.
Запустите приложение и перейдите к Movies
контроллеру, добавив /Movies к URL-адресу в адресной строке браузера. Удерживайте указатель мыши на ссылку "Изменить ", чтобы просмотреть URL-адрес, на который он ссылается.
Ссылка "Изменить" была создана методом Html.ActionLink
в представлении Views\Movies\Index.cshtml:
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
Объект Html
является вспомогательным объектом, который предоставляется с помощью свойства в базовом классе System.Web.Mvc.WebViewPage . Метод ActionLink
вспомогательного средства упрощает динамическое создание гиперссылок HTML, которые связываются с методами действий на контроллерах. Первым аргументом метода ActionLink
является текст ссылки для отрисовки (например, <a>Edit Me</a>
). Второй аргумент — это имя вызываемого метода действия. Последним аргументом является анонимный объект , который создает данные маршрута (в данном случае — идентификатор 4).
Созданная ссылка, показанная на предыдущем изображении http://localhost:xxxxx/Movies/Edit/4
. Маршрут по умолчанию (установленный в App_Start\RouteConfig.cs) принимает шаблон {controller}/{action}/{id}
URL-адреса. Таким образом, ASP.NET преобразуется http://localhost:xxxxx/Movies/Edit/4
в запрос Edit
к методу действия контроллера Movies
с параметром ID
, равным 4. Изучите следующий код из файла App_Start\RouteConfig.cs .
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
Можно также передать параметры метода действия с помощью строки запроса. Например, URL-адрес http://localhost:xxxxx/Movies/Edit?ID=4
также передает параметр ID
4 Edit
методу действия контроллера Movies
.
Movies
Откройте контроллер. Ниже показаны два Edit
метода действия.
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Обратите внимание на второй метод действия Edit
, которому предшествует атрибут HttpPost
. Этот атрибут указывает, что перегрузка Edit
метода может вызываться только для запросов POST. Атрибут можно применить HttpGet
к первому методу редактирования, но это не обязательно, так как это значение по умолчанию. (Мы будем ссылаться на методы действий, неявно назначенные атрибуту HttpGet
в качестве HttpGet
методов.)
Метод HttpGet
Edit
принимает параметр идентификатора фильма, ищет фильм с помощью метода Entity Framework Find
и возвращает выбранный фильм в представление "Изменить". Параметр ID задает значение по умолчанию, равное нулю, если Edit
метод вызывается без параметра. Если не удается найти фильм, возвращается httpNotFound . Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie
и создает код для отображения элементов <label>
и <input>
для каждого свойства класса. В следующем примере показано представление "Изменить", созданное:
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Обратите внимание, что шаблон представления содержит @model MvcMovie.Models.Movie
инструкцию в верхней части файла. Это указывает, что представление ожидает, что модель для шаблона представления будет типом Movie
.
Шаблонный код использует несколько вспомогательных методов для упрощения разметки HTML. Вспомогателя Html.LabelFor
отображается имя поля ("Title", "ReleaseDate", "Жанр" или "Цена"). Вспомогательный Html.EditorFor
элемент отображает элемент HTML <input>
. Вспомогательный Html.ValidationMessageFor
элемент отображает все сообщения проверки, связанные с этим свойством.
Запустите приложение и перейдите по URL-адресу /Movies . Щелкните ссылку Edit (Изменить). Просмотрите исходный код страницы в окне браузера. Html-код для элемента формы показан ниже.
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="2.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
Элементы <input>
находятся в html-элементе <form>
, атрибут которого action
задается для записи в URL-адрес /Movies/Edit . Данные формы будут размещены на сервере при нажатии кнопки "Изменить ".
Обработка запроса POST
В следующем листинге демонстрируется версия HttpPost
метода действия Edit
.
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Привязка модели MVC ASP.NET принимает опубликованные значения формы и создает Movie
объект, переданный в качестве movie
параметра. Метод ModelState.IsValid
проверяет, можно ли использовать переданные в форме данные для изменения (редактирования или обновления) объекта Movie
. Если данные допустимы, данные фильма сохраняются в Movies
коллекции экземпляра db(MovieDBContext
). Новые данные фильма сохраняются в базе данных путем SaveChanges
вызова метода MovieDBContext
. После сохранения данных код перенаправляет пользователя в Index
метод MoviesController
действия класса, который отображает коллекцию фильмов, включая только что внесенные изменения.
Если опубликованные значения недопустимы, они переиграются в форме. Вспомогательные Html.ValidationMessageFor
элементы в шаблоне представления Edit.cshtml заботятся о отображении соответствующих сообщений об ошибках.
Примечание.
для поддержки проверки jQuery для языковых стандартов, отличных от английского языка, использующих запятую (",") для десятичной запятой, необходимо включить globalize.js и определенные язык и региональные параметры/globalize.cultures.js(из https://github.com/jquery/globalize ) и JavaScript для использованияGlobalize.parseFloat
. В следующем коде показаны изменения файла Views\Movies\Edit.cshtml для работы с языком и региональными параметрами fr-FR:
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
Десятичное поле может требовать запятую, а не десятичную точку. В качестве временного исправления можно добавить элемент глобализации в корневой файл конфигурации проекта web.config. В следующем коде показан элемент глобализации с языком и региональными параметрами, заданными для США английского языка.
<system.web>
<globalization culture ="en-US" />
<!--elements removed for clarity-->
</system.web>
HttpGet
Все методы соответствуют аналогичному шаблону. Они получают объект фильма (или список объектов в случае Index
) и передают модель в представление. Метод Create
передает пустой объект фильма в представление Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода HttpPost
. Изменение данных в методе HTTP GET представляет собой риск безопасности. Изменение данных в методе GET также нарушает рекомендации ПО HTTP и шаблон REST архитектуры, который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.
Добавление метода поиска и представления поиска
В этом разделе вы добавите SearchIndex
метод действия, позволяющий искать фильмы по жанру или имени. Это будет доступно с помощью URL-адреса /Movies/SearchIndex . Запрос отобразит HTML-форму, содержащую входные элементы, которые пользователь может ввести для поиска фильма. Когда пользователь отправляет форму, метод действия получит значения поиска, опубликованные пользователем, и используйте значения для поиска в базе данных.
Отображение формы SearchIndex
Начните с добавления метода действия в существующий SearchIndex
MoviesController
класс. Метод возвращает представление, содержащее HTML-форму. Вот этот код:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Первая строка SearchIndex
метода создает следующий запрос LINQ , чтобы выбрать фильмы:
var movies = from m in db.Movies
select m;
Запрос определен на этом этапе, но еще не был запущен в хранилище данных.
searchString
Если параметр содержит строку, запрос фильмов изменяется для фильтрации по значению строки поиска, используя следующий код:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
Приведенный выше код s => s.Title
представляет собой лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе методов на основе методов LINQ в качестве аргументов стандартных методов оператора запросов, таких как метод Where , используемый в приведенном выше коде. Запросы LINQ не выполняются, когда они определены или изменяются путем вызова метода, Where
например или OrderBy
. Вместо этого выполнение запроса отложено, что означает, что оценка выражения задерживается до тех пор, пока не будет фактически итерировано значение или ToList
вызывается метод. SearchIndex
В примере запрос выполняется в представлении SearchIndex. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.
Теперь вы можете реализовать SearchIndex
представление, отображающее форму пользователю. Щелкните правой кнопкой мыши внутри SearchIndex
метода и нажмите кнопку "Добавить представление". В диалоговом окне "Добавление представления" укажите, что объект будет передаваться Movie
шаблону представления в качестве класса модели. В списке шаблонов шаблонов шаблонов выберите "Список", а затем нажмите кнопку "Добавить".
При нажатии кнопки "Добавить" создается шаблон представления Views\Movies\SearchIndex.cshtml. Так как вы выбрали список в списке шаблонов шаблонов, Visual Studio автоматически создает (шаблон) некоторые разметки по умолчанию в представлении. Формирование шаблонов создало HTML-форму. Он изучил Movie
класс и создал код для отрисовки <label>
элементов для каждого свойства класса. В приведенном ниже списке показано представление "Создать", созданное:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<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>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
Запустите приложение и перейдите к /Movies/SearchIndex. Добавьте в URL-адрес строку запроса, например ?searchString=ghost
. Отображаются отфильтрованные фильмы.
Если изменить сигнатуру SearchIndex
метода с именем id
параметра, id
параметр будет соответствовать {id}
заполнителю маршрутов по умолчанию, заданным в файле Global.asax .
{controller}/{action}/{id}
Исходный SearchIndex
метод выглядит следующим образом:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Измененный SearchIndex
метод будет выглядеть следующим образом:
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Теперь вы добавите пользовательский интерфейс, чтобы помочь им фильтровать фильмы. Если вы изменили сигнатуру метода, чтобы проверить, как передать параметр идентификатора SearchIndex
, привязанного к маршруту, измените его обратно, чтобы SearchIndex
метод принимает строковый параметр с именем searchString
:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Откройте файл Views\Movies\SearchIndex.cshtml и сразу после @Html.ActionLink("Create New", "Create")
этого добавьте следующее:
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" /></p>
}
В следующем примере показана часть файла Views\Movies\SearchIndex.cshtml с добавленной разметкой фильтрации.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
Вспомогательный Html.BeginForm
элемент создает открывающий <form>
тег. Вспомогательный Html.BeginForm
элемент вызывает отправку формы, когда пользователь отправляет форму, нажав кнопку "Фильтр ".
Запустите приложение и попробуйте найти фильм.
Нет HttpPost
перегрузки SearchIndex
метода. Он не нужен, так как метод не изменяет состояние приложения, просто фильтруя данные.
Можно добавить следующий метод HttpPost SearchIndex
. В этом случае вызывающий объект действия будет соответствовать HttpPost SearchIndex
методу, и HttpPost SearchIndex
метод будет выполняться, как показано на рисунке ниже.
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
Тем не менее при добавлении этой версии HttpPost
метода SearchIndex
существует ограничение на общую реализацию. Допустим, вам необходимо добавить в закладки конкретный поиск или отправить друзьям ссылку, по которой они могут просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание, что URL-адрес HTTP-запроса POST совпадает с URL-адресом запроса GET (localhost:xxxxx/Movies/SearchIndex) — в самом URL-адресе нет сведений об поиске. Сейчас данные строки поиска отправляются на сервер в качестве значения поля формы. Это означает, что вы не можете записывать данные поиска для закладки или отправки друзьям в URL-адресе.
Решение заключается в том, чтобы использовать перегрузку BeginForm
, указывающую, что запрос POST должен добавлять данные поиска в URL-адрес и направлять его в версию SearchIndex
HttpGet метода. Замените существующий метод без BeginForm
параметров следующим образом:
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
Теперь при отправке поиска URL-адрес содержит строку запроса поиска. Поиск также переносится в метод HttpGet SearchIndex
, даже если у вас определен метод HttpPost SearchIndex
.
Добавление поиска по жанру
Если вы добавили HttpPost
версию SearchIndex
метода, удалите его сейчас.
Затем вы добавите функцию, чтобы пользователи искали фильмы по жанру. Замените метод SearchIndex
следующим кодом:
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
Эта версия метода принимает дополнительный SearchIndex
параметр, а именно movieGenre
. Первые несколько строк кода создают List
объект для хранения жанров фильмов из базы данных.
Следующий код определяет запрос LINQ, который извлекает все жанры из базы данных.
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
Код использует AddRange
метод универсальной List
коллекции для добавления всех различных жанров в список. (Без Distinct
модификатора будут добавлены повторяющиеся жанры — например, комедия будет добавлена дважды в нашем примере). Затем код сохраняет список жанров в объекте ViewBag
.
В следующем коде показано, как проверить movieGenre
параметр. Если это не пусто, код дополнительно ограничивает запрос фильмов, чтобы ограничить выбранные фильмы указанным жанром.
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
Добавление разметки в представление SearchIndex для поддержки поиска по жанру
Добавьте вспомогательный Html.DropDownList
элемент в файл Views\Movies\SearchIndex.cshtml перед вспомогательным файлом TextBox
. Завершенная разметка показана ниже:
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
Запустите приложение и перейдите к /Movies/SearchIndex. Попробуйте выполнить поиск по жанру, по имени фильма и по обоим критериям.
В этом разделе вы изучили методы и представления действий CRUD, созданные платформой. Вы создали метод действия поиска и представление, позволяющее пользователям выполнять поиск по названию фильма и жанру. В следующем разделе вы узнаете, как добавить свойство в Movie
модель и как добавить инициализатор, который автоматически создаст тестовую базу данных.