Изучение методов Edit и представления Edit (C#)

Рик Андерсон

Примечание

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

В этом руководстве описаны основы создания веб-приложения ASP.NET MVC с помощью Microsoft Visual Web Developer 2010 Express с пакетом обновления 1 (SP1), который является бесплатной версией Microsoft Visual Studio. Перед началом работы убедитесь, что вы установили необходимые компоненты, перечисленные ниже. Вы можете установить все из них, щелкнув следующую ссылку: Установщик веб-платформы. Кроме того, предварительные требования можно установить по отдельности, используя следующие ссылки:

Если вы используете Visual Studio 2010 вместо Visual Web Developer 2010, установите необходимые компоненты, щелкнув следующую ссылку: Предварительные требования Для Visual Studio 2010.

К этому разделу можно ознакомиться с проектом Visual Web Developer с исходным кодом C#. Скачайте версию C#. Если вы предпочитаете Visual Basic, переключитесь на версию этого руководства на Visual Basic .

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

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

EditLink_sm

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

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

Html.ActionLink

Объект Html является вспомогательным объектом, предоставляемым с помощью свойства WebViewPage базового класса. Метод ActionLink вспомогательного средства упрощает динамическое создание html-гиперссылок, ссылающихся на методы действий на контроллерах. Первым аргументом ActionLink метода является текст ссылки для отрисовки (например, <a>Edit Me</a>). Второй аргумент — это имя вызываемого метода действия. Последним аргументом является анонимный объект , который создает данные маршрута (в данном случае идентификатор 4).

Созданная ссылка, показанная на предыдущем изображении, — http://localhost:xxxxx/Movies/Edit/4. Маршрут по умолчанию принимает шаблон {controller}/{action}/{id}URL-адреса . Таким образом, ASP.NET преобразуется http://localhost:xxxxx/Movies/Edit/4 в запрос к методу Edit действия контроллера Movies с параметром ID , равным 4.

Вы также можете передать параметры метода действия с помощью строки запроса. Например, URL-адрес http://localhost:xxxxx/Movies/Edit?ID=4 также передает параметр ID 4 методу Edit действия контроллера Movies .

EditQueryString

Movies Откройте контроллер. Ниже приведены два Edit метода действия.

//
// GET: /Movies/Edit/5

public ActionResult Edit(int id) 
{
    Movie movie = db.Movies.Find(id);
    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 к первому методу edit, но это необязательно, так как он используется по умолчанию. (Мы будем называть методы действий, которым атрибут неявно назначается HttpGet как HttpGet методы.)

Метод HttpGetEdit принимает параметр movie ID, ищет фильм с помощью метода Entity Framework Find и возвращает выбранный фильм в представление Правка. Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie и создает код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано созданное представление Правка:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@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>

Обратите внимание, что шаблон представления имеет @model MvcMovie.Models.Movie оператор в верхней части файла. Это указывает, что представление ожидает, что модель для шаблона представления будет иметь тип Movie.

Шаблонный код использует несколько вспомогательных методов для упрощения разметки HTML. В Html.LabelFor вспомогательном средстве отображается имя поля ("Title", "ReleaseDate", "Genre" или "Price"). Во Html.EditorFor вспомогательном элементе отображается элемент HTML <input> . Вспомогательное Html.ValidationMessageFor средство отображает все сообщения проверки, связанные с этим свойством.

Запустите приложение и перейдите по URL-адресу /Movies . Щелкните ссылку Edit (Изменить). Просмотрите исходный код страницы в окне браузера. HTML-код на странице выглядит так, как показано в следующем примере. (Разметка меню была исключена для ясности.)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Edit</title>
    <link href="/Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>MVC Movie App</h1>
            </div>
           ...
        </header>
        <section id="main">

<h2>Edit</h2>

<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

<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-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="9.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>
<div>
    <a href="/Movies">Back to List</a>
</div>

        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

Элементы <input> находятся в элементе HTML <form> , атрибут которого action имеет значение post в 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);
}

Связыватель модели платформы ASP.NET принимает опубликованные значения формы и создает Movie объект, передаваемый в movie качестве параметра . Проверка ModelState.IsValid в коде проверяет, можно ли использовать данные, отправленные в форме, для изменения Movie объекта. Если данные допустимы, код сохраняет данные фильма в Movies коллекции экземпляра MovieDBContext . Затем код сохраняет данные нового фильма в базе данных путем вызова SaveChanges метода MovieDBContext, который сохраняет изменения в базе данных. После сохранения данных код перенаправляет пользователя к методу IndexMoviesController action класса , что приводит к отображению обновленного фильма в списке фильмов.

Если опубликованные значения недопустимы, они повторно отображаются в форме. Вспомогательные Html.ValidationMessageFor функции в шаблоне представления Edit.cshtml помогают отображать соответствующие сообщения об ошибках.

abcNotValid

Примечание о языковых стандартах Если вы обычно работаете с языковым стандартом, отличным от английского, см. раздел Поддержка проверки ASP.NET MVC 3 с языковыми стандартами, не на английском языке.

Обеспечение надежности метода Edit

МетодHttpGetEdit, созданный системой формирования шаблонов, не проверка, что переданный ему идентификатор является допустимым. Если пользователь удаляет сегмент идентификатора из URL-адреса (http://localhost:xxxxx/Movies/Edit), отображается следующая ошибка:

Null_ID

Пользователь также может передать идентификатор, который не существует в базе данных, например http://localhost:xxxxx/Movies/Edit/1234. Чтобы устранить это ограничение, HttpGetEdit в метод действия можно внести два изменения. Сначала измените ID параметр на нулевое значение по умолчанию, если идентификатор не передается явным образом. Вы также можете проверка, что Find метод на самом деле нашел фильм, прежде чем возвращать объект movie в шаблон представления. Обновленный Edit метод показан ниже.

public ActionResult Edit(int id = 0)
{
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

Если фильм не найден, HttpNotFound вызывается метод .

Все методы HttpGet следуют аналогичному шаблону. Они получают объект фильма (или список объектов, в случае Index) и передают модель в представление. Метод Create передает пустой объект movie в представление Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода HttpPost. Изменение данных в методе HTTP GET представляет угрозу безопасности, как описано в записи блога ASP.NET совет MVC No 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 Если параметр содержит строку, запрос movies изменяется для фильтрации по значению строки поиска с использованием следующего кода:

if (!String.IsNullOrEmpty(searchString))
{
    movies = movies.Where(s => s.Title.Contains(searchString));
}

Запросы LINQ не выполняются при их определении или изменении путем вызова метода, Where например или OrderBy. Вместо этого выполнение запроса откладывается, что означает, что вычисление выражения откладывается до фактического перебора его реализованного значения или ToList вызова метода. SearchIndex В примере запрос выполняется в представлении SearchIndex. Дополнительные сведения об отложенном и немедленном выполнении запросов см. в разделе Выполнение запроса.

Теперь можно реализовать SearchIndex представление, которое будет отображать форму для пользователя. Щелкните правой SearchIndex кнопкой мыши внутри метода и выберите команду Добавить представление. В диалоговом окне Добавление представления укажите, что вы собираетесь передать Movie объект в шаблон представления в качестве класса модели. В списке Шаблон шаблонов выберите Список, а затем нажмите кнопку Добавить.

AddSearchView

При нажатии кнопки Добавить создается шаблон представления Views\Movies\SearchIndex.cshtml . Так как вы выбрали Список в списке шаблонов шаблонов, Visual Web Developer автоматически сгенерировал (с формированием шаблонов) некоторое содержимое по умолчанию в представлении. Формирование шаблонов создало 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 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") 
         <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 функция отправляет форму в себя, когда пользователь отправляет форму, нажимая кнопку Фильтр .

Запустите приложение и попробуйте найти фильм.

SearchIndxIE9_title

Перегрузка метода отсутствует HttpPostSearchIndex . Он не нужен, так как метод не изменяет состояние приложения, а просто фильтрует данные.

Можно добавить следующий метод HttpPost SearchIndex. В этом случае вызов действия будет соответствовать методу HttpPost SearchIndexHttpPost 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()){   
         <p>Genre: @Html.DropDownList("movieGenre", "All")  
           Title: @Html.TextBox("SearchString")  
         <input type="submit" value="Filter" /></p>
        }
</p>

Запустите приложение и перейдите по папке /Movies/SearchIndex. Попробуйте выполнить поиск по жанру, по имени фильма и по обоим критериям.

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