Бөлісу құралы:


Часть 6. Методы и представления контроллера в ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

Автор: Рик Андерсон (Rick Anderson)

Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент ReleaseDate должен состоять из двух слов.

Представление индекса: заголовок

Откройте файл и добавьте выделенные Models/Movie.cs строки, показанные ниже:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

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

Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")], чтобы Entity Framework Core корректно сопоставила Price с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных.

Перейдите к контроллеру Movies. Наведите указатель мыши на ссылку Edit (Изменить) и удерживайте его на месте, чтобы просмотреть целевой URL-адрес.

Окно браузера с указателем, наведенным на ссылку Edit (Изменить), и URL-адресом ссылки https://localhost:5001/Movies/Edit/5

Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки Core MVC в Views/Movies/Index.cshtml файле.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor. В представленном выше коде AnchorTagHelper динамически создает значение атрибута HTML href на основе метода действия контроллера и идентификатора маршрута. Для изучения созданной разметки используйте функцию просмотра исходного кода или средства для разработчика в предпочитаемом вами браузере. Ниже показана часть созданного кода HTML:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Формат для routing задан в файле Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core преобразует https://localhost:5001/Movies/Edit/4 в запрос метода действия Edit контроллера Movies с параметром Id, равным 4. (Методы контроллера также называются методами действия.)

Вспомогательные функции тегов являются одной из самых популярных новых возможностей в ASP.NET Core. Подробнее см. в разделе Дополнительные ресурсы.

Откройте контроллер Movies и изучите два метода действия Edit. В следующем коде демонстрируется метод HTTP GET Edit, который выполняет выборку фильмов и заполняет форму редактирования, созданную файлом Edit.cshtml Razor.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

В следующем коде показан метод HTTP POST Edit, который является владельцем переданных значений фильмов:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [Bind] является одним из способов защиты от чрезмерной передачи данных. Свойства необходимо включать только в тот атрибут [Bind], который вы хотите изменить. Дополнительные сведения см. в разделе Защита контроллера от чрезмерной передачи данных. ViewModels реализует альтернативный подход к защите от чрезмерной передачи данных.

Обратите внимание на второй метод действия Edit, которому предшествует атрибут [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут HttpPost указывает на то, что этот метод Edit может вызываться только для запросов POST. Вы могли бы применить атрибут [HttpGet] к первому методу редактирования, однако это необязательно, поскольку значение [HttpGet] задается по умолчанию.

Атрибут ValidateAntiForgeryToken используется для предотвращения подделки запроса и сопряжен с маркером антифоргерии, созданным в файле представления редактирования (Views/Movies/Edit.cshtml). Файл представления редактирования создает маркер антифоргерии с вспомогательным элементом тега формы.

<form asp-action="Edit">

Вспомогательный элемент тега формы создает скрытый маркер антифоргерии, который должен соответствовать [ValidateAntiForgeryToken] созданному маркеру антифоргерии в Edit методе контроллера Movies. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Метод HttpGet Edit принимает параметр фильма ID, выполняет поиск фильма с использованием метода FindAsync платформы Entity Framework и возвращает выбранный фильм в представление редактирования. Если фильм найти не удается, возвращается ошибка NotFound (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie и создает код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано представление редактирования, созданное системой формирования шаблонов Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

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

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

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

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Элементы <input> находятся в элементе HTML <form>, атрибут action которого задает передачу данных по URL-адресу /Movies/Edit/id. Данные формы будут передаваться на сервер при нажатии кнопки Save. В последней строке перед закрывающим элементом </form> отображается скрытый маркер XSRF, созданный вспомогательной функцией тега Form.

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

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [ValidateAntiForgeryToken] проверяет скрытый маркер XSRF , созданный генератором маркеров защиты от подделки в вспомогательном элементе тега формы.

Система модели привязки принимает переданные значения формы и создает объект Movie, который передается в качестве параметра movie. Свойство ModelState.IsValid проверяет, можно ли использовать переданные в форме данные для изменения (редактирования или обновления) объекта Movie. Допустимые данные сохраняются. Обновленные (измененные) данные фильма сохраняются в базе данных посредством вызова метода SaveChangesAsync в контексте базы данных. После сохранения данных код перенаправляет пользователя в метод действия Index класса MoviesController, который отображает коллекцию фильмов с учетом только что внесенных изменений.

Перед отправкой формы на сервер на стороне клиента проверяется выполнение всех правил проверки для полей. При обнаружении ошибок проверки отображается сообщение об ошибке, а форма не передается. Если JavaScript отключен, проверка на стороне клиента не выполняется. Тем не менее, сервер обнаружит переданные недопустимые значения, в результате чего значения формы будут отображены повторно с сообщениями об ошибках. Далее в этом руководстве мы более подробно изучим проверку модели. Вспомогательный элемент тега проверки в шаблоне Views/Movies/Edit.cshtml представления заботится о отображении соответствующих сообщений об ошибках.

Представление редактирования. Исключение из-за неправильного значения

Все методы HttpGet в контроллере Movie имеют схожий шаблон. Они получают объект фильма (или список объектов для метода Index) и передают объект (модель) в представление. Метод Create передает в представление пустой объект фильма Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода [HttpPost]. Изменение данных в методе HTTP GET сопряжено с угрозой безопасности. Изменение данных в HTTP GET методе также нарушает рекомендации ПО HTTP и шаблон архитектуры REST , который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.

Дополнительные ресурсы

Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент ReleaseDate должен состоять из двух слов.

Представление индекса: заголовок

Откройте файл и добавьте выделенные Models/Movie.cs строки, показанные ниже:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

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

Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")], чтобы Entity Framework Core корректно сопоставила Price с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных.

Перейдите к контроллеру Movies. Наведите указатель мыши на ссылку Edit (Изменить) и удерживайте его на месте, чтобы просмотреть целевой URL-адрес.

Окно браузера с указателем, наведенным на ссылку Edit (Изменить), и URL-адресом ссылки https://localhost:5001/Movies/Edit/5

Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки Core MVC в Views/Movies/Index.cshtml файле.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor. В представленном выше коде AnchorTagHelper динамически создает значение атрибута HTML href на основе метода действия контроллера и идентификатора маршрута. Для изучения созданной разметки используйте функцию просмотра исходного кода или средства для разработчика в предпочитаемом вами браузере. Ниже показана часть созданного кода HTML:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Формат для routing задан в файле Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core преобразует https://localhost:5001/Movies/Edit/4 в запрос метода действия Edit контроллера Movies с параметром Id, равным 4. (Методы контроллера также называются методами действия.)

Вспомогательные функции тегов являются одной из самых популярных новых возможностей в ASP.NET Core. Подробнее см. в разделе Дополнительные ресурсы.

Откройте контроллер Movies и изучите два метода действия Edit. В следующем коде демонстрируется метод HTTP GET Edit, который выполняет выборку фильмов и заполняет форму редактирования, созданную файлом Edit.cshtml Razor.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

В следующем коде показан метод HTTP POST Edit, который является владельцем переданных значений фильмов:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [Bind] является одним из способов защиты от чрезмерной передачи данных. Свойства необходимо включать только в тот атрибут [Bind], который вы хотите изменить. Дополнительные сведения см. в разделе Защита контроллера от чрезмерной передачи данных. ViewModels реализует альтернативный подход к защите от чрезмерной передачи данных.

Обратите внимание на второй метод действия Edit, которому предшествует атрибут [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут HttpPost указывает на то, что этот метод Edit может вызываться только для запросов POST. Вы могли бы применить атрибут [HttpGet] к первому методу редактирования, однако это необязательно, поскольку значение [HttpGet] задается по умолчанию.

Атрибут ValidateAntiForgeryToken используется для предотвращения подделки запроса и сопряжен с маркером антифоргерии, созданным в файле представления редактирования (Views/Movies/Edit.cshtml). Файл представления редактирования создает маркер антифоргерии с вспомогательным элементом тега формы.

<form asp-action="Edit">

Вспомогательный элемент тега формы создает скрытый маркер антифоргерии, который должен соответствовать [ValidateAntiForgeryToken] созданному маркеру антифоргерии в Edit методе контроллера Movies. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Метод HttpGet Edit принимает параметр фильма ID, выполняет поиск фильма с использованием метода FindAsync платформы Entity Framework и возвращает выбранный фильм в представление редактирования. Если фильм найти не удается, возвращается ошибка NotFound (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie и создает код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано представление редактирования, созданное системой формирования шаблонов Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

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

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

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

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Элементы <input> находятся в элементе HTML <form>, атрибут action которого задает передачу данных по URL-адресу /Movies/Edit/id. Данные формы будут передаваться на сервер при нажатии кнопки Save. В последней строке перед закрывающим элементом </form> отображается скрытый маркер XSRF, созданный вспомогательной функцией тега Form.

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

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [ValidateAntiForgeryToken] проверяет скрытый маркер XSRF , созданный генератором маркеров защиты от подделки в вспомогательном элементе тега формы.

Система модели привязки принимает переданные значения формы и создает объект Movie, который передается в качестве параметра movie. Свойство ModelState.IsValid проверяет, можно ли использовать переданные в форме данные для изменения (редактирования или обновления) объекта Movie. Допустимые данные сохраняются. Обновленные (измененные) данные фильма сохраняются в базе данных посредством вызова метода SaveChangesAsync в контексте базы данных. После сохранения данных код перенаправляет пользователя в метод действия Index класса MoviesController, который отображает коллекцию фильмов с учетом только что внесенных изменений.

Перед отправкой формы на сервер на стороне клиента проверяется выполнение всех правил проверки для полей. При обнаружении ошибок проверки отображается сообщение об ошибке, а форма не передается. Если JavaScript отключен, проверка на стороне клиента не выполняется. Тем не менее, сервер обнаружит переданные недопустимые значения, в результате чего значения формы будут отображены повторно с сообщениями об ошибках. Далее в этом руководстве мы более подробно изучим проверку модели. Вспомогательный элемент тега проверки в шаблоне Views/Movies/Edit.cshtml представления заботится о отображении соответствующих сообщений об ошибках.

Представление редактирования. Исключение из-за неправильного значения

Все методы HttpGet в контроллере Movie имеют схожий шаблон. Они получают объект фильма (или список объектов для метода Index) и передают объект (модель) в представление. Метод Create передает в представление пустой объект фильма Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода [HttpPost]. Изменение данных в методе HTTP GET сопряжено с угрозой безопасности. Изменение данных в HTTP GET методе также нарушает рекомендации ПО HTTP и шаблон архитектуры REST , который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.

Дополнительные ресурсы

Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент ReleaseDate должен состоять из двух слов.

Представление индекса: заголовок

Откройте файл и добавьте выделенные Models/Movie.cs строки, показанные ниже:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

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

Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")], чтобы Entity Framework Core корректно сопоставила Price с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных.

Перейдите к контроллеру Movies. Наведите указатель мыши на ссылку Edit (Изменить) и удерживайте его на месте, чтобы просмотреть целевой URL-адрес.

Окно браузера с указателем, наведенным на ссылку Edit (Изменить), и URL-адресом ссылки https://localhost:5001/Movies/Edit/5

Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки Core MVC в Views/Movies/Index.cshtml файле.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor. В представленном выше коде AnchorTagHelper динамически создает значение атрибута HTML href на основе метода действия контроллера и идентификатора маршрута. Для изучения созданной разметки используйте функцию просмотра исходного кода или средства для разработчика в предпочитаемом вами браузере. Ниже показана часть созданного кода HTML:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Формат для routing задан в файле Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core преобразует https://localhost:5001/Movies/Edit/4 в запрос метода действия Edit контроллера Movies с параметром Id, равным 4. (Методы контроллера также называются методами действия.)

Вспомогательные функции тегов являются одной из самых популярных новых возможностей в ASP.NET Core. Подробнее см. в разделе Дополнительные ресурсы.

Откройте контроллер Movies и изучите два метода действия Edit. В следующем коде демонстрируется метод HTTP GET Edit, который выполняет выборку фильмов и заполняет форму редактирования, созданную файлом Edit.cshtml Razor.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

В следующем коде показан метод HTTP POST Edit, который является владельцем переданных значений фильмов:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [Bind] является одним из способов защиты от чрезмерной передачи данных. Свойства необходимо включать только в тот атрибут [Bind], который вы хотите изменить. Дополнительные сведения см. в разделе Защита контроллера от чрезмерной передачи данных. ViewModels реализует альтернативный подход к защите от чрезмерной передачи данных.

Обратите внимание на второй метод действия Edit, которому предшествует атрибут [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут HttpPost указывает на то, что этот метод Edit может вызываться только для запросов POST. Вы могли бы применить атрибут [HttpGet] к первому методу редактирования, однако это необязательно, поскольку значение [HttpGet] задается по умолчанию.

Атрибут ValidateAntiForgeryToken используется для предотвращения подделки запроса и сопряжен с маркером антифоргерии, созданным в файле представления редактирования (Views/Movies/Edit.cshtml). Файл представления редактирования создает маркер антифоргерии с вспомогательным элементом тега формы.

<form asp-action="Edit">

Вспомогательный элемент тега формы создает скрытый маркер антифоргерии, который должен соответствовать [ValidateAntiForgeryToken] созданному маркеру антифоргерии в Edit методе контроллера Movies. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Метод HttpGet Edit принимает параметр фильма ID, выполняет поиск фильма с использованием метода FindAsync платформы Entity Framework и возвращает выбранный фильм в представление редактирования. Если фильм найти не удается, возвращается ошибка NotFound (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie и создает код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано представление редактирования, созданное системой формирования шаблонов Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

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

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

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

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Элементы <input> находятся в элементе HTML <form>, атрибут action которого задает передачу данных по URL-адресу /Movies/Edit/id. Данные формы будут передаваться на сервер при нажатии кнопки Save. В последней строке перед закрывающим элементом </form> отображается скрытый маркер XSRF, созданный вспомогательной функцией тега Form.

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

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [ValidateAntiForgeryToken] проверяет скрытый маркер XSRF , созданный генератором маркеров защиты от подделки в вспомогательном элементе тега формы.

Система модели привязки принимает переданные значения формы и создает объект Movie, который передается в качестве параметра movie. Свойство ModelState.IsValid проверяет, можно ли использовать переданные в форме данные для изменения (редактирования или обновления) объекта Movie. Допустимые данные сохраняются. Обновленные (измененные) данные фильма сохраняются в базе данных посредством вызова метода SaveChangesAsync в контексте базы данных. После сохранения данных код перенаправляет пользователя в метод действия Index класса MoviesController, который отображает коллекцию фильмов с учетом только что внесенных изменений.

Перед отправкой формы на сервер на стороне клиента проверяется выполнение всех правил проверки для полей. При обнаружении ошибок проверки отображается сообщение об ошибке, а форма не передается. Если JavaScript отключен, проверка на стороне клиента не выполняется. Тем не менее, сервер обнаружит переданные недопустимые значения, в результате чего значения формы будут отображены повторно с сообщениями об ошибках. Далее в этом руководстве мы более подробно изучим проверку модели. Вспомогательный элемент тега проверки в шаблоне Views/Movies/Edit.cshtml представления заботится о отображении соответствующих сообщений об ошибках.

Представление редактирования. Исключение из-за неправильного значения

Все методы HttpGet в контроллере Movie имеют схожий шаблон. Они получают объект фильма (или список объектов для метода Index) и передают объект (модель) в представление. Метод Create передает в представление пустой объект фильма Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода [HttpPost]. Изменение данных в методе HTTP GET сопряжено с угрозой безопасности. Изменение данных в HTTP GET методе также нарушает рекомендации ПО HTTP и шаблон архитектуры REST , который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.

Дополнительные ресурсы

Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент ReleaseDate должен состоять из двух слов.

Представление индекса: заголовок

Откройте файл и добавьте выделенные Models/Movie.cs строки, показанные ниже:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

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

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string? Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

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

Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")], чтобы Entity Framework Core корректно сопоставила Price с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных.

Перейдите к контроллеру Movies. Наведите указатель мыши на ссылку Edit (Изменить) и удерживайте его на месте, чтобы просмотреть целевой URL-адрес.

Окно браузера с указателем, наведенным на ссылку Edit (Изменить), и URL-адресом ссылки https://localhost:5001/Movies/Edit/5

Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки Core MVC в Views/Movies/Index.cshtml файле.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor. В представленном выше коде AnchorTagHelper динамически создает значение атрибута HTML href на основе метода действия контроллера и идентификатора маршрута. Для изучения созданной разметки используйте функцию просмотра исходного кода или средства для разработчика в предпочитаемом вами браузере. Ниже показана часть созданного кода HTML:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Формат для routing задан в файле Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core преобразует https://localhost:5001/Movies/Edit/4 в запрос метода действия Edit контроллера Movies с параметром Id, равным 4. (Методы контроллера также называются методами действия.)

Вспомогательные функции тегов — это популярная функция в ASP.NET Core. Дополнительные сведения о них см. в разделе "Дополнительные ресурсы".

Откройте контроллер Movies и изучите два метода действия Edit. В следующем коде демонстрируется метод HTTP GET Edit, который выполняет выборку фильмов и заполняет форму редактирования, созданную файлом Edit.cshtml Razor.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

В следующем коде показан метод HTTP POST Edit, который является владельцем переданных значений фильмов:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [Bind] является одним из способов защиты от чрезмерной передачи данных. Свойства необходимо включать только в тот атрибут [Bind], который вы хотите изменить. Дополнительные сведения см. в разделе Защита контроллера от чрезмерной передачи данных. ViewModels реализует альтернативный подход к защите от чрезмерной передачи данных.

Обратите внимание на второй метод действия Edit, которому предшествует атрибут [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут HttpPost указывает на то, что этот метод Edit может вызываться только для запросов POST. Вы могли бы применить атрибут [HttpGet] к первому методу редактирования, однако это необязательно, поскольку значение [HttpGet] задается по умолчанию.

Атрибут ValidateAntiForgeryToken используется для предотвращения подделки запроса и сопряжен с маркером антифоргерии, созданным в файле представления редактирования (Views/Movies/Edit.cshtml). Файл представления редактирования создает маркер антифоргерии с вспомогательным элементом тега формы.

<form asp-action="Edit">

Вспомогательный элемент тега формы создает скрытый маркер антифоргерии, который должен соответствовать [ValidateAntiForgeryToken] созданному маркеру антифоргерии в Edit методе контроллера Movies. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Метод HttpGet Edit принимает параметр фильма ID, выполняет поиск фильма с использованием метода FindAsync платформы Entity Framework и возвращает выбранный фильм в представление редактирования. Если фильм найти не удается, возвращается ошибка NotFound (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie и создает код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано представление редактирования, созданное системой формирования шаблонов Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

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

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

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

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Элементы <input> находятся в элементе HTML <form>, атрибут action которого задает передачу данных по URL-адресу /Movies/Edit/id. Данные формы будут передаваться на сервер при нажатии кнопки Save. В последней строке перед закрывающим элементом </form> отображается скрытый маркер XSRF, созданный вспомогательной функцией тега Form.

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

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [ValidateAntiForgeryToken] проверяет скрытый маркер XSRF , созданный генератором маркеров защиты от подделки в вспомогательном элементе тега формы.

Система модели привязки принимает переданные значения формы и создает объект Movie, который передается в качестве параметра movie. Свойство ModelState.IsValid проверяет, можно ли использовать переданные в форме данные для изменения (редактирования или обновления) объекта Movie. Допустимые данные сохраняются. Обновленные (измененные) данные фильма сохраняются в базе данных посредством вызова метода SaveChangesAsync в контексте базы данных. После сохранения данных код перенаправляет пользователя в метод действия Index класса MoviesController, который отображает коллекцию фильмов с учетом только что внесенных изменений.

Перед отправкой формы на сервер на стороне клиента проверяется выполнение всех правил проверки для полей. При обнаружении ошибок проверки отображается сообщение об ошибке, а форма не передается. Если JavaScript отключен, проверка на стороне клиента не выполняется. Тем не менее, сервер обнаружит переданные недопустимые значения, в результате чего значения формы будут отображены повторно с сообщениями об ошибках. Далее в этом руководстве мы более подробно изучим проверку модели. Вспомогательный элемент тега проверки в шаблоне Views/Movies/Edit.cshtml представления заботится о отображении соответствующих сообщений об ошибках.

Представление редактирования. Исключение из-за неправильного значения

Все методы HttpGet в контроллере Movie имеют схожий шаблон. Они получают объект фильма (или список объектов для метода Index) и передают объект (модель) в представление. Метод Create передает в представление пустой объект фильма Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода [HttpPost]. Изменение данных в методе HTTP GET сопряжено с угрозой безопасности. Изменение данных в HTTP GET методе также нарушает рекомендации ПО HTTP и шаблон архитектуры REST , который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.

Дополнительные ресурсы

Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент ReleaseDate должен состоять из двух слов.

Представление индекса: заголовок

Откройте файл и добавьте выделенные Models/Movie.cs строки, показанные ниже:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

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

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

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

Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")], чтобы Entity Framework Core корректно сопоставила Price с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных.

Перейдите к контроллеру Movies. Наведите указатель мыши на ссылку Edit (Изменить) и удерживайте его на месте, чтобы просмотреть целевой URL-адрес.

Окно браузера с указателем, наведенным на ссылку Edit (Изменить), и URL-адресом ссылки https://localhost:5001/Movies/Edit/5

Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки Core MVC в Views/Movies/Index.cshtml файле.

        <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
    </td>
</tr>

Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor. В представленном выше коде AnchorTagHelper динамически создает значение атрибута HTML href на основе метода действия контроллера и идентификатора маршрута. Для изучения созданной разметки используйте функцию просмотра исходного кода или средства для разработчика в предпочитаемом вами браузере. Ниже показана часть созданного кода HTML:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Формат для routing задан в файле Startup.cs:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

ASP.NET Core преобразует https://localhost:5001/Movies/Edit/4 в запрос метода действия Edit контроллера Movies с параметром Id, равным 4. (Методы контроллера также называются методами действия.)

Дополнительные сведения о вспомогательных функциях тегов см. в разделе "Дополнительные ресурсы".

Откройте контроллер Movies и изучите два метода действия Edit. В следующем коде демонстрируется метод HTTP GET Edit, который выполняет выборку фильмов и заполняет форму редактирования, созданную файлом Edit.cshtml Razor.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

В следующем коде показан метод HTTP POST Edit, который является владельцем переданных значений фильмов:

// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

Атрибут [Bind] является одним из способов защиты от чрезмерной передачи данных. Свойства необходимо включать только в тот атрибут [Bind], который вы хотите изменить. Дополнительные сведения см. в разделе Защита контроллера от чрезмерной передачи данных. ViewModels реализует альтернативный подход к защите от чрезмерной передачи данных.

Обратите внимание на второй метод действия Edit, которому предшествует атрибут [HttpPost].

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут HttpPost указывает на то, что этот метод Edit может вызываться только для запросов POST. Вы могли бы применить атрибут [HttpGet] к первому методу редактирования, однако это необязательно, поскольку значение [HttpGet] задается по умолчанию.

Атрибут ValidateAntiForgeryToken используется для предотвращения подделки запроса и сопряжен с маркером антифоргерии, созданным в файле представления редактирования (Views/Movies/Edit.cshtml). Файл представления редактирования создает маркер антифоргерии с вспомогательным элементом тега формы.

<form asp-action="Edit">

Вспомогательный элемент тега формы создает скрытый маркер антифоргерии, который должен соответствовать [ValidateAntiForgeryToken] созданному маркеру антифоргерии в Edit методе контроллера Movies. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Метод HttpGet Edit принимает параметр фильма ID, выполняет поиск фильма с использованием метода FindAsync платформы Entity Framework и возвращает выбранный фильм в представление редактирования. Если фильм найти не удается, возвращается ошибка NotFound (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Если в представлении редактирования создана система формирования шаблонов, она проверяет класс Movie и создает код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано представление редактирования, созданное системой формирования шаблонов Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

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

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

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

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Элементы <input> находятся в элементе HTML <form>, атрибут action которого задает передачу данных по URL-адресу /Movies/Edit/id. Данные формы будут передаваться на сервер при нажатии кнопки Save. В последней строке перед закрывающим элементом </form> отображается скрытый маркер XSRF, созданный вспомогательной функцией тега Form.

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

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

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Атрибут [ValidateAntiForgeryToken] проверяет скрытый маркер XSRF , созданный генератором маркеров защиты от подделки в вспомогательном элементе тега формы.

Система модели привязки принимает переданные значения формы и создает объект Movie, который передается в качестве параметра movie. Свойство ModelState.IsValid проверяет, можно ли использовать переданные в форме данные для изменения (редактирования или обновления) объекта Movie. Допустимые данные сохраняются. Обновленные (измененные) данные фильма сохраняются в базе данных посредством вызова метода SaveChangesAsync в контексте базы данных. После сохранения данных код перенаправляет пользователя в метод действия Index класса MoviesController, который отображает коллекцию фильмов с учетом только что внесенных изменений.

Перед отправкой формы на сервер на стороне клиента проверяется выполнение всех правил проверки для полей. При обнаружении ошибок проверки отображается сообщение об ошибке, а форма не передается. Если JavaScript отключен, проверка на стороне клиента не выполняется. Тем не менее, сервер обнаружит переданные недопустимые значения, в результате чего значения формы будут отображены повторно с сообщениями об ошибках. Далее в этом руководстве мы более подробно изучим проверку модели. Вспомогательный элемент тега проверки в шаблоне Views/Movies/Edit.cshtml представления заботится о отображении соответствующих сообщений об ошибках.

Представление редактирования. Исключение из-за неправильного значения

Все методы HttpGet в контроллере Movie имеют схожий шаблон. Они получают объект фильма (или список объектов для метода Index) и передают объект (модель) в представление. Метод Create передает в представление пустой объект фильма Create. Все методы, которые создают, редактируют, удаляют или иным образом изменяют данные, делают это в перегрузке метода [HttpPost]. Изменение данных в методе HTTP GET сопряжено с угрозой безопасности. Изменение данных в HTTP GET методе также нарушает рекомендации ПО HTTP и шаблон архитектуры REST , который указывает, что запросы GET не должны изменять состояние приложения. Другими словами, операция GET должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.

Дополнительные ресурсы