Часть 3. Razor Pages, созданные путем формирования шаблонов, в ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
Автор: Рик Андерсон (Rick Anderson)
Этот учебник описывает Razor Pages, созданные путем формирования шаблонов в предыдущем учебнике.
Страницы Create, Delete, Details и Edit
Проверьте модель страницы Pages/Movies/Index.cshtml.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; } = default!;
public async Task OnGetAsync()
{
Movie = await _context.Movie.ToListAsync();
}
}
}
Razor Pages являются производными от класса PageModel. Как правило, класс, производный от PageModel
, называется PageNameModel
. Например, страница Index называется IndexModel
.
Используя внедрение зависимостей, конструктор добавляет на страницу RazorPagesMovieContext
:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
Дополнительные сведения об асинхронном программировании с использованием Entity Framework см. в разделе Асинхронный код.
GET
При выполнении запроса на страницу метод возвращает список фильмов на страницу OnGetAsync
Razor. В Razor Page для инициализации состояния страницы вызывается OnGetAsync
или OnGet
. В этом случае OnGetAsync
возвращает список фильмов для отображения.
Когда OnGet
возвращает void
или OnGetAsync
возвращает Task
, оператор return не используется. Например, обратитесь к Privacy Page:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Если возвращаемый тип — IActionResult или Task<IActionResult>
, необходимо предоставить оператор return. Например, метод Pages/Movies/Create.cshtml.cs OnPostAsync
:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Проверьте страницу Pages/Movies/Index.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@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>
Razor может выполнять переход с HTML на C# или на разметку Razor. Если за символом @
следует зарезервированное ключевое слово Razor, он переходит на разметку Razor, а если нет, то на C#.
директиву @page
Директива Razor @page
преобразует файл в действие MVC, а значит, он может обрабатывать запросы. @page
должна быть первой директивой Razor на странице. @page
и @model
являются примерами перехода на разметку, относящуюся к Razor. Дополнительные сведения см. в статье Синтаксис Razor.
директиву @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
Директива @model
определяет тип модели, передаваемой на страницу Razor. В приведенном выше примере строка @model
делает класс, производный от PageModel
, доступным для Razor Page. Модель используется на странице во вспомогательных методах HTML @Html.DisplayNameFor
и @Html.DisplayFor
.
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title)
Вспомогательный метод HTML DisplayNameFor проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Лямбда-выражение проверяется, а не вычисляется. Это означает, что в случае, если model
, model.Movie
или model.Movie[0]
имеют значение null
или пусты, права доступа не нарушаются. При вычислении лямбда-выражения, например с помощью @Html.DisplayFor(modelItem => item.Title)
, вычисляются значения для свойств модели.
Страница макета
Выберите ссылки на меню RazorPagesMovie, Homeи Privacy. Меню на каждой странице имеют одинаковый макет. Макет меню реализуется в Pages/Shared/_Layout.cshtml
файле.
Откройте файл Pages/Shared/_Layout.cshtml
и проверьте его.
Шаблоны макета позволяют сделать следующее для макета контейнера HTML:
- указать его в одном расположении;
- применить его на нескольких страницах сайта.
Найдите строку @RenderBody()
. RenderBody
— это заполнитель для отображения всех представлений определенных страниц, упакованных в страницу макета. Например, выберите ссылку Privacy и Pages/Privacy.cshtml
представление отображается внутри RenderBody
метода.
ViewData и макет
Рассмотрим следующую разметку Pages/Movies/Index.cshtml
из файла:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
Выделенная выше разметка представляет собой пример перехода Razor на C#. Символы {
и }
ограничивают блок кода C#.
Базовый класс PageModel
содержит свойство словаря ViewData
. Оно позволяет передать данные в представление. Объекты добавляются в словарь ViewData
с помощью шаблона ключ — значение. В приведенном выше примере в словарь ViewData
добавляется свойство Title
.
Свойство Title
используется в файле Pages/Shared/_Layout.cshtml
. В следующей _Layout.cshtml
разметке показаны первые несколько строк файла.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-version="true" />
Обновление макета
Измените
<title>
элемент вPages/Shared/_Layout.cshtml
файле, чтобы отобразить Movie , а не RazorPagesMovie.<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Movie</title>
Найдите следующий элемент привязки
Pages/Shared/_Layout.cshtml
в файле.<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
Измените указанный выше элемент на следующую разметку.
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
Указанный выше элемент привязки является вспомогательной функцией тега. В данном случае он является вспомогательной функцией тега привязки. Атрибут вспомогательной функции тега
asp-page="/Movies/Index"
и его значение создают ссылку на страницу Razor/Movies/Index
. Атрибутasp-area
имеет пустое значение, поэтому эта область не используется в ссылке. Дополнительные сведения см. в статье Области.Сохраните изменения и протестируйте приложение, выбрав ссылку RpMovie. Если у вас возникли проблемы, ознакомьтесь с файлом _Layout.cshtml в GitHub.
Проверьте ссылки Home, RpMovie, Create, Edit и Delete. Каждая страница задает заголовок, который можно увидеть на вкладке браузера. При закладке страницы заголовок используется для закладки.
Примечание.
В поле Price
нельзя вводить десятичные запятые. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации приложения. Инструкции по добавлению десятичной запятой см. в вопросе № 4076 на сайте GitHub.
Свойство Layout
задается в файле Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Представленный выше код задает файл разметки Pages/Shared/_Layout.cshtml
для всех файлов Razor в папке Pages. Дополнительные сведения см. в статье о макете.
Страничная модель Create
Изучите Pages/Movies/Create.cshtml.cs
модель страницы:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; } = default!;
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Метод OnGet
инициализирует все состояния, необходимые для страницы. Страница Create не содержит никаких состояний для инициализации, поэтому возвращается Page
. Далее в этом руководстве показан пример инициализации состояния OnGet
. Метод Page
создает PageResult
объект, который отрисовывает страницу Create.cshtml
.
Для указания согласия на привязку модели в свойстве Movie
используется атрибут [BindProperty]. Когда форма Create публикует свои значения, среда выполнения ASP.NET Core связывает переданные значения с моделью Movie
.
Метод OnPostAsync
выполняется, когда страница публикует данные формы:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Если в модели есть ошибки, форма отображается снова вместе со всеми опубликованными данными этой формы. Большинство ошибок в модели может быть перехвачено на стороне клиента до публикации формы. Пример ошибки в модели — это публикация значения для поля даты, которое нельзя конвертировать в дату. Проверка на стороне клиента и проверка модели обсуждаются подробнее далее в этом учебнике.
Если нет ошибок модели:
- Данные сохранены.
- Браузер перенаправляется на страницу Index.
Страница Razor создания
Проверьте файл страницы Pages/Movies/Create.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio выделяет следующие теги полужирным шрифтом, который используется для вспомогательных функций тегов.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
Элемент <form method="post">
представляет собой вспомогательную функцию тега Form. Вспомогательная функция тега Form автоматически включает маркер защиты от подделки.
Ядро формирования шаблонов создает разметку Razor для каждого поля в модели (кроме ID) следующего вида:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательные функции тегов Validation (<div asp-validation-summary
и <span asp-validation-for
) отображают ошибки проверки. Более подробно проверка рассматривается далее в этой серии статей.
Вспомогательная функция тега Label (<label asp-for="Movie.Title" class="control-label"></label>
) создает подпись к метке и атрибут [for]
для свойства Title
.
Вспомогательная функция тега Input (<input asp-for="Movie.Title" class="form-control">
) использует атрибуты DataAnnotations и создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Дополнительные сведения о вспомогательных функциях тегов, таких как <form method="post">
, см. в статье Вспомогательные функции тегов в ASP.NET Core.
Следующие шаги
Страницы Create, Delete, Details и Edit
Проверьте модель страницы Pages/Movies/Index.cshtml.cs
:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies;
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; } = default!;
public async Task OnGetAsync()
{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}
Razor Pages являются производными от класса PageModel. Как правило, класс, производный от PageModel
, называется PageNameModel
. Например, страница Index называется IndexModel
.
Используя внедрение зависимостей, конструктор добавляет на страницу RazorPagesMovieContext
:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
Дополнительные сведения об асинхронном программировании с использованием Entity Framework см. в разделе Асинхронный код.
GET
При выполнении запроса на страницу метод возвращает список фильмов на страницу OnGetAsync
Razor. В Razor Page для инициализации состояния страницы вызывается OnGetAsync
или OnGet
. В этом случае OnGetAsync
возвращает список фильмов для отображения.
Когда OnGet
возвращает void
или OnGetAsync
возвращает Task
, оператор return не используется. Например, обратитесь к Privacy Page:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Если возвращаемый тип — IActionResult или Task<IActionResult>
, необходимо предоставить оператор return. Например, метод Pages/Movies/Create.cshtml.cs OnPostAsync
:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Проверьте страницу Pages/Movies/Index.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@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>
Razor может выполнять переход с HTML на C# или на разметку Razor. Если за символом @
следует зарезервированное ключевое слово Razor, он переходит на разметку Razor, а если нет, то на C#.
директиву @page
Директива Razor @page
преобразует файл в действие MVC, а значит, он может обрабатывать запросы. @page
должна быть первой директивой Razor на странице. @page
и @model
являются примерами перехода на разметку, относящуюся к Razor. Дополнительные сведения см. в статье Синтаксис Razor.
директиву @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
Директива @model
определяет тип модели, передаваемой на страницу Razor. В приведенном выше примере строка @model
делает класс, производный от PageModel
, доступным для Razor Page. Модель используется на странице во вспомогательных методах HTML @Html.DisplayNameFor
и @Html.DisplayFor
.
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title)
Вспомогательный метод HTML DisplayNameFor проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Лямбда-выражение проверяется, а не вычисляется. Это означает, что в случае, если model
, model.Movie
или model.Movie[0]
имеют значение null
или пусты, права доступа не нарушаются. При вычислении лямбда-выражения, например с помощью @Html.DisplayFor(modelItem => item.Title)
, вычисляются значения для свойств модели.
Страница макета
Выберите ссылки на меню RazorPagesMovie, Homeи Privacy. Меню на каждой странице имеют одинаковый макет. Макет меню реализуется в Pages/Shared/_Layout.cshtml
файле.
Откройте файл Pages/Shared/_Layout.cshtml
и проверьте его.
Шаблоны макета позволяют сделать следующее для макета контейнера HTML:
- указать его в одном расположении;
- применить его на нескольких страницах сайта.
Найдите строку @RenderBody()
. RenderBody
— это заполнитель для отображения всех представлений определенных страниц, упакованных в страницу макета. Например, выберите ссылку Privacy и Pages/Privacy.cshtml
представление отображается внутри RenderBody
метода.
ViewData и макет
Рассмотрим следующую разметку Pages/Movies/Index.cshtml
из файла:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
Выделенная выше разметка представляет собой пример перехода Razor на C#. Символы {
и }
ограничивают блок кода C#.
Базовый класс PageModel
содержит свойство словаря ViewData
. Оно позволяет передать данные в представление. Объекты добавляются в словарь ViewData
с помощью шаблона ключ — значение. В приведенном выше примере в словарь ViewData
добавляется свойство Title
.
Свойство Title
используется в файле Pages/Shared/_Layout.cshtml
. В следующей _Layout.cshtml
разметке показаны первые несколько строк файла.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-version="true" />
Обновление макета
Измените
<title>
элемент вPages/Shared/_Layout.cshtml
файле, чтобы отобразить Movie , а не RazorPagesMovie.<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Movie</title>
Найдите следующий элемент привязки
Pages/Shared/_Layout.cshtml
в файле.<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
Измените указанный выше элемент на следующую разметку.
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
Указанный выше элемент привязки является вспомогательной функцией тега. В данном случае он является вспомогательной функцией тега привязки. Атрибут вспомогательной функции тега
asp-page="/Movies/Index"
и его значение создают ссылку на страницу Razor/Movies/Index
. Атрибутasp-area
имеет пустое значение, поэтому эта область не используется в ссылке. Дополнительные сведения см. в статье Области.Сохраните изменения и протестируйте приложение, выбрав ссылку RpMovie. Если у вас возникли проблемы, ознакомьтесь с файлом _Layout.cshtml в GitHub.
Проверьте ссылки Home, RpMovie, Create, Edit и Delete. Каждая страница задает заголовок, который можно увидеть на вкладке браузера. При закладке страницы заголовок используется для закладки.
Примечание.
В поле Price
нельзя вводить десятичные запятые. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации приложения. Инструкции по добавлению десятичной запятой см. в вопросе № 4076 на сайте GitHub.
Свойство Layout
задается в файле Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Представленный выше код задает файл разметки Pages/Shared/_Layout.cshtml
для всех файлов Razor в папке Pages. Дополнительные сведения см. в статье о макете.
Страничная модель Create
Изучите Pages/Movies/Create.cshtml.cs
модель страницы:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; } = default!;
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Метод OnGet
инициализирует все состояния, необходимые для страницы. Страница Create не содержит никаких состояний для инициализации, поэтому возвращается Page
. Далее в этом руководстве показан пример инициализации состояния OnGet
. Метод Page
создает PageResult
объект, который отрисовывает страницу Create.cshtml
.
Для указания согласия на привязку модели в свойстве Movie
используется атрибут [BindProperty]. Когда форма Create публикует свои значения, среда выполнения ASP.NET Core связывает переданные значения с моделью Movie
.
Метод OnPostAsync
выполняется, когда страница публикует данные формы:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Если в модели есть ошибки, форма отображается снова вместе со всеми опубликованными данными этой формы. Большинство ошибок в модели может быть перехвачено на стороне клиента до публикации формы. Пример ошибки в модели — это публикация значения для поля даты, которое нельзя конвертировать в дату. Проверка на стороне клиента и проверка модели обсуждаются подробнее далее в этом учебнике.
Если нет ошибок модели:
- Данные сохранены.
- Браузер перенаправляется на страницу Index.
Страница Razor создания
Проверьте файл страницы Pages/Movies/Create.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio выделяет следующие теги полужирным шрифтом, который используется для вспомогательных функций тегов.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
Элемент <form method="post">
представляет собой вспомогательную функцию тега Form. Вспомогательная функция тега Form автоматически включает маркер защиты от подделки.
Ядро формирования шаблонов создает разметку Razor для каждого поля в модели (кроме ID) следующего вида:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательные функции тегов Validation (<div asp-validation-summary
и <span asp-validation-for
) отображают ошибки проверки. Более подробно проверка рассматривается далее в этой серии статей.
Вспомогательная функция тега Label (<label asp-for="Movie.Title" class="control-label"></label>
) создает подпись к метке и атрибут [for]
для свойства Title
.
Вспомогательная функция тега Input (<input asp-for="Movie.Title" class="form-control">
) использует атрибуты DataAnnotations и создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Дополнительные сведения о вспомогательных функциях тегов, таких как <form method="post">
, см. в статье Вспомогательные функции тегов в ASP.NET Core.
Следующие шаги
Страницы Create, Delete, Details и Edit
Проверьте модель страницы Pages/Movies/Index.cshtml.cs
:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies;
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; } = default!;
public async Task OnGetAsync()
{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}
Razor Pages являются производными от класса PageModel. Как правило, класс, производный от PageModel
, называется PageNameModel
. Например, страница Index называется IndexModel
.
Используя внедрение зависимостей, конструктор добавляет на страницу RazorPagesMovieContext
:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
Дополнительные сведения об асинхронном программировании с использованием Entity Framework см. в разделе Асинхронный код.
GET
При выполнении запроса на страницу метод возвращает список фильмов на страницу OnGetAsync
Razor. В Razor Page для инициализации состояния страницы вызывается OnGetAsync
или OnGet
. В этом случае OnGetAsync
возвращает список фильмов для отображения.
Когда OnGet
возвращает void
или OnGetAsync
возвращает Task
, оператор return не используется. Например, обратитесь к Privacy Page:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Если возвращаемый тип — IActionResult или Task<IActionResult>
, необходимо предоставить оператор return. Например, метод Pages/Movies/Create.cshtml.cs OnPostAsync
:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Проверьте страницу Pages/Movies/Index.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@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>
Razor может выполнять переход с HTML на C# или на разметку Razor. Если за символом @
следует зарезервированное ключевое слово Razor, он переходит на разметку Razor, а если нет, то на C#.
директиву @page
Директива Razor @page
преобразует файл в действие MVC, а значит, он может обрабатывать запросы. @page
должна быть первой директивой Razor на странице. @page
и @model
являются примерами перехода на разметку, относящуюся к Razor. Дополнительные сведения см. в статье Синтаксис Razor.
директиву @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
Директива @model
определяет тип модели, передаваемой на страницу Razor. В приведенном выше примере строка @model
делает класс, производный от PageModel
, доступным для Razor Page. Модель используется на странице во вспомогательных методах HTML @Html.DisplayNameFor
и @Html.DisplayFor
.
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title)
Вспомогательный метод HTML DisplayNameFor проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Лямбда-выражение проверяется, а не вычисляется. Это означает, что в случае, если model
, model.Movie
или model.Movie[0]
имеют значение null
или пусты, права доступа не нарушаются. При вычислении лямбда-выражения, например с помощью @Html.DisplayFor(modelItem => item.Title)
, вычисляются значения для свойств модели.
Страница макета
Выберите ссылки на меню RazorPagesMovie, Homeи Privacy. Меню на каждой странице имеют одинаковый макет. Макет меню реализуется в Pages/Shared/_Layout.cshtml
файле.
Откройте файл Pages/Shared/_Layout.cshtml
и проверьте его.
Шаблоны макета позволяют сделать следующее для макета контейнера HTML:
- указать его в одном расположении;
- применить его на нескольких страницах сайта.
Найдите строку @RenderBody()
. RenderBody
— это заполнитель для отображения всех представлений определенных страниц, упакованных в страницу макета. Например, выберите ссылку Privacy и Pages/Privacy.cshtml
представление отображается внутри RenderBody
метода.
ViewData и макет
Рассмотрим следующую разметку Pages/Movies/Index.cshtml
из файла:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
Выделенная выше разметка представляет собой пример перехода Razor на C#. Символы {
и }
ограничивают блок кода C#.
Базовый класс PageModel
содержит свойство словаря ViewData
. Оно позволяет передать данные в представление. Объекты добавляются в словарь ViewData
с помощью шаблона ключ — значение. В приведенном выше примере в словарь ViewData
добавляется свойство Title
.
Свойство Title
используется в файле Pages/Shared/_Layout.cshtml
. В следующей _Layout.cshtml
разметке показаны первые несколько строк файла.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-version="true" />
Строка @*Markup removed for brevity.*@
представляет собой комментарий Razor. В отличие от комментариев HTML <!-- -->
комментарии Razor не отправляются клиенту. Дополнительные сведения см . в веб-документах MDN: начало работы с HTML .
Обновление макета
Измените
<title>
элемент вPages/Shared/_Layout.cshtml
файле, чтобы отобразить Movie , а не RazorPagesMovie.<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Movie</title>
Найдите следующий элемент привязки
Pages/Shared/_Layout.cshtml
в файле.<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
Измените указанный выше элемент на следующую разметку.
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
Указанный выше элемент привязки является вспомогательной функцией тега. В данном случае он является вспомогательной функцией тега привязки. Атрибут вспомогательной функции тега
asp-page="/Movies/Index"
и его значение создают ссылку на страницу Razor/Movies/Index
. Атрибутasp-area
имеет пустое значение, поэтому эта область не используется в ссылке. Дополнительные сведения см. в статье Области.Сохраните изменения и протестируйте приложение, выбрав ссылку RpMovie. Если у вас возникли проблемы, ознакомьтесь с файлом _Layout.cshtml в GitHub.
Проверьте ссылки Home, RpMovie, Create, Edit и Delete. Каждая страница задает заголовок, который можно увидеть на вкладке браузера. При закладке страницы заголовок используется для закладки.
Примечание.
В поле Price
нельзя вводить десятичные запятые. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации приложения. Инструкции по добавлению десятичной запятой см. в вопросе № 4076 на сайте GitHub.
Свойство Layout
задается в файле Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Представленный выше код задает файл разметки Pages/Shared/_Layout.cshtml
для всех файлов Razor в папке Pages. Дополнительные сведения см. в статье о макете.
Страничная модель Create
Изучите Pages/Movies/Create.cshtml.cs
модель страницы:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; } = default!;
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Метод OnGet
инициализирует все состояния, необходимые для страницы. Страница Create не содержит никаких состояний для инициализации, поэтому возвращается Page
. Далее в этом руководстве показан пример инициализации состояния OnGet
. Метод Page
создает PageResult
объект, который отрисовывает страницу Create.cshtml
.
Для указания согласия на привязку модели в свойстве Movie
используется атрибут [BindProperty]. Когда форма Create публикует свои значения, среда выполнения ASP.NET Core связывает переданные значения с моделью Movie
.
Метод OnPostAsync
выполняется, когда страница публикует данные формы:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Если в модели есть ошибки, форма отображается снова вместе со всеми опубликованными данными этой формы. Большинство ошибок в модели может быть перехвачено на стороне клиента до публикации формы. Пример ошибки в модели — это публикация значения для поля даты, которое нельзя конвертировать в дату. Проверка на стороне клиента и проверка модели обсуждаются подробнее далее в этом учебнике.
Если нет ошибок модели:
- Данные сохранены.
- Браузер перенаправляется на страницу Index.
Страница Razor создания
Проверьте файл страницы Pages/Movies/Create.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio выделяет следующие теги полужирным шрифтом, который используется для вспомогательных функций тегов.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
Элемент <form method="post">
представляет собой вспомогательную функцию тега Form. Вспомогательная функция тега Form автоматически включает маркер защиты от подделки.
Ядро формирования шаблонов создает разметку Razor для каждого поля в модели (кроме ID) следующего вида:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательные функции тегов Validation (<div asp-validation-summary
и <span asp-validation-for
) отображают ошибки проверки. Более подробно проверка рассматривается далее в этой серии статей.
Вспомогательная функция тега Label (<label asp-for="Movie.Title" class="control-label"></label>
) создает подпись к метке и атрибут [for]
для свойства Title
.
Вспомогательная функция тега Input (<input asp-for="Movie.Title" class="form-control">
) использует атрибуты DataAnnotations и создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Дополнительные сведения о вспомогательных функциях тегов, таких как <form method="post">
, см. в статье Вспомогательные функции тегов в ASP.NET Core.
Следующие шаги
Страницы Create, Delete, Details и Edit
Проверьте модель страницы Pages/Movies/Index.cshtml.cs
:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; } = default!;
public async Task OnGetAsync()
{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}
}
Razor Pages являются производными от класса PageModel. Как правило, класс, производный от PageModel
, называется PageNameModel
. Например, страница Index называется IndexModel
.
Используя внедрение зависимостей, конструктор добавляет на страницу RazorPagesMovieContext
:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
Дополнительные сведения об асинхронном программировании с использованием Entity Framework см. в разделе Асинхронный код.
Когда к странице направляется запрос, метод OnGetAsync
возвращает на страницу Razor список фильмов. В Razor Page для инициализации состояния страницы вызывается OnGetAsync
или OnGet
. В этом случае OnGetAsync
возвращает список фильмов для отображения.
Когда OnGet
возвращает void
или OnGetAsync
возвращает Task
, оператор return не используется. Например, обратитесь к Privacy Page:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Если возвращаемый тип — IActionResult или Task<IActionResult>
, необходимо предоставить оператор return. Например, Pages/Movies/Create.cshtml.cs
OnPostAsync
метод:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Проверьте страницу Pages/Movies/Index.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@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>
Razor может выполнять переход с HTML на C# или на разметку Razor. Если за символом @
следует зарезервированное ключевое слово Razor, он переходит на разметку Razor, а если нет, то на C#.
директиву @page
Директива Razor @page
преобразует файл в действие MVC, а значит, он может обрабатывать запросы. @page
должна быть первой директивой Razor на странице. @page
и @model
являются примерами перехода на разметку, относящуюся к Razor. Дополнительные сведения см. в статье Синтаксис Razor.
директиву @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
Директива @model
определяет тип модели, передаваемой на страницу Razor. В приведенном выше примере строка @model
делает класс, производный от PageModel
, доступным для Razor Page. Модель используется на странице во вспомогательных методах HTML @Html.DisplayNameFor
и @Html.DisplayFor
.
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title)
Вспомогательный метод HTML DisplayNameFor проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Лямбда-выражение проверяется, а не вычисляется. Это означает, что в случае, если model
, model.Movie
или model.Movie[0]
имеют значение null
или пусты, права доступа не нарушаются. При вычислении лямбда-выражения, например с помощью @Html.DisplayFor(modelItem => item.Title)
, вычисляются значения для свойств модели.
Страница макета
Выберите ссылки на меню RazorPagesMovie, Homeи Privacy. Меню на каждой странице имеют одинаковый макет. Макет меню реализуется в Pages/Shared/_Layout.cshtml
файле.
Откройте файл Pages/Shared/_Layout.cshtml
и проверьте его.
Шаблоны макета позволяют сделать следующее для макета контейнера HTML:
- указать его в одном расположении;
- применить его на нескольких страницах сайта.
Найдите строку @RenderBody()
. RenderBody
— это заполнитель для отображения всех представлений определенных страниц, упакованных в страницу макета. Например, выберите ссылку Privacy и Pages/Privacy.cshtml
представление отображается внутри RenderBody
метода.
ViewData и макет
Рассмотрим следующую разметку Pages/Movies/Index.cshtml
из файла:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
Выделенная выше разметка представляет собой пример перехода Razor на C#. Символы {
и }
ограничивают блок кода C#.
Базовый класс PageModel
содержит свойство словаря ViewData
. Оно позволяет передать данные в представление. Объекты добавляются в словарь ViewData
с помощью шаблона ключ — значение. В приведенном выше примере в словарь ViewData
добавляется свойство Title
.
Свойство Title
используется в файле Pages/Shared/_Layout.cshtml
. В следующей _Layout.cshtml
разметке показаны первые несколько строк файла.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
@*Markup removed for brevity.*@
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
Строка @*Markup removed for brevity.*@
представляет собой комментарий Razor. В отличие от комментариев HTML <!-- -->
комментарии Razor не отправляются клиенту. Дополнительные сведения см . в веб-документах MDN: начало работы с HTML .
Обновление макета
Измените
<title>
элемент вPages/Shared/_Layout.cshtml
файле, чтобы отобразить Movie , а не RazorPagesMovie.<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Movie</title>
Найдите следующий элемент привязки
Pages/Shared/_Layout.cshtml
в файле.<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
Измените указанный выше элемент на следующую разметку.
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
Указанный выше элемент привязки является вспомогательной функцией тега. В данном случае он является вспомогательной функцией тега привязки. Атрибут вспомогательной функции тега
asp-page="/Movies/Index"
и его значение создают ссылку на страницу Razor/Movies/Index
. Атрибутasp-area
имеет пустое значение, поэтому эта область не используется в ссылке. Дополнительные сведения см. в статье Области.Сохраните изменения и протестируйте приложение, выбрав ссылку RpMovie. Если у вас возникли проблемы, ознакомьтесь с файлом _Layout.cshtml в GitHub.
Проверьте ссылки Home, RpMovie, Create, Edit и Delete. Каждая страница задает заголовок, который можно увидеть на вкладке браузера. При закладке страницы заголовок используется для закладки.
Примечание.
В поле Price
нельзя вводить десятичные запятые. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации приложения. Инструкции по добавлению десятичной запятой см. в вопросе № 4076 на сайте GitHub.
Свойство Layout
задается в файле Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Представленный выше код задает файл разметки Pages/Shared/_Layout.cshtml
для всех файлов Razor в папке Pages. Дополнительные сведения см. в статье о макете.
Страничная модель Create
Изучите Pages/Movies/Create.cshtml.cs
модель страницы:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; } = default!;
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Метод OnGet
инициализирует все состояния, необходимые для страницы. Страница Create не содержит никаких состояний для инициализации, поэтому возвращается Page
. Далее в этом руководстве показан пример инициализации состояния OnGet
. Метод Page
создает PageResult
объект, который отрисовывает страницу Create.cshtml
.
Для указания согласия на привязку модели в свойстве Movie
используется атрибут [BindProperty]. Когда форма Create публикует свои значения, среда выполнения ASP.NET Core связывает переданные значения с моделью Movie
.
Метод OnPostAsync
выполняется, когда страница публикует данные формы:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Если в модели есть ошибки, форма отображается снова вместе со всеми опубликованными данными этой формы. Большинство ошибок в модели может быть перехвачено на стороне клиента до публикации формы. Пример ошибки в модели — это публикация значения для поля даты, которое нельзя конвертировать в дату. Проверка на стороне клиента и проверка модели обсуждаются подробнее далее в этом учебнике.
Если нет ошибок модели:
- Данные сохранены.
- Браузер перенаправляется на страницу Index.
Страница Razor создания
Проверьте файл страницы Pages/Movies/Create.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio выделяет следующие теги полужирным шрифтом, который используется для вспомогательных функций тегов.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
Элемент <form method="post">
представляет собой вспомогательную функцию тега Form. Вспомогательная функция тега Form автоматически включает маркер защиты от подделки.
Ядро формирования шаблонов создает разметку Razor для каждого поля в модели (кроме ID) следующего вида:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательные функции тегов Validation (<div asp-validation-summary
и <span asp-validation-for
) отображают ошибки проверки. Более подробно проверка рассматривается далее в этой серии статей.
Вспомогательная функция тега Label (<label asp-for="Movie.Title" class="control-label"></label>
) создает подпись к метке и атрибут [for]
для свойства Title
.
Вспомогательная функция тега Input (<input asp-for="Movie.Title" class="form-control">
) использует атрибуты DataAnnotations и создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Дополнительные сведения о вспомогательных функциях тегов, таких как <form method="post">
, см. в статье Вспомогательные функции тегов в ASP.NET Core.
Следующие шаги
Страницы Create, Delete, Details и Edit
Проверьте модель страницы Pages/Movies/Index.cshtml.cs
:
// Unused usings removed.
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; }
public async Task OnGetAsync()
{
Movie = await _context.Movie.ToListAsync();
}
}
}
Razor Pages являются производными от класса PageModel
. Как правило, класс, производный от PageModel
, называется <PageName>Model
. Используя внедрение зависимостей, конструктор добавляет на страницу RazorPagesMovieContext
:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
Дополнительные сведения об асинхронном программировании с использованием Entity Framework см. в разделе Асинхронный код.
Когда к странице направляется запрос, метод OnGetAsync
возвращает на страницу Razor список фильмов. В Razor Page для инициализации состояния страницы вызывается OnGetAsync
или OnGet
. В этом случае OnGetAsync
возвращает список фильмов для отображения.
Когда OnGet
возвращает void
или OnGetAsync
возвращает Task
, оператор return не используется. Например, страница Privacy:
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
Если возвращаемый тип — IActionResult
или Task<IActionResult>
, необходимо предоставить оператор return. Например, Pages/Movies/Create.cshtml.cs
OnPostAsync
метод:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Проверьте страницу Pages/Movies/Index.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@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>
Razor может выполнять переход с HTML на C# или на разметку Razor. Если за символом @
следует зарезервированное ключевое слово Razor, он переходит на разметку Razor, а если нет, то на C#.
директиву @page
Директива Razor @page
преобразует файл в действие MVC, а значит, он может обрабатывать запросы. @page
должна быть первой директивой Razor на странице. @page
и @model
являются примерами перехода на разметку, относящуюся к Razor. Дополнительные сведения см. в статье Синтаксис Razor.
директиву @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
Директива @model
определяет тип модели, передаваемой на страницу Razor. В приведенном выше примере строка @model
делает класс, производный от PageModel
, доступным для страницы Razor. Модель используется на странице во вспомогательных методах HTML @Html.DisplayNameFor
и @Html.DisplayFor
.
Проверьте лямбда-выражение, которое используется в следующем вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title)
Вспомогательный метод HTML DisplayNameFor проверяет свойство Title
, указанное в лямбда-выражении, и определяет отображаемое имя. Лямбда-выражение проверяется, а не вычисляется. Это означает, что в случае, если model
, model.Movie
или model.Movie[0]
имеют значение null
или пусты, права доступа не нарушаются. При вычислении лямбда-выражения, например с помощью @Html.DisplayFor(modelItem => item.Title)
, вычисляются значения для свойств модели.
Страница макета
Выберите ссылки на меню RazorPagesMovie, Homeи Privacy. Меню на каждой странице имеют одинаковый макет. Макет меню реализуется в Pages/Shared/_Layout.cshtml
файле.
Откройте файл Pages/Shared/_Layout.cshtml
и проверьте его.
Шаблоны макета позволяют сделать следующее для макета контейнера HTML:
- указать его в одном расположении;
- применить его на нескольких страницах сайта.
Найдите строку @RenderBody()
. RenderBody
— это заполнитель для отображения всех представлений определенных страниц, упакованных в страницу макета. Например, выберите ссылку Privacy и Pages/Privacy.cshtml
представление отображается внутри RenderBody
метода.
ViewData и макет
Рассмотрим следующую разметку Pages/Movies/Index.cshtml
из файла:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
Выделенная выше разметка представляет собой пример перехода Razor на C#. Символы {
и }
ограничивают блок кода C#.
Базовый класс PageModel
содержит свойство словаря ViewData
. Оно позволяет передать данные в представление. Объекты добавляются в словарь ViewData
с помощью шаблона ключ — значение. В приведенном выше примере в словарь ViewData
добавляется свойство Title
.
Свойство Title
используется в файле Pages/Shared/_Layout.cshtml
. В следующей _Layout.cshtml
разметке показаны первые несколько строк файла.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
@*Markup removed for brevity.*@
Строка @*Markup removed for brevity.*@
представляет собой комментарий Razor. В отличие от комментариев HTML <!-- -->
комментарии Razor не отправляются клиенту. Дополнительные сведения см . в веб-документах MDN: начало работы с HTML .
Обновление макета
Измените
<title>
элемент вPages/Shared/_Layout.cshtml
файле, чтобы отобразить Movie , а не RazorPagesMovie.<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Movie</title>
Найдите следующий элемент привязки
Pages/Shared/_Layout.cshtml
в файле.<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
Измените указанный выше элемент на следующую разметку.
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
Указанный выше элемент привязки является вспомогательной функцией тега. В данном случае он является вспомогательной функцией тега привязки. Атрибут вспомогательной функции тега
asp-page="/Movies/Index"
и его значение создают ссылку на страницу Razor/Movies/Index
. Атрибутasp-area
имеет пустое значение, поэтому эта область не используется в ссылке. Дополнительные сведения см. в статье Области.Сохраните изменения и протестируйте приложение, выбрав ссылку RpMovie. Если у вас возникли проблемы, ознакомьтесь с файлом _Layout.cshtml в GitHub.
Проверьте ссылки Home, RpMovie, Create, Edit и Delete. Каждая страница задает заголовок, который можно увидеть на вкладке браузера. При закладке страницы заголовок используется для закладки.
Примечание.
В поле Price
нельзя вводить десятичные запятые. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации приложения. Инструкции по добавлению десятичной запятой см. в вопросе № 4076 на сайте GitHub.
Свойство Layout
задается в файле Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Представленный выше код задает файл разметки Pages/Shared/_Layout.cshtml
для всех файлов Razor в папке Pages. Дополнительные сведения см. в статье о макете.
Страничная модель Create
Изучите Pages/Movies/Create.cshtml.cs
модель страницы:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Метод OnGet
инициализирует все состояния, необходимые для страницы. Страница Create не содержит никаких состояний для инициализации, поэтому возвращается Page
. Далее в этом руководстве показан пример инициализации состояния OnGet
. Метод Page
создает PageResult
объект, который отрисовывает страницу Create.cshtml
.
Для указания согласия на привязку модели в свойстве Movie
используется атрибут [BindProperty]. Когда форма Create публикует свои значения, среда выполнения ASP.NET Core связывает переданные значения с моделью Movie
.
Метод OnPostAsync
выполняется, когда страница публикует данные формы:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Если в модели есть ошибки, форма отображается снова вместе со всеми опубликованными данными этой формы. Большинство ошибок в модели может быть перехвачено на стороне клиента до публикации формы. Пример ошибки в модели — это публикация значения для поля даты, которое нельзя конвертировать в дату. Проверка на стороне клиента и проверка модели обсуждаются подробнее далее в этом учебнике.
Если нет ошибок модели:
- Данные сохранены.
- Браузер перенаправляется на страницу Index.
Страница Razor создания
Проверьте файл страницы Pages/Movies/Create.cshtml
Razor:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio выделяет следующие теги полужирным шрифтом, который используется для вспомогательных функций тегов.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
Элемент <form method="post">
представляет собой вспомогательную функцию тега Form. Вспомогательная функция тега Form автоматически включает маркер защиты от подделки.
Ядро формирования шаблонов создает разметку Razor для каждого поля в модели (кроме ID) следующего вида:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательные функции тегов Validation (<div asp-validation-summary
и <span asp-validation-for
) отображают ошибки проверки. Более подробно проверка рассматривается далее в этой серии статей.
Вспомогательная функция тега Label (<label asp-for="Movie.Title" class="control-label"></label>
) создает подпись к метке и атрибут [for]
для свойства Title
.
Вспомогательная функция тега Input (<input asp-for="Movie.Title" class="form-control">
) использует атрибуты DataAnnotations и создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Дополнительные сведения о вспомогательных функциях тегов, таких как <form method="post">
, см. в статье Вспомогательные функции тегов в ASP.NET Core.
Следующие шаги
ASP.NET Core