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

Рик Андерсон

Примечание

Обновленная версия этого руководства доступна здесь , используя последнюю версию Visual Studio. В новом руководстве используется ASP.NET Core MVC, который предоставляет множество улучшений по сравнению с этим руководством.

В этом руководстве описывается модель MVC ASP.NET Core с контроллерами и представлениями. Razor Pages — это новая альтернатива в ASP.NET Core, модель программирования на основе страниц, которая упрощает и повышает эффективность создания пользовательского веб-интерфейса. Мы рекомендуем ознакомиться с руководством по Razor Pages до версии MVC. Руководство по Razor Pages:

  • проще для выполнения;
  • охватывает дополнительные возможности;
  • Предпочтительный подход к разработке новых приложений.

В этом разделе вы изучите созданные Edit методы действий и представления для контроллера фильма. Но сначала мы сделаем небольшой диверсии, чтобы дата выпуска выглядела лучше. Откройте файл Models\Movie.cs и добавьте выделенные строки, показанные ниже:

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

Вы также можете указать язык и региональные параметры даты следующим образом:

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }

Пространство имен DataAnnotations будет рассмотрено в следующем учебнике. Атрибут Display определяет отображаемое имя поля (в этом случае "Release Date" вместо "ReleaseDate"). Атрибут DataType указывает тип данных, в данном случае это дата, поэтому сведения о времени, хранящиеся в поле, не отображаются. Атрибут DisplayFormat необходим для ошибки в браузере Chrome, которая неправильно отображает форматы даты.

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

EditLink_sm

Ссылка Edit была создана методом 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>). Второй аргумент — это имя вызываемого метода действия (в данном случае Edit это действие). Последним аргументом является анонимный объект , который создает данные маршрута (в данном случае — идентификатор 4).

Созданная ссылка, показанная на предыдущем изображении, — http://localhost:1234/Movies/Edit/4. Маршрут по умолчанию (установленный в App_Start\RouteConfig.cs) принимает шаблон {controller}/{action}/{id}URL-адреса . Таким образом, ASP.NET преобразуется http://localhost:1234/Movies/Edit/4 в запрос к методу Edit действия контроллера Movies с параметром ID , равным 4. Изучите следующий код из файла App_Start\RouteConfig.cs . Метод MapRoute используется для маршрутизации HTTP-запросов к правильному контроллеру и методу действия и предоставления необязательного параметра ID. Метод MapRoute также используется HtmlHelpers , например ActionLink для создания URL-адресов с учетом контроллера, метода действия и любых данных маршрута.

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:1234/Movies/Edit?ID=3 также передает параметр ID 3 методу Edit действия контроллера Movies .

EditQueryString

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

// GET: /Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

// POST: /Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] 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 методы.) Атрибут Bind — это еще один важный механизм безопасности, который предотвращает чрезмерное размещение данных в вашей модели. В атрибут привязки следует включать только свойства, которые требуется изменить. Дополнительные сведения о переуступе и атрибуте привязки см. в примечании к безопасности о превышении. В простой модели, используемой в этом руководстве, мы привязаем все данные в модели. Атрибут ValidateAntiForgeryToken используется для предотвращения подделки запроса и связывается с @Html.AntiForgeryToken() в файле представления редактирования (Views\Movies\Edit.cshtml), часть показана ниже:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

@Html.AntiForgeryToken() создает скрытый маркер защиты от подделки, который должен совпадать в Edit методе контроллера Movies . Дополнительные сведения о подделке межсайтовых запросов (также известных как XSRF или CSRF) см. в руководстве по предотвращению XSRF/CSRF в MVC.

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

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate)
                @Html.ValidationMessageFor(model => model.ReleaseDate)
            </div>
        </div>
        @*Genre and Price removed for brevity.*@        
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

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

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

Запустите приложение и перейдите по URL-адресу /Movies . Щелкните ссылку Edit (Изменить). Просмотрите исходный код страницы в окне браузера. Html-код для элемента формы показан ниже.

<form action="/movies/Edit/4" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <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="control-group">
         <label class="control-label" for="Title">Title</label>
         <div class="controls">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="ReleaseDate">Release Date</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" />
            <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Genre">Genre</label>
         <div class="controls">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Price">Price</label>
         <div class="controls">
            <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="7.99" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>

Элементы <input> находятся в ЭЛЕМЕНТе HTML <form> , атрибут которого action имеет значение post в URL-адрес /Movies/Edit . Данные формы будут отправляться на сервер при нажатии кнопки Сохранить . Во второй строке показан скрытый токен XSRF , созданный вызовом @Html.AntiForgeryToken() .

Обработка запроса POST

В следующем листинге демонстрируется версия HttpPost метода действия Edit.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

Атрибут ValidateAntiForgeryToken проверяет токен XSRF , созданный вызовом @Html.AntiForgeryToken() в представлении.

Связыватель модели MVC ASP.NET принимает опубликованные значения формы и создает Movie объект , который передается в movie качестве параметра. проверяет ModelState.IsValid , можно ли использовать данные, отправленные в форме, для изменения (изменения или обновления) Movie объекта. Если данные допустимы, данные фильма сохраняются в Movies коллекции экземпляра db(MovieDBContext ). Новые данные фильма сохраняются в базе данных путем вызова SaveChanges метода MovieDBContext. После сохранения данных код перенаправляет пользователя в метод действия Index класса MoviesController, который отображает коллекцию фильмов с учетом только что внесенных изменений.

Как только проверка на стороне клиента определяет недопустимое значение поля, отображается сообщение об ошибке. Если JavaScript отключен, проверка на стороне клиента отключена. Тем не менее сервер обнаруживает, что опубликованные значения недопустимы, и значения формы повторно отображаются с сообщениями об ошибках.

Проверка рассматривается более подробно далее в этом руководстве.

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

abcNotValid

HttpGet Все методы следуют аналогичному шаблону. Они получают объект movie (или список объектов, в случае Index) и передают модель в представление. Метод Create передает пустой объект movie в представление Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода HttpPost. Изменение данных в методе HTTP GET представляет угрозу безопасности, как описано в записи блога ASP.NET MVC Tip #46 — Don't use Delete Links because they create Security Holes. Изменение данных в методе GET также нарушает рекомендации ПО HTTP и архитектурный шаблон REST , который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.

Проверка jQuery для языковых стандартов, отличных от английского

Если вы используете US-English компьютер, можно пропустить этот раздел и перейти к следующему руководству. Вы можете скачать версию этого руководства по глобализации здесь. Отличный учебник из двух частей по интернационализации см. в статье Nadeem's ASP.NET MVC 5 Internationalization.

Примечание

Для поддержки проверки jQuery для языковых стандартов, отличных от английского языка, в которых используется запятая (",") для десятичной запятой и форматов даты, отличных от US-English, необходимо включить globalize.js и язык и региональные параметры /globalize.cultures.js file(from https://github.com/jquery/globalize ) и JavaScript для использования Globalize.parseFloat. Вы можете получить проверку jQuery не на английском языке из NuGet. (Не устанавливайте Globalize, если вы используете языковой стандарт на английском языке.)

  1. В меню Сервис выберите диспетчер пакетов NuGet, а затем — Управление пакетами NuGet для решения.

    Снимок экрана: меню

  2. В области слева выберите Обзор*. *(См. изображение ниже.)

  3. В поле ввода введите Globalize*.

    Снимок экрана: поле ввода для ввода globalize.

    Выберите jQuery.Validation.Globalize, выберите MvcMovie и щелкните Установить. Файл Scripts\jquery.globalize\globalize.js будет добавлен в проект. Папка *Scripts\jquery.globalize\cultures* будет содержать множество файлов JavaScript для языка и региональных параметров. Обратите внимание, что установка этого пакета может занять пять минут.

    В следующем коде показаны изменения в файле Views\Movies\Edit.cshtml:

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

<script src="~/Scripts/globalize/globalize.js"></script>
<script src="~/Scripts/globalize/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture.Name).js"></script>
<script>
    $.validator.methods.number = function (value, element) {
        return this.optional(element) ||
            !isNaN(Globalize.parseFloat(value));
    }
    $(document).ready(function () {
        Globalize.culture('@(System.Threading.Thread.CurrentThread.CurrentCulture.Name)');
    });
</script>
<script>
    jQuery.extend(jQuery.validator.methods, {
        range: function (value, element, param) {
            //Use the Globalization plugin to parse the value
            var val = Globalize.parseFloat(value);
            return this.optional(element) || (
                val >= param[0] && val <= param[1]);
        }
    });
    $.validator.methods.date = function (value, element) {
        return this.optional(element) ||
            Globalize.parseDate(value) ||
            Globalize.parseDate(value, "yyyy-MM-dd");
    }
</script>
}

Чтобы избежать повторения этого кода в каждом представлении правки, можно переместить его в файл макета. Чтобы оптимизировать скачивание скрипта, ознакомьтесь с моим учебником Объединение и минификация.

Дополнительные сведения см . в разделе ASP.NET MVC 3 Internationalization and ASP.NET MVC 3 Internationalization - Part 2 (NerdDinner).

В качестве временного исправления, если вы не можете получить проверку в вашем языковом стандарте, вы можете принудительно использовать английский язык (США) или отключить JavaScript в браузере. Чтобы принудить компьютер использовать английский (США), можно добавить элемент глобализации в корневой файлweb.config проектов. В следующем коде показан элемент глобализации с языком и региональными параметрами США английском языке.

<system.web>
    <globalization culture ="en-US" />
    <!--elements removed for clarity-->
  </system.web>

В следующем руководстве мы реализуем функцию поиска.