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

Рик Андерсон

В этом руководстве описаны основы создания веб-приложения 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 с VB.NET исходным кодом. Скачайте версию VB.NET. Если вы предпочитаете C#, переключитесь на версию этого руководства на C# .

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

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

Снимок экрана: приложение MVC Move Со ссылкой

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

@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.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

Function Edit(id As Integer) As ViewResult
    Dim movie As Movie = db.Movies.Find(id)
    Return View(movie)
End Function

'
' POST: /Movies/Edit/5

<HttpPost()>
Function Edit(movie As Movie) As ActionResult
    If ModelState.IsValid Then
        db.Entry(movie).State = EntityState.Modified
        db.SaveChanges()
        Return RedirectToAction("Index")
    End If

    Return View(movie)
End Function

Обратите внимание на второй метод действия Edit, которому предшествует атрибут HttpPost. Этот атрибут указывает, что перегрузку Edit метода можно вызывать только для запросов POST. Атрибут можно применить HttpGet к первому методу edit, но это необязательно, так как он используется по умолчанию. (Мы будем называть методы действий, которым атрибут неявно назначается HttpGet как HttpGet методы.)

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

@ModelType MvcMovie.Movie

@Code
    ViewData("Title") = "Edit"
End Code

<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(Function(model) model.ID)

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.Title)
            @Html.ValidationMessageFor(Function(model) model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.ReleaseDate)
            @Html.ValidationMessageFor(Function(model) model.ReleaseDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.Genre)
            @Html.ValidationMessageFor(Function(model) model.Genre)
        </div>

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.Price)
            @Html.ValidationMessageFor(Function(model) model.Price)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
End Using

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Обратите внимание, что шаблон представления имеет @ModelType 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.

'
' POST: /Movies/Edit/5

<HttpPost()>
Function Edit(movie As Movie) As ActionResult
    If ModelState.IsValid Then
        db.Entry(movie).State = EntityState.Modified
        db.SaveChanges()
        Return RedirectToAction("Index")
    End If

    Return View(movie)
End Function

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

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

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 Function Edit(Optional ByVal id As Integer = 0) As ActionResult
    Dim movie As Movie = db.Movies.Find(id)
    If movie Is Nothing Then
        Return HttpNotFound()
    End If
    Return View(movie)
End Function

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

Все методы HttpGet следуют аналогичному шаблону. Они получают объект фильма (или список объектов, в случае Index) и передают модель в представление. Метод Create передает пустой объект movie в представление Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода HttpPost. Изменение данных в методе HTTP GET представляет угрозу безопасности, как описано в записи блога ASP.NET совет MVC No 46. Не используйте удаление ссылок, так как они создают брели в системе безопасности. Изменение данных в методе GET также нарушает рекомендации по HTTP и архитектурный шаблон REST, который указывает, что запросы GET не должны изменять состояние приложения. Иными словами, выполнение операции GET должно быть безопасной операцией без побочных эффектов.

Добавление метода поиска и представления поиска

В этом разделе вы добавите SearchIndex метод действия, который позволяет искать фильмы по жанру или имени. Это будет доступно по URL-адресу /Movies/SearchIndex . В запросе отобразится HTML-форма, содержащая входные элементы, которые пользователь может заполнить для поиска фильма. Когда пользователь отправляет форму, метод действия получает значения поиска, опубликованные пользователем, и использует их для поиска в базе данных.

SearchIndx_SM

Отображение формы SearchIndex

Начните с SearchIndex добавления метода действия в существующий MoviesController класс. Метод возвращает представление, содержащее HTML-форму. Вот этот код:

Public Function SearchIndex(ByVal searchString As String) As ActionResult
    Dim movies = From m In db.Movies
                 Select m 

    If Not String.IsNullOrEmpty(searchString) Then 
        movies = movies.Where(Function(s) s.Title.Contains(searchString)) 
    End If
    Return View(movies) 
End Function

В первой строке SearchIndex метода создается следующий запрос LINQ для выбора фильмов:

Dim movies = From m In db.Movies    Select m

На этом этапе запрос определен, но еще не был выполнен в хранилище данных.

searchString Если параметр содержит строку, запрос movies изменяется для фильтрации по значению строки поиска с использованием следующего кода:

If Not String.IsNullOrEmpty(searchString) Then
movies = фильмы. Where(Function(s) s.Title.Contains(searchString))
Окончание при

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

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

AddSearchView

При нажатии кнопки Добавить создается шаблон представления Views\Movies\SearchIndex.vbhtml . Так как вы выбрали Список в списке шаблонов , Visual Web Developer автоматически создает (формирует шаблон) содержимое по умолчанию в представлении. Формирование шаблонов создало HTML-форму. Он изучил Movie класс и создал код для отрисовки <label> элементов для каждого свойства класса. В приведенном ниже списке показано созданное представление Создания:

@ModelType IEnumerable(Of MvcMovie.Movie)

@Code
    ViewData("Title") = "SearchIndex"
End Code

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

@For Each item In Model
    Dim currentItem = item
    @<tr>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Title)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.ReleaseDate)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Genre)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Price)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |
            @Html.ActionLink("Details", "Details", New With {.id = currentItem.ID}) |
            @Html.ActionLink("Delete", "Delete", New With {.id = currentItem.ID})
        </td>
    </tr>
Next

</table>

Запустите приложение и перейдите в папку /Movies/SearchIndex. Добавьте в URL-адрес строку запроса, например ?searchString=ghost. Отображаются отфильтрованные фильмы.

SearchQryStr

Если изменить сигнатуру SearchIndex метода на параметр с именем id, id параметр будет соответствовать {id} заполнителю для маршрутов по умолчанию, заданных в файле Global.asax .

{controller}/{action}/{id}

Измененный SearchIndex метод будет выглядеть следующим образом:

Public Function SearchIndex(ByVal id As String) As ActionResult
Dim searchString As String = id
Dim movies = From m In db.Movies
             Select m

If Not String.IsNullOrEmpty(searchString) Then
    movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If

Return View(movies)
End Function

Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент URL-адреса) вместо значения строки запроса.

SearchRouteData

Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для поиска фильмов. Теперь вы добавите пользовательский интерфейс, чтобы помочь им фильтровать фильмы. Если вы изменили сигнатуру SearchIndex метода для проверки способа передачи параметра идентификатора, привязанного к маршруту, измените его обратно, чтобы SearchIndex метод принимает строковый параметр с именем searchString:

Откройте файл Views\Movies\SearchIndex.vbhtml и сразу после @Html.ActionLink("Create New", "Create")добавьте следующее:

@Code
    ViewData("Title") = "SearchIndex"
    Using (Html.BeginForm())
         @<p> Title: @Html.TextBox("SearchString") 
         <input type="submit" value="Filter" /></p>
        End Using
End Code

Вспомогатель Html.BeginForm создает открывающий <form> тег. Вспомогающая Html.BeginForm функция приводит к публикации формы, когда пользователь отправляет форму, нажимая кнопку Фильтр .

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

SearchIndxIE9_title

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

<HttpPost()>
 Public Function SearchIndex(ByVal fc As FormCollection, ByVal searchString As String) As String
     Return "<h3> From [HttpPost]SearchIndex: " & searchString & "</h3>"
 End Function

SearchPostGhost

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

Если вы добавили HttpPost версию SearchIndex метода, удалите ее сейчас.

Далее вы добавите функцию, которая позволит пользователям искать фильмы по жанру. Замените метод SearchIndex следующим кодом:

Public Function SearchIndex(ByVal movieGenre As String, ByVal searchString As String) As ActionResult
    Dim GenreLst = New List(Of String)()

    Dim GenreQry = From d In db.Movies
                   Order By d.Genre
                   Select d.Genre
    GenreLst.AddRange(GenreQry.Distinct())
    ViewBag.movieGenre = New SelectList(GenreLst)

    Dim movies = From m In db.Movies
                 Select m

    If Not String.IsNullOrEmpty(searchString) Then
        movies = movies.Where(Function(s) s.Title.Contains(searchString))
    End If

    If String.IsNullOrEmpty(movieGenre) Then
        Return View(movies)
    Else
        Return View(movies.Where(Function(x) x.Genre = movieGenre))
    End If

End Function

Эта версия SearchIndex метода принимает дополнительный параметр, а именно movieGenre. Первые несколько строк кода создают List объект для хранения жанров фильмов из базы данных.

Следующий код определяет запрос LINQ, который извлекает все жанры из базы данных.

Dim GenreQry = From d In db.Movies
                   Order By d.Genre
                   Select d.Genre

Код использует AddRange метод универсальной List коллекции для добавления всех различных жанров в список. (Без модификатора Distinct будут добавлены дублирующиеся жанры— например, комедия будет добавлена дважды в нашем примере). Затем код сохраняет список жанров в объекте ViewBag .

В следующем коде показано, как проверка movieGenre параметр . Если он не пуст, код дополнительно ограничивает запрос фильмов, чтобы ограничить выбранные фильмы указанным жанром.

If String.IsNullOrEmpty(movieGenre) Then
        Return View(movies)
    Else
        Return View(movies.Where(Function(x) x.Genre = movieGenre))
    End If

Добавление разметки в представление SearchIndex для поддержки поиска по жанрам

Добавьте вспомогатель Html.DropDownList в файл Views\Movies\SearchIndex.vbhtml непосредственно перед вспомогательным файлом TextBox . Завершенная разметка показана ниже:

<p>
    @Html.ActionLink("Create New", "Create")
    @Code
    ViewData("Title") = "SearchIndex"
    Using (Html.BeginForm())
         @<p> Genre: @Html.DropDownList("movieGenre", "All")
         Title: @Html.TextBox("SearchString") 
         <input type="submit" value="Filter" /></p>
        End Using
End Code
</p>

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

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