Часть 5. Изменение созданных страниц в приложении ASP.NET Core

Примечание.

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

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

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

Каркасное приложение для фильмов является хорошим началом, но презентация не является идеальной. Вместо 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:7247/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:7247/Movies/Details возвращает ошибку 404. Чтобы сделать идентификатор необязательным, добавьте ? к ограничению маршрута:

@page "{id:int?}"

Чтобы проверить поведение @page "{id:int?}":

  1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "{id:int?}".
  2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int? id), в Pages/Movies/Details.cshtml.cs.
  3. Перейдите к https://localhost:7247/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();
}

Проверка обработки исключений нежесткой блокировки

Просмотрите OnPostAsyncPages/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, выполните указанные ниже действия.

  1. Задайте точку останова в catch (DbUpdateConcurrencyException).
  2. Выберите "Изменить " для фильма, внесите изменения, но не нажмите кнопку "Сохранить".
  3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
  4. В первом окне браузера опубликуйте изменения для фильма.

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

Проверка публикации и привязки

Просмотрите файл 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:7247/Movies/Edit/3

  • Метод OnGetAsync извлекает запись фильма из базы данных и возвращает метод Page.
  • Метод Page отображает страницу Pages/Movies/Edit.cshtmlRazor. Файл Pages/Movies/Edit.cshtml содержит директиву @model RazorPagesMovie.Pages.Movies.EditModelмодели, которая делает модель фильма доступной на странице.
  • Форма "Изменить" отображает значения из фильма.

При публикации 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 должно стоять Дата выпуска, то есть два слова.

Приложение Movie с данными по фильмам, открытое в Chrome

Обновление модели

Обновите 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 (Изменить), и URL-адресом ссылки https://localhost:1234/Movies/Edit/5

Ссылки 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?}":

  1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "{id:int?}".
  2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int? id), в Pages/Movies/Details.cshtml.cs.
  3. Перейдите к 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();
}

Проверка обработки исключений нежесткой блокировки

Просмотрите OnPostAsyncPages/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, выполните указанные ниже действия.

  1. Задайте точку останова в catch (DbUpdateConcurrencyException).
  2. Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
  3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
  4. В первом окне браузера опубликуйте изменения для фильма.

Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.

Проверка публикации и привязки

Просмотрите файл 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.cshtmlRazor. Файл 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 должно стоять Дата выпуска, то есть два слова.

Приложение Movie с данными по фильмам, открытое в Chrome

Обновление модели

Обновите 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 (Изменить), и URL-адресом ссылки https://localhost:1234/Movies/Edit/5

Ссылки 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?}":

  1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "{id:int?}".
  2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int? id), в Pages/Movies/Details.cshtml.cs.
  3. Перейдите к 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();
}

Проверка обработки исключений нежесткой блокировки

Просмотрите OnPostAsyncPages/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, выполните указанные ниже действия.

  1. Задайте точку останова в catch (DbUpdateConcurrencyException).
  2. Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
  3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
  4. В первом окне браузера опубликуйте изменения для фильма.

Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.

Проверка публикации и привязки

Просмотрите файл 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.cshtmlRazor. Файл 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 должно стоять Дата выпуска, то есть два слова.

Приложение Movie с данными по фильмам, открытое в Chrome

Обновление модели

Обновите 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 (Изменить), и URL-адресом ссылки https://localhost:1234/Movies/Edit/5

Ссылки 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?}":

  1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "{id:int?}".
  2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int? id), в Pages/Movies/Details.cshtml.cs.
  3. Перейдите к 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();
}

Проверка обработки исключений нежесткой блокировки

Просмотрите OnPostAsyncPages/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, выполните указанные ниже действия.

  1. Задайте точку останова в catch (DbUpdateConcurrencyException).
  2. Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
  3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
  4. В первом окне браузера опубликуйте изменения для фильма.

Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.

Проверка публикации и привязки

Просмотрите файл 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.cshtmlRazor. Файл 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 должно стоять Дата выпуска, то есть два слова.

Приложение Movie с данными по фильмам, открытое в Chrome

Обновление созданного кода

Обновите 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 (Изменить), и URL-адресом ссылки https://localhost:1234/Movies/Edit/5

Ссылки 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?}":

  1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "{id:int?}".
  2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int? id), в Pages/Movies/Details.cshtml.cs.
  3. Перейдите к 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();
}

Проверка обработки исключений нежесткой блокировки

Просмотрите OnPostAsyncPages/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, выполните указанные ниже действия.

  1. Задайте точку останова в catch (DbUpdateConcurrencyException).
  2. Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
  3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
  4. В первом окне браузера опубликуйте изменения для фильма.

Рабочий код может потребоваться обнаружить дополнительные конфликты параллелизма, такие как несколько клиентов, изменяя сущность одновременно. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.

Проверка публикации и привязки

Просмотрите файл 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.cshtmlRazor. Файл 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 должно стоять Дата выпуска, то есть два слова.

Приложение Movie с данными по фильмам, открытое в Chrome

Обновление созданного кода

Откройте файл и добавьте выделенные 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 (Изменить), и URL-адресом ссылки https://localhost:1234/Movies/Edit/5

Ссылки 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?}":

  1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "{id:int?}".
  2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int? id), в Pages/Movies/Details.cshtml.cs.
  3. Перейдите к 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();
}

Проверка обработки исключений нежесткой блокировки

Просмотрите OnPostAsyncPages/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, выполните указанные ниже действия.

  1. Задайте точку останова в catch (DbUpdateConcurrencyException).
  2. Выберите команду Изменить у фильма, внесите изменения, но не вводите Сохранить.
  3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем удалите его.
  4. В первом окне браузера опубликуйте изменения для фильма.

Коду в рабочей среде может потребоваться обнаружение конфликтов параллелизма. Дополнительные сведения см. в статье Обработка конфликтов параллелизма.

Проверка публикации и привязки

Просмотрите файл 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.cshtmlRazor. Файл 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.

Следующие шаги