Часть 6. Методы и представления контроллера в ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
Автор: Рик Андерсон (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-адрес.
Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки 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 должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.
Дополнительные ресурсы
- Глобализация и локализация
- Общие сведения о вспомогательных функциях тегов
- Создание вспомогательных функций тегов
- Предотвращение атак с межсайтовой подделкой запросов (XSRF/CSRF) в ASP.NET Core
- Защита контроллера от чрезмерной передачи данных
- ViewModels
- Вспомогательная функция тега форм
- Вспомогательная функция тега Input
- Вспомогательная функция тега метки
- Вспомогательная функция тега Select
- Вспомогательная функция тега Validation
Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент 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-адрес.
Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки 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 должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.
Дополнительные ресурсы
- Глобализация и локализация
- Общие сведения о вспомогательных функциях тегов
- Создание вспомогательных функций тегов
- Предотвращение атак с межсайтовой подделкой запросов (XSRF/CSRF) в ASP.NET Core
- Защита контроллера от чрезмерной передачи данных
- ViewModels
- Вспомогательная функция тега форм
- Вспомогательная функция тега Input
- Вспомогательная функция тега метки
- Вспомогательная функция тега Select
- Вспомогательная функция тега Validation
Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент 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-адрес.
Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки 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 должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.
Дополнительные ресурсы
- Глобализация и локализация
- Общие сведения о вспомогательных функциях тегов
- Создание вспомогательных функций тегов
- Предотвращение атак с межсайтовой подделкой запросов (XSRF/CSRF) в ASP.NET Core
- Защита контроллера от чрезмерной передачи данных
- ViewModels
- Вспомогательная функция тега форм
- Вспомогательная функция тега Input
- Вспомогательная функция тега метки
- Вспомогательная функция тега Select
- Вспомогательная функция тега Validation
Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент 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-адрес.
Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки 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 должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.
Дополнительные ресурсы
- Глобализация и локализация
- Общие сведения о вспомогательных функциях тегов
- Создание вспомогательных функций тегов
- Предотвращение атак с межсайтовой подделкой запросов (XSRF/CSRF) в ASP.NET Core
- Защита контроллера от чрезмерной передачи данных
- ViewModels
- Вспомогательная функция тега форм
- Вспомогательная функция тега Input
- Вспомогательная функция тега метки
- Вспомогательная функция тега Select
- Вспомогательная функция тега Validation
Все готово для приложения по работе с фильмами, но презентация далеко не идеальна, например, элемент 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-адрес.
Ссылки "Изменить", "Сведения" и "Удалить " создаются вспомогательным элементом тега привязки 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 должна выполняться безопасным способом, то есть не иметь побочных эффектов и не изменять существующие данные.
Дополнительные ресурсы
- Глобализация и локализация
- Общие сведения о вспомогательных функциях тегов
- Создание вспомогательных функций тегов
- Предотвращение атак с межсайтовой подделкой запросов (XSRF/CSRF) в ASP.NET Core
- Защита контроллера от чрезмерной передачи данных
- ViewModels
- Вспомогательная функция тега форм
- Вспомогательная функция тега Input
- Вспомогательная функция тега метки
- Вспомогательная функция тега Select
- Вспомогательная функция тега Validation
ASP.NET Core