Поделиться через


Изучение методов действий и представлений для контроллера фильма

Рик Андерсон

Примечание.

Обновленная версия этого руководства доступна здесь , где используется ASP.NET MVC 5 и Visual Studio 2013. Это более безопасно, гораздо проще следовать и демонстрирует больше функций.

В этом разделе вы изучите созданные методы действий и представления для контроллера фильма. Затем вы добавите настраиваемую страницу поиска.

Запустите приложение и перейдите к Movies контроллеру, добавив /Movies к URL-адресу в адресной строке браузера. Удерживайте указатель мыши на ссылку "Изменить ", чтобы просмотреть URL-адрес, на который он ссылается.

EditLink_sm

Ссылка "Изменить" была создана методом Html.ActionLink в представлении Views\Movies\Index.cshtml:

@Html.ActionLink("Edit", "Edit", new { id=item.ID })

Html.ActionLink

Объект 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 .

EditQueryString

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 заботятся о отображении соответствующих сообщений об ошибках.

abcNotValid

Примечание.

для поддержки проверки 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 представляет собой риск безопасности, как описано в записи записи блога ASP.NET подсказке MVC #46. Не используйте ссылки удаления, так как они создают дыры безопасности. Изменение данных в методе 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 шаблону представления в качестве класса модели. В списке шаблонов шаблонов шаблонов выберите "Список", а затем нажмите кнопку "Добавить".

AddSearchView

При нажатии кнопки "Добавить" создается шаблон представления 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. Отображаются отфильтрованные фильмы.

SearchQryStr

Если изменить сигнатуру 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-адреса) вместо значения строки запроса.

SearchRouteData

Тем не менее пользователи вряд ли будут каждый раз изменять 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>"; 
}

SearchPostGhost

Тем не менее при добавлении этой версии 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))

BeginFormPost_SM

Теперь при отправке поиска URL-адрес содержит строку запроса поиска. Поиск также переносится в метод HttpGet SearchIndex, даже если у вас определен метод HttpPost SearchIndex.

SearchIndexWithGetURL

Добавление поиска по жанру

Если вы добавили 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 модель и как добавить инициализатор, который автоматически создаст тестовую базу данных.