Часть 5. Изменение созданных страниц в приложении ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
Приложение для работы с фильмами подготовлено, но представление данных далеко от идеала. Вместо ReleaseDate должно стоять Дата выпуска, то есть два слова.
Обновление модели
Обновите Models/Movie.cs
следующий выделенный код:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
В предыдущем коде:
- Заметка к данным
[Column(TypeName = "decimal(18, 2)")]
позволяет Entity Framework Core корректно сопоставитьPrice
с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных. - Атрибут [Display] указывает на отображаемое имя поля. В приведенном выше коде
Release Date
вместоReleaseDate
. - Атрибут [DataType] указывает тип данных (
Date
). Сведения о времени, хранящиеся в поле, не отображаются.
Пространство имен DataAnnotations будет рассмотрено в следующем руководстве.
Перейдите к Pages/Movies и наведите указатель мыши на ссылку Edit (Изменение), чтобы просмотреть целевой URL-адрес.
Ссылки Edit, Details и Delete создаются вспомогательной функцией тегов привязки в файле Pages/Movies/Index.cshtml
.
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
В приведенном выше коде Вспомогательная функция привязки тегов динамически создает значение атрибута HTML href
на основе Razor Page (маршрут является относительным), атрибут asp-page
и идентификатор маршрута (asp-route-id
). Дополнительные сведения см. в разделе Формирование URL-адресов для страниц.
Для проверки созданной разметки используйте в браузере параметр Просмотреть исходный код. Ниже показана часть созданного кода HTML:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Динамически созданные ссылки передают идентификатор фильма строкой запроса. Например, ?id=1
в https://localhost:5001/Movies/Details?id=1
.
Добавление шаблона маршрута
Обновите страницы Razor Pages Edit, Details и Delete так, чтобы использовался шаблон маршрута {id:int}
. Измените директиву страницы для каждой из этих страниц c @page
на @page "{id:int}"
. Запустите приложение и просмотрите исходный код.
Созданный код HTML добавляет идентификатор в путь URL-адреса:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Запрос на страницу с шаблоном {id:int}
маршрута, который не включает целое число, возвращает ошибку HTTP 404 (не найдена). Например, https://localhost:5001/Movies/Details
возвращает ошибку 404. Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Чтобы проверить поведение @page "{id:int?}"
:
- Задайте директиву страницы в
Pages/Movies/Details.cshtml
как@page "{id:int?}"
. - Установите точку останова в
public async Task<IActionResult> OnGetAsync(int? id)
, вPages/Movies/Details.cshtml.cs
. - Перейдите к
https://localhost:5001/Movies/Details/
.
Из-за директивы @page "{id:int}"
точка останова не достигается. Механизм маршрутизации возвращает ошибку HTTP 404. При использовании @page "{id:int?}"
метод OnGetAsync
возвращает NotFound
(HTTP 404):
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
else
{
Movie = movie;
}
return Page();
}
Проверка обработки исключений нежесткой блокировки
Просмотрите OnPostAsync
Pages/Movies/Edit.cshtml.cs
метод в файле:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
Предыдущий код обнаруживает исключения параллелизма, когда один клиент удаляет фильм, а другой вносит изменения в фильм.
Чтобы протестировать блок catch
, выполните указанные ниже действия.
- Задайте точку останова в
catch (DbUpdateConcurrencyException)
. - Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
- В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
- В первом окне браузера опубликуйте изменения для фильма.
Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.
Проверка публикации и привязки
Просмотрите файл Pages/Movies/Edit.cshtml.cs
.
public class EditModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
При выполнении HTTP-запроса GET к странице Movies/Edit, например https://localhost:5001/Movies/Edit/3
, происходит следующее:
- Метод
OnGetAsync
извлекает запись фильма из базы данных и возвращает методPage
. - Метод
Page
отображает страницуPages/Movies/Edit.cshtml
Razor. ФайлPages/Movies/Edit.cshtml
содержит директиву@model RazorPagesMovie.Pages.Movies.EditModel
модели, которая делает модель фильма доступной на странице. - Отображается форма Edit со значениями из записи фильма.
При публикации страницы Movies/Edit происходит следующее:
Значения формы на странице привязываются к свойству
Movie
. Атрибут[BindProperty]
обеспечивает привязку модели.[BindProperty] public Movie Movie { get; set; }
При наличии ошибок в состоянии модели, например
ReleaseDate
невозможно преобразовать в дату, форма отображается повторно с предоставленными значениями.Если ошибки модели отсутствуют, данные фильма сохраняются.
Методы HTTP GET на страницах Razor Index, Create и Delete работают аналогично. Метод HTTP POST OnPostAsync
на странице Razor Create работает аналогично методу OnPostAsync
на странице Razor Edit.
Следующие шаги
Приложение для работы с фильмами подготовлено, но представление данных далеко от идеала. Вместо ReleaseDate должно стоять Дата выпуска, то есть два слова.
Обновление модели
Обновите Models/Movie.cs
следующий выделенный код:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
В предыдущем коде:
- Заметка к данным
[Column(TypeName = "decimal(18, 2)")]
позволяет Entity Framework Core корректно сопоставитьPrice
с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных. - Атрибут [Display] указывает на отображаемое имя поля. В приведенном выше коде
Release Date
вместоReleaseDate
. - Атрибут [DataType] указывает тип данных (
Date
). Сведения о времени, хранящиеся в поле, не отображаются.
Пространство имен DataAnnotations будет рассмотрено в следующем руководстве.
Перейдите к Pages/Movies и наведите указатель мыши на ссылку Edit (Изменение), чтобы просмотреть целевой URL-адрес.
Ссылки Edit, Details и Delete создаются вспомогательной функцией тегов привязки в файле Pages/Movies/Index.cshtml
.
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
В приведенном выше коде Вспомогательная функция привязки тегов динамически создает значение атрибута HTML href
на основе Razor Page (маршрут является относительным), атрибут asp-page
и идентификатор маршрута (asp-route-id
). Дополнительные сведения см. в разделе Формирование URL-адресов для страниц.
Для проверки созданной разметки используйте в браузере параметр Просмотреть исходный код. Ниже показана часть созданного кода HTML:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Динамически созданные ссылки передают идентификатор фильма строкой запроса. Например, ?id=1
в https://localhost:5001/Movies/Details?id=1
.
Добавление шаблона маршрута
Обновите страницы Razor Pages Edit, Details и Delete так, чтобы использовался шаблон маршрута {id:int}
. Измените директиву страницы для каждой из этих страниц c @page
на @page "{id:int}"
. Запустите приложение и просмотрите исходный код.
Созданный код HTML добавляет идентификатор в путь URL-адреса:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Запрос на страницу с шаблоном {id:int}
маршрута, который не включает целое число, возвращает ошибку HTTP 404 (не найдена). Например, https://localhost:5001/Movies/Details
возвращает ошибку 404. Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Чтобы проверить поведение @page "{id:int?}"
:
- Задайте директиву страницы в
Pages/Movies/Details.cshtml
как@page "{id:int?}"
. - Установите точку останова в
public async Task<IActionResult> OnGetAsync(int? id)
, вPages/Movies/Details.cshtml.cs
. - Перейдите к
https://localhost:5001/Movies/Details/
.
Из-за директивы @page "{id:int}"
точка останова не достигается. Механизм маршрутизации возвращает ошибку HTTP 404. При использовании @page "{id:int?}"
метод OnGetAsync
возвращает NotFound
(HTTP 404):
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
Проверка обработки исключений нежесткой блокировки
Просмотрите OnPostAsync
Pages/Movies/Edit.cshtml.cs
метод в файле:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
Предыдущий код обнаруживает исключения параллелизма, когда один клиент удаляет фильм, а другой вносит изменения в фильм.
Чтобы протестировать блок catch
, выполните указанные ниже действия.
- Задайте точку останова в
catch (DbUpdateConcurrencyException)
. - Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
- В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
- В первом окне браузера опубликуйте изменения для фильма.
Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.
Проверка публикации и привязки
Просмотрите файл Pages/Movies/Edit.cshtml.cs
.
public class EditModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
При выполнении HTTP-запроса GET к странице Movies/Edit, например https://localhost:5001/Movies/Edit/3
, происходит следующее:
- Метод
OnGetAsync
извлекает запись фильма из базы данных и возвращает методPage
. - Метод
Page
отображает страницуPages/Movies/Edit.cshtml
Razor. ФайлPages/Movies/Edit.cshtml
содержит директиву@model RazorPagesMovie.Pages.Movies.EditModel
модели, которая делает модель фильма доступной на странице. - Отображается форма Edit со значениями из записи фильма.
При публикации страницы Movies/Edit происходит следующее:
Значения формы на странице привязываются к свойству
Movie
. Атрибут[BindProperty]
обеспечивает привязку модели.[BindProperty] public Movie Movie { get; set; }
При наличии ошибок в состоянии модели, например
ReleaseDate
невозможно преобразовать в дату, форма отображается повторно с предоставленными значениями.Если ошибки модели отсутствуют, данные фильма сохраняются.
Методы HTTP GET на страницах Razor Index, Create и Delete работают аналогично. Метод HTTP POST OnPostAsync
на странице Razor Create работает аналогично методу OnPostAsync
на странице Razor Edit.
Следующие шаги
Приложение для работы с фильмами подготовлено, но представление данных далеко от идеала. Вместо ReleaseDate должно стоять Дата выпуска, то есть два слова.
Обновление модели
Обновите Models/Movie.cs
следующий выделенный код:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
В предыдущем коде:
- Заметка к данным
[Column(TypeName = "decimal(18, 2)")]
позволяет Entity Framework Core корректно сопоставитьPrice
с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных. - Атрибут [Display] указывает на отображаемое имя поля. В приведенном выше коде
Release Date
вместоReleaseDate
. - Атрибут [DataType] указывает тип данных (
Date
). Сведения о времени, хранящиеся в поле, не отображаются.
Пространство имен DataAnnotations будет рассмотрено в следующем руководстве.
Перейдите к Pages/Movies и наведите указатель мыши на ссылку Edit (Изменение), чтобы просмотреть целевой URL-адрес.
Ссылки Edit, Details и Delete создаются вспомогательной функцией тегов привязки в файле Pages/Movies/Index.cshtml
.
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
В приведенном выше коде Вспомогательная функция привязки тегов динамически создает значение атрибута HTML href
на основе Razor Page (маршрут является относительным), атрибут asp-page
и идентификатор маршрута (asp-route-id
). Дополнительные сведения см. в разделе Формирование URL-адресов для страниц.
Для проверки созданной разметки используйте в браузере параметр Просмотреть исходный код. Ниже показана часть созданного кода HTML:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Динамически созданные ссылки передают идентификатор фильма строкой запроса. Например, ?id=1
в https://localhost:5001/Movies/Details?id=1
.
Добавление шаблона маршрута
Обновите страницы Razor Pages Edit, Details и Delete так, чтобы использовался шаблон маршрута {id:int}
. Измените директиву страницы для каждой из этих страниц c @page
на @page "{id:int}"
. Запустите приложение и просмотрите исходный код.
Созданный код HTML добавляет идентификатор в путь URL-адреса:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Запрос на страницу с шаблоном {id:int}
маршрута, который не включает целое число, возвращает ошибку HTTP 404 (не найдена). Например, https://localhost:5001/Movies/Details
возвращает ошибку 404. Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Чтобы проверить поведение @page "{id:int?}"
:
- Задайте директиву страницы в
Pages/Movies/Details.cshtml
как@page "{id:int?}"
. - Установите точку останова в
public async Task<IActionResult> OnGetAsync(int? id)
, вPages/Movies/Details.cshtml.cs
. - Перейдите к
https://localhost:5001/Movies/Details/
.
Из-за директивы @page "{id:int}"
точка останова не достигается. Механизм маршрутизации возвращает ошибку HTTP 404. При использовании @page "{id:int?}"
метод OnGetAsync
возвращает NotFound
(HTTP 404):
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
Проверка обработки исключений нежесткой блокировки
Просмотрите OnPostAsync
Pages/Movies/Edit.cshtml.cs
метод в файле:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
Предыдущий код обнаруживает исключения параллелизма, когда один клиент удаляет фильм, а другой вносит изменения в фильм.
Чтобы протестировать блок catch
, выполните указанные ниже действия.
- Задайте точку останова в
catch (DbUpdateConcurrencyException)
. - Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
- В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
- В первом окне браузера опубликуйте изменения для фильма.
Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.
Проверка публикации и привязки
Просмотрите файл Pages/Movies/Edit.cshtml.cs
.
public class EditModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
При выполнении HTTP-запроса GET к странице Movies/Edit, например https://localhost:5001/Movies/Edit/3
, происходит следующее:
- Метод
OnGetAsync
извлекает запись фильма из базы данных и возвращает методPage
. - Метод
Page
отображает страницуPages/Movies/Edit.cshtml
Razor. ФайлPages/Movies/Edit.cshtml
содержит директиву@model RazorPagesMovie.Pages.Movies.EditModel
модели, которая делает модель фильма доступной на странице. - Отображается форма Edit со значениями из записи фильма.
При публикации страницы Movies/Edit происходит следующее:
Значения формы на странице привязываются к свойству
Movie
. Атрибут[BindProperty]
обеспечивает привязку модели.[BindProperty] public Movie Movie { get; set; }
При наличии ошибок в состоянии модели, например
ReleaseDate
невозможно преобразовать в дату, форма отображается повторно с предоставленными значениями.Если ошибки модели отсутствуют, данные фильма сохраняются.
Методы HTTP GET на страницах Razor Index, Create и Delete работают аналогично. Метод HTTP POST OnPostAsync
на странице Razor Create работает аналогично методу OnPostAsync
на странице Razor Edit.
Следующие шаги
Приложение для работы с фильмами подготовлено, но представление данных далеко от идеала. Вместо ReleaseDate должно стоять Дата выпуска, то есть два слова.
Обновление созданного кода
Обновите Models/Movie.cs
следующий выделенный код:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
}
В предыдущем коде:
- Заметка к данным
[Column(TypeName = "decimal(18, 2)")]
позволяет Entity Framework Core корректно сопоставитьPrice
с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных. - Атрибут [Display] указывает на отображаемое имя поля. В приведенном выше коде ReleaseDate заменен на "Дата выпуска".
- Атрибут [DataType] указывает тип данных (
Date
). Сведения о времени, хранящиеся в поле, не отображаются.
Пространство имен DataAnnotations будет рассмотрено в следующем руководстве.
Перейдите к Pages/Movies и наведите указатель мыши на ссылку Edit (Изменение), чтобы просмотреть целевой URL-адрес.
Ссылки Edit, Details и Delete создаются вспомогательной функцией тегов привязки в файле Pages/Movies/Index.cshtml
.
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
В приведенном выше коде Вспомогательная функция привязки тегов динамически создает значение атрибута HTML href
на основе Razor Page (маршрут является относительным), атрибут asp-page
и идентификатор маршрута (asp-route-id
). Дополнительные сведения см. в разделе Формирование URL-адресов для страниц.
Для проверки созданной разметки используйте в браузере параметр Просмотреть исходный код. Ниже показана часть созданного кода HTML:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
В динамически созданных ссылках идентификаторы фильмов передаются с помощью строки запроса. Например, ?id=1
в https://localhost:5001/Movies/Details?id=1
.
Добавление шаблона маршрута
Обновите страницы Razor Pages Edit, Details и Delete так, чтобы использовался шаблон маршрута {id:int}
. Измените директиву страницы для каждой из этих страниц c @page
на @page "{id:int}"
. Запустите приложение и просмотрите исходный код.
Созданный код HTML добавляет идентификатор в путь URL-адреса:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Запрос к странице с шаблоном маршрута {id:int}
, который не включает в себя целое число, приводит к ошибке HTTP 404 (не найдено). Например, https://localhost:5001/Movies/Details
приведет к ошибке 404. Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Чтобы проверить поведение @page "{id:int?}"
:
- Задайте директиву страницы в
Pages/Movies/Details.cshtml
как@page "{id:int?}"
. - Установите точку останова в
public async Task<IActionResult> OnGetAsync(int? id)
, вPages/Movies/Details.cshtml.cs
. - Перейдите к
https://localhost:5001/Movies/Details/
.
Из-за директивы @page "{id:int}"
точка останова не достигается. Механизм маршрутизации возвращает ошибку HTTP 404. При использовании @page "{id:int?}"
метод OnGetAsync
возвращает NotFound
(HTTP 404):
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
Проверка обработки исключений нежесткой блокировки
Просмотрите OnPostAsync
Pages/Movies/Edit.cshtml.cs
метод в файле:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}
Предыдущий код обнаруживает исключения параллелизма, когда один клиент удаляет фильм, а другой вносит изменения в фильм. Предыдущий код не обнаруживает конфликты, возникающие из-за двух или нескольких клиентов, которые одновременно редактировать один и тот же фильм. В этом случае изменения несколькими клиентами применяются в том порядке, который SaveChanges
вызывается и редактирует, которые применяются позже, может перезаписать более ранние изменения с устаревшими значениями.
Чтобы протестировать блок catch
, выполните указанные ниже действия.
- Задайте точку останова в
catch (DbUpdateConcurrencyException)
. - Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
- В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
- В первом окне браузера опубликуйте изменения для фильма.
Рабочий код может потребоваться обнаружить дополнительные конфликты параллелизма, такие как несколько клиентов, изменяя сущность одновременно. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.
Проверка публикации и привязки
Просмотрите файл Pages/Movies/Edit.cshtml.cs
.
public class EditModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}
При выполнении HTTP-запроса GET к странице Movies/Edit, например https://localhost:5001/Movies/Edit/3
, происходит следующее:
- Метод
OnGetAsync
извлекает запись фильма из базы данных и возвращает методPage
. - Метод
Page
отображает страницуPages/Movies/Edit.cshtml
Razor. ФайлPages/Movies/Edit.cshtml
содержит директиву@model RazorPagesMovie.Pages.Movies.EditModel
модели, которая делает модель фильма доступной на странице. - Отображается форма Edit со значениями из записи фильма.
При публикации страницы Movies/Edit происходит следующее:
Значения формы на странице привязываются к свойству
Movie
. Атрибут[BindProperty]
обеспечивает привязку модели.[BindProperty] public Movie Movie { get; set; }
При наличии ошибок в состоянии модели, например
ReleaseDate
невозможно преобразовать в дату, форма отображается повторно с предоставленными значениями.Если ошибки модели отсутствуют, данные фильма сохраняются.
Методы HTTP GET на страницах Razor Index, Create и Delete работают аналогично. Метод HTTP POST OnPostAsync
на странице Razor Create работает аналогично методу OnPostAsync
на странице Razor Edit.
Следующие шаги
Приложение для работы с фильмами подготовлено, но представление данных далеко от идеала. Вместо ReleaseDate должно стоять Дата выпуска, то есть два слова.
Обновление созданного кода
Откройте файл и добавьте выделенные Models/Movie.cs
строки, показанные в следующем коде:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.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; }
}
}
В предыдущем коде:
- Заметка к данным
[Column(TypeName = "decimal(18, 2)")]
позволяет Entity Framework Core корректно сопоставитьPrice
с валютой в базе данных. Дополнительные сведения см. в разделе Типы данных. - Атрибут [Display] указывает на отображаемое имя поля. В приведенном выше коде ReleaseDate заменен на "Дата выпуска".
- Атрибут [DataType] указывает тип данных (
Date
). Сведения о времени, хранящиеся в поле, не отображаются.
Пространство имен DataAnnotations будет рассмотрено в следующем руководстве.
Перейдите к Pages/Movies и наведите указатель мыши на ссылку Edit (Изменение), чтобы просмотреть целевой URL-адрес.
Ссылки Edit, Details и Delete создаются вспомогательной функцией тегов привязки в файле Pages/Movies/Index.cshtml
.
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
В приведенном выше коде Вспомогательная функция привязки тегов динамически создает значение атрибута HTML href
на основе Razor Page (маршрут является относительным), атрибут asp-page
и идентификатор маршрута (asp-route-id
). Дополнительные сведения см. в разделе Формирование URL-адресов для страниц.
Для проверки созданной разметки используйте в браузере параметр Просмотреть исходный код. Ниже показана часть созданного кода HTML:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
В динамически созданных ссылках идентификаторы фильмов передаются с помощью строки запроса. Например, ?id=1
в https://localhost:5001/Movies/Details?id=1
.
Добавление шаблона маршрута
Обновите страницы Razor Pages Edit, Details и Delete так, чтобы использовался шаблон маршрута {id:int}
. Измените директиву страницы для каждой из этих страниц c @page
на @page "{id:int}"
. Запустите приложение и просмотрите исходный код.
Созданный код HTML добавляет идентификатор в путь URL-адреса:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Запрос к странице с шаблоном маршрута {id:int}
, который не включает в себя целое число, приводит к ошибке HTTP 404 (не найдено). Например, https://localhost:5001/Movies/Details
приведет к ошибке 404. Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Чтобы проверить поведение @page "{id:int?}"
:
- Задайте директиву страницы в
Pages/Movies/Details.cshtml
как@page "{id:int?}"
. - Установите точку останова в
public async Task<IActionResult> OnGetAsync(int? id)
, вPages/Movies/Details.cshtml.cs
. - Перейдите к
https://localhost:5001/Movies/Details/
.
Из-за директивы @page "{id:int}"
точка останова не достигается. Механизм маршрутизации возвращает ошибку HTTP 404. При использовании @page "{id:int?}"
метод OnGetAsync
возвращает NotFound
(HTTP 404):
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
Проверка обработки исключений нежесткой блокировки
Просмотрите OnPostAsync
Pages/Movies/Edit.cshtml.cs
метод в файле:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.ID == id);
}
Предыдущий код обнаруживает исключения параллелизма, когда один клиент удаляет фильм, а другой вносит изменения в фильм.
Чтобы протестировать блок catch
, выполните указанные ниже действия.
- Задайте точку останова в
catch (DbUpdateConcurrencyException)
. - Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
- В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
- В первом окне браузера опубликуйте изменения для фильма.
Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.
Проверка публикации и привязки
Просмотрите файл Pages/Movies/Edit.cshtml.cs
.
public class EditModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public Movie Movie { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.ID == id);
}
При выполнении HTTP-запроса GET к странице Movies/Edit, например https://localhost:5001/Movies/Edit/3
, происходит следующее:
- Метод
OnGetAsync
извлекает запись фильма из базы данных и возвращает методPage
. - Метод
Page
отображает страницуPages/Movies/Edit.cshtml
Razor. ФайлPages/Movies/Edit.cshtml
содержит директиву@model RazorPagesMovie.Pages.Movies.EditModel
модели, которая делает модель фильма доступной на странице. - Отображается форма Edit со значениями из записи фильма.
При публикации страницы Movies/Edit происходит следующее:
Значения формы на странице привязываются к свойству
Movie
. Атрибут[BindProperty]
обеспечивает привязку модели.[BindProperty] public Movie Movie { get; set; }
При наличии ошибок в состоянии модели, например
ReleaseDate
невозможно преобразовать в дату, форма отображается повторно с предоставленными значениями.Если ошибки модели отсутствуют, данные фильма сохраняются.
Методы HTTP GET на страницах Razor Index, Create и Delete работают аналогично. Метод HTTP POST OnPostAsync
на странице Razor Create работает аналогично методу OnPostAsync
на странице Razor Edit.
Следующие шаги
ASP.NET Core