Введение в Razor Pages в ASP.NET Core
Авторы: Рик Андерсон (Rick Anderson), Дейв Брок (Dave Brock) и Кирк Ларкин (Kirk Larkin)
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
Razor Pages делает создание кодов сценариев для страниц проще и эффективнее по сравнению с использованием контроллеров и представлений.
Если вам нужно руководство, использующее подход "модель-представление-контроллер", см. статью Начало работы с MVC в ASP.NET Core.
Этот документ содержит вводные сведения о Razor Pages. Это не пошаговое руководство. Если некоторые разделы покажутся вам слишком сложными, см. Начало работы с Razor Pages. Общие сведения об ASP.NET Core см. в разделе Введение в ASP.NET Core.
Необходимые компоненты
- Visual Studio 2022 с рабочей нагрузкой ASP.NET и веб-разработка.
- Пакет SDK для .NET 6.0
Создание проекта Razor Pages
Подробные инструкции по созданию проекта Razor Pages см. в статье Начало работы с Razor Pages.
Razor Pages
Razor Страницы включены в Program.cs
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
В предыдущем коде:
- AddRazorPages добавляет в приложение службы для Razor Pages.
- MapRazorPages добавляет в IEndpointRouteBuilder конечные точки для Razor Pages.
@page
<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>
Приведенный выше код выглядит как Razor файл представления, используемый в приложениях ASP.NET Core с контроллерами и представлениями. Он отличается от него только директивой @page
. @page
превращает файл в действие MVC, обрабатывая запросы напрямую без контроллера. @page
должна быть первой директивой Razor на странице. @page
влияет на поведение всех остальных конструкций Razor. Имена файлов Razor Pages содержат суффикс .cshtml
.
Похожая страница с использованием класса PageModel
показана в следующих двух файлах. Файл Pages/Index2.cshtml
:
@page
@using RazorPagesIntro.Pages
@model Index2Model
<h2>Separate page model</h2>
<p>
@Model.Message
</p>
Модель страницы Pages/Index2.cshtml.cs
:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
namespace RazorPagesIntro.Pages
{
public class Index2Model : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
public void OnGet()
{
Message += $" Server time is { DateTime.Now }";
}
}
}
По соглашению файл класса имеет то же имя, PageModel
что Razor и файл page с .cs
добавленным. Например, предыдущая страница Razor Pages называется Pages/Index2.cshtml
. Файл, содержащий класс PageModel
, называется Pages/Index2.cshtml.cs
.
Сопоставления URL-адресов со страницами определяются расположением конкретной страницы в файловой системе. В приведенной ниже таблице показаны пути Razor Pages и соответствующие URL-адреса.
Имя файла и путь | Соответствующий URL |
---|---|
/Pages/Index.cshtml |
/ или /Index |
/Pages/Contact.cshtml |
/Contact |
/Pages/Store/Contact.cshtml |
/Store/Contact |
/Pages/Store/Index.cshtml |
/Store или /Store/Index |
Примечания:
- Среда выполнения по умолчанию ищет файлы Razor Pages в папке Pages.
- Если в URL-адресе не указана конкретная страница, по умолчанию открывается страница
Index
.
Создание простой формы
Razor Pages предназначена для упрощения реализации типовых шаблонов, которые используются в браузерах, при создании приложения. Привязки моделей, вспомогательные функции тегов и вспомогательные методы HTML работают со свойствами, определенными в классе Razor Pages. Рассмотрим страницу с простой формой связи для модели Contact
.
Примеры в этом документе DbContext
инициализированы в файле Program.cs .
Для работы с базой данных в памяти требуется пакет NuGet Microsoft.EntityFrameworkCore.InMemory
.
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Модель данных:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string? Name { get; set; }
}
}
Контекст базы данных:
using Microsoft.EntityFrameworkCore;
namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext (DbContextOptions<CustomerDbContext> options)
: base(options)
{
}
public DbSet<RazorPagesContacts.Models.Customer> Customer => Set<RazorPagesContacts.Models.Customer>();
}
}
Файл представления Pages/Customers/Create.cshtml
:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
Модель страницы Pages/Customers/Create.cshtml.cs
:
public class CreateModel : PageModel
{
private readonly Data.CustomerDbContext _context;
public CreateModel(Data.CustomerDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Customer? Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
if (Customer != null) _context.Customer.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Как правило, класс PageModel
называется <PageName>Model
и находится в том же пространстве имен, что и страница.
Класс PageModel
позволяет разделять логику страницы и ее представление. Он определяет обработчики страницы для запросов, отправляемых на страницу, а также данные для ее визуализации. Это разделение позволяет:
- управлять зависимостями страниц с помощью внедрения зависимостей.
- Модульное тестирование
Страница содержит метод обработчика OnPostAsync
, который выполняется по запросам POST
(когда пользователь публикует форму). Можно добавить методы обработчика для любой HTTP-команды. Ниже перечислены наиболее распространенные обработчики.
OnGet
инициализирует нужное состояние для страницы. В приведенном выше коде методOnGet
отображает страницу RazorCreate.cshtml
.OnPost
обрабатывает отправку формы.
Суффикс имени Async
является необязательным, но часто применяется в соответствии с соглашением для асинхронных функций. Этот код типичен для Razor Pages.
Если вы знакомы с приложениями ASP.NET, использующими контроллеры и представления:
- Код
OnPostAsync
в предыдущем примере похож на стандартный код контроллера. - Большинство примитивов MVC, включая привязку модели, проверку и результаты действий, одинаково работают с контроллерами и Razor Pages.
Предыдущий метод OnPostAsync
:
[BindProperty]
public Customer? Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
if (Customer != null) _context.Customer.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Простая схема OnPostAsync
:
Проверка на наличие ошибок проверки.
- Если ошибок нет, сохранение данных и перенаправление.
- Если есть ошибки, отображение страницы с сообщениями проверки. Зачастую ошибки выявляются при проверке в клиенте и не передаются на сервер.
Файл представления Pages/Customers/Create.cshtml
:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
Отрисованный HTML из Pages/Customers/Create.cshtml
:
<p>Enter a customer name:</p>
<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum length of 10."
data-val-length-max="10" data-val-required="The Name field is required."
id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>
В приведенном выше коде, разместив форму:
С допустимыми данными:
Метод обработчика
OnPostAsync
вызывает вспомогательный метод RedirectToPage.RedirectToPage
возвращает экземпляр RedirectToPageResult.RedirectToPage
:- Является результатом действия.
- Аналогичен
RedirectToAction
илиRedirectToRoute
(используется в контроллерах и представлениях). - Настраивается для страниц. В приведенном выше примере он выполняет перенаправление на корневую страницу индекса (
/Index
). Более подробноRedirectToPage
рассматривается в разделе Создание URL для страниц.
С ошибками проверки, передаваемыми на сервер:
- Метод обработчика
OnPostAsync
вызывает вспомогательный метод Page.Page
возвращает экземпляр PageResult. ВозвращениеPage
аналогично тому, как действия в контроллерах возвращаютView
.PageResult
— тип возвращаемого значения по умолчанию для метода обработчика. Метод обработчика, вернувшийvoid
, визуализирует страницу. - В предыдущем примере публикация формы без значения приводит к тому, что ModelState.IsValid возвращает значение false. В этом примере на клиенте не отображаются ошибки проверки. Обработка ошибок проверки рассматривается далее в этом документе.
[BindProperty] public Customer? Customer { get; set; } public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } if (Customer != null) _context.Customer.Add(Customer); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); }
- Метод обработчика
С ошибками проверки, обнаруженными при проверке на стороне клиента:
- Данные не отправляются на сервер.
- Проверка на стороне клиента описана далее в этом документе.
Для указания согласия на привязку модели в свойстве Customer
используется атрибут [BindProperty]
:
[BindProperty]
public Customer? Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
if (Customer != null) _context.Customer.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
[BindProperty]
не следует использовать в моделях, содержащих свойства, которые не должны изменяться клиентом. Дополнительные сведения см. в этом разделе.
По умолчанию Razor привязывает свойства ко всем командам, кроме GET
. Привязка к свойствам устраняет необходимость в написании кода для преобразования данных HTTP в тип модели. Привязка уменьшает код за счет того, что для визуализации полей формы (<input asp-for="Customer.Name">
) и получения входных данных используется одно и то же свойство.
Предупреждение
В целях безопасности вам следует задать привязку данных запроса GET
к свойствам страничной модели. Проверьте введенные данные пользователя, прежде чем сопоставлять их со свойствами. Привязка GET
полезна при обращении к сценариям, использующим строку запроса или значения маршрутов.
Чтобы привязать свойство к запросам GET
, задайте для свойства SupportsGet
атрибута [BindProperty]
значение true
:
[BindProperty(SupportsGet = true)]
Дополнительные сведения: ASP.NET Core Community Standup: Bind on GET discussion (YouTube).
Pages/Customers/Create.cshtml
Просмотр файла представления:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
- В приведенном выше коде вспомогательная функция тега ввода
<input asp-for="Customer.Name" />
привязывает элемент<input>
HTML к выражению моделиCustomer.Name
. - Директива
@addTagHelper
обеспечивает доступность вспомогательных функций тегов.
Страница home
Index.cshtml
home— это страница:
@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Contacts home page</h1>
<form method="post">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
@if (Model.Customers != null)
{
foreach (var contact in Model.Customers)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<td>
<!-- <snippet_Edit> -->
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
<!-- </snippet_Edit> -->
<!-- <snippet_Delete> -->
<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
<!-- </snippet_Delete> -->
</td>
</tr>
}
}
</tbody>
</table>
<a asp-page="Create">Create New</a>
</form>
Связанный класс PageModel
(Index.cshtml.cs
):
public class IndexModel : PageModel
{
private readonly Data.CustomerDbContext _context;
public IndexModel(Data.CustomerDbContext context)
{
_context = context;
}
public IList<Customer>? Customers { get; set; }
public async Task OnGetAsync()
{
Customers = await _context.Customer.ToListAsync();
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _context.Customer.FindAsync(id);
if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
}
Файл Index.cshtml
содержит следующую разметку:
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
Вспомогательный тег привязки <a /a>
использовал атрибут asp-route-{value}
для создания ссылки на страницу редактирования. Эта ссылка содержит данные о маршруте с идентификатором контактного лица. Например, https://localhost:5001/Edit/1
. Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
Файл Index.cshtml
содержит разметку для создания кнопки удаления для каждого контакта клиента:
<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
Отображаемый HTML:
<button type="submit" formaction="/Customers?id=1&handler=delete">delete</button>
Во время обработки кнопки удаления в HTML ее formaction включает параметры для следующего:
- Идентификатор контакта клиента, указанный атрибутом
asp-route-id
. - Параметр
handler
, указанный атрибутомasp-page-handler
.
При выборе кнопки на сервер отправляется запрос формы POST
. По соглашению имя метода обработчика выбирается на основе значения параметра handler
в соответствии со схемой OnPost[handler]Async
.
Так как handler
— delete
в этом примере, метод обработчика OnPostDeleteAsync
используется для обработки запроса POST
. Если asp-page-handler
имеет другое значение, например remove
, выбирается метод обработчика с именем OnPostRemoveAsync
.
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _context.Customer.FindAsync(id);
if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
Метод OnPostDeleteAsync
:
- Получает
id
из строки запроса. - Отправляет в базу данных запрос контакта клиента с
FindAsync
. - Если контакт клиента найден, он удаляется, и база данных обновляется.
- Вызывает RedirectToPage для перенаправления на корневую страницу индекса (
/Index
).
Файл Edit.cshtml
@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Customer</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Customer!.Id" />
<div class="form-group">
<label asp-for="Customer!.Name" class="control-label"></label>
<input asp-for="Customer!.Name" class="form-control" />
<span asp-validation-for="Customer!.Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Первая строка содержит директиву @page "{id:int}"
. Ограничение маршрутизации "{id:int}"
указывает, что страница должна принимать обращенные к ней запросы, которые содержат данные маршрутизации int
. Если запрос к странице не содержит данные о маршруте, которые можно конвертировать в int
, среда выполнения возвращает ошибку HTTP 404 (не найдено). Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Файл Edit.cshtml.cs
:
public class EditModel : PageModel
{
private readonly RazorPagesContacts.Data.CustomerDbContext _context;
public EditModel(RazorPagesContacts.Data.CustomerDbContext context)
{
_context = context;
}
[BindProperty]
public Customer? Customer { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id == id);
if (Customer == null)
{
return NotFound();
}
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();
}
if (Customer != null)
{
_context.Attach(Customer).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(Customer.Id))
{
return NotFound();
}
else
{
throw;
}
}
}
return RedirectToPage("./Index");
}
private bool CustomerExists(int id)
{
return _context.Customer.Any(e => e.Id == id);
}
}
Проверка
Правила проверки:
- Декларативно задаются в классе Model.
- Применяются везде в приложении.
Пространство имен System.ComponentModel.DataAnnotations предоставляет набор встроенных атрибутов проверки, которые декларативно применяются к классу или свойству. Кроме того, DataAnnotations содержит атрибуты форматирования (такие как [DataType]
), которые обеспечивают форматирование и не предназначены для проверки.
Рассмотрим модель Customer
:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string? Name { get; set; }
}
}
С помощью следующего Create.cshtml
файла представления:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Validation: customer name:</p>
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer!.Name"></span>
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
Предыдущий код:
Включает jQuery и скрипты проверки jQuery.
Использует вспомогательные функции тегов
<div />
и<span />
для следующих задач:- Проверка на стороне клиента.
- Отображение ошибок при проверке.
Генерирует следующий HTML:
<p>Enter a customer name:</p> <form method="post"> Name: <input type="text" data-val="true" data-val-length="The field Name must be a string with a maximum length of 10." data-val-length-max="10" data-val-required="The Name field is required." id="Customer_Name" maxlength="10" name="Customer.Name" value="" /> <input type="submit" /> <input name="__RequestVerificationToken" type="hidden" value="<Antiforgery token here>" /> </form> <script src="/lib/jquery/dist/jquery.js"></script> <script src="/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
При публикации формы создания без значения имени в форме отображается сообщение об ошибке: "Поле имени является обязательным". Если на клиенте включен JavaScript, браузер отображает ошибку без отправки на сервер.
Атрибут [StringLength(10)]
создает data-val-length-max="10"
в отображаемом HTML-коде. data-val-length-max
не дает браузерам ввести больше заданной максимальной длины. Если для изменения и воспроизведения записи используется средство, например Fiddler, выполните следующие действия:
- С именем, превышающим 10.
- Возвращается сообщение об ошибке: "Имя поля должно быть строкой с максимальной длиной 10".
Рассмотрим следующую модель Movie
:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}
}
Атрибуты проверки определяют поведение для свойств модели, к которым они применяются:
Атрибуты
Required
иMinimumLength
указывают, что свойство должно иметь значение. Тем не менее, чтобы удовлетворить требованиям проверки, пользователю достаточно ввести пробел.Атрибут
RegularExpression
ограничивает набор допустимых для ввода символов. В приведенном выше коде в Genre:- должны использоваться только буквы;
- первая буква должна быть прописной; пробелы, цифры и специальные символы не допускаются.
В
RegularExpression
Rating:- первый символ должен быть прописной буквой;
- допускаются специальные символы и цифры, а также последующие пробелы. Значение "PG-13" допустимо для рейтинга, но недопустимо для жанра.
атрибут
Range
ограничивает значения указанным диапазоном.Атрибут
StringLength
задает максимальную и при необходимости минимальную длину строкового свойства.Типы значений (например,
decimal
,int
,float
,DateTime
) по своей природе являются обязательными и не требуют атрибута[Required]
.
На странице создания для модели Movie
отображаются ошибки с недопустимыми значениями:
Дополнительные сведения см. в разделе:
Изоляция CSS
Изоляция стилей CSS для отдельных страниц, представлений и компонентов, чтобы сократить или избежать следующего:
- Зависимости от глобальных стилей, которые могут быть сложными в обслуживании.
- Конфликты стилей во вложенном содержимом.
Чтобы добавить CSS-файл с заданной областью для страницы или представления, поместите стили CSS в сопутствующий файл .cshtml.css
, имя которого соответствует имени файла .cshtml
. В следующем примере файл Index.cshtml.css
предоставляет стили CSS, которые применяются только к представлению или странице Index.cshtml
.
Pages/Index.cshtml.css
(Razor Pages) или Views/Index.cshtml.css
(MVC):
h1 {
color: red;
}
Изоляция CSS выполняется во время сборки. Платформа повторно создает селекторы CSS в соответствии с разметкой, отображаемой на страницах или в представлениях приложения. Созданные повторно стили CSS объединяются и создаются как статический ресурс {APP ASSEMBLY}.styles.css
. Заполнитель {APP ASSEMBLY}
— это имя сборки проекта. Ссылка на объединенные стили CSS помещается в макет приложения.
В содержимом <head>
файла Pages/Shared/_Layout.cshtml
приложения (Razor Pages) или Views/Shared/_Layout.cshtml
(MVC) приложения добавьте ссылку на объединенные стили CSS или подтвердите ее наличие:
<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />
В следующем примере имя сборки приложения — WebApp
:
<link rel="stylesheet" href="WebApp.styles.css" />
Стили, определенные в файле CSS с заданной областью, применяются только к отображаемым выходным данным соответствующего файла. В предыдущем примере все объявления CSS h1
, определенные в других областях приложения, не будут конфликтовать со стилем заголовка Index
. Правила каскадов и наследования стилей CSS и дальше действуют для файлов CSS с заданной областью. Например, стили, примененные непосредственно к элементу <h1>
в файле Index.cshtml
, переопределяют стили файла CSS с заданной областью в Index.cshtml.css
.
Примечание.
Чтобы обеспечить изоляцию стиля CSS при выполнении объединения, импорт CSS в блоки кода Razor не поддерживается.
Изоляция CSS применяется только к элементам HTML. Изоляция CSS не поддерживается для вспомогательных функций тегов.
В объединенном файле CSS каждая страница, представление или компонент Razor связаны с идентификатором области в формате b-{STRING}
, где заполнитель {STRING}
представляет собой строку из десяти символов, сгенерированную платформой. В следующем примере представлен стиль для предыдущего элемента <h1>
на странице Index
приложения Razor Pages:
/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}
На странице Index
, где применяется стиль CSS из объединенного файла, идентификатор области добавляется как атрибут HTML:
<h1 b-3xxtam6d07>
Идентификатор является уникальным для приложения. Во время сборки создается пакет проектов с использованием соглашения {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css
, где заполнитель {STATIC WEB ASSETS BASE PATH}
является базовым путем к статическим веб-ресурсам.
Если используются другие проекты, такие как пакеты NuGet или библиотеки классов Razor, то объединенный файл:
- ссылается на стили с помощью импорта CSS;
- не публикуется как статический веб-ресурс приложения, использующего стили.
Поддержка препроцессоров CSS
Препроцессоры CSS полезны для улучшения разработки CSS с помощью таких функций, как переменные, вложенность, модули, примеси и наследование. Хотя изоляция CSS изначально не поддерживает препроцессоры CSS, такие как Sass и Less, интеграция препроцессоров CSS происходит без проблем, так как компиляция препроцессора выполняется до того, как платформа перезаписывает селекторы CSS в процессе сборки. С помощью Visual Studio, например, настройте существующую компиляцию препроцессора в качестве задачи перед сборкой в диспетчере выполнения задач Visual Studio.
Многие сторонние пакеты NuGet, такие как AspNetCore.SassCompiler
, могут компилировать файлы SASS и SCSS в начале процесса сборки перед выполнением изоляции CSS, и дополнительная настройка не требуется.
Конфигурация изоляции CSS
Изоляция CSS позволяет настраивать некоторые сложные сценарии, например включающих зависимости от существующих инструментов или рабочих процессов.
Настройка формата идентификатора области
В этом разделе заполнитель {Pages|Views}
— либо Pages
для приложений Razor Pages, либо Views
для приложений MVC.
По умолчанию идентификаторы областей используют формат b-{STRING}
, где заполнитель {STRING}
— это строка из десяти символов, сгенерированная платформой. Чтобы настроить формат идентификатора области, измените шаблон в файле проекта:
<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
В предыдущем примере CSS, сформированный для Index.cshtml.css
, изменяет свой идентификатор области с b-{STRING}
на custom-scope-identifier
.
Используйте идентификаторы областей, чтобы обеспечить наследование файлов CSS с назначенной областью. В следующем примере файла проекта файл BaseView.cshtml.css
содержит общие стили для представлений. Файл DerivedView.cshtml.css
наследует эти стили.
<ItemGroup>
<None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-identifier" />
<None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
Используйте оператор подстановочного знака (*
), чтобы предоставить идентификаторы областей для нескольких файлов:
<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
Изменение базового пути для статических веб-ресурсов
Файл CSS с заданной областью создается в корне приложения. Чтобы изменить путь по умолчанию, используйте свойство StaticWebAssetBasePath
в файле проекта. В следующем примере поместите css-файл с областью действия и rest ресурсы приложения по _content
пути:
<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>
Отключение автоматического объединения
Чтобы отказаться от того, как платформа публикует и загружает файлы с заданной областью во время выполнения, используйте свойство DisableScopedCssBundling
. При использовании этого свойства за получение изолированных файлов CSS из каталога obj
и их публикацию и загрузку во время выполнения отвечают другие средства или процессы:
<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>
Поддержка библиотеки классов Razor (RCL)
Когда библиотека классов Razor (RCL) предоставляет изолированные стили, атрибут href
тега <link>
указывает на {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css
со следующими заполнителями:
{STATIC WEB ASSET BASE PATH}
: базовый путь статического веб-ресурса.{PACKAGE ID}
: идентификатор пакета библиотеки. Если идентификатор пакета не указан в файле проекта, идентификатору пакета по умолчанию присваивается имя сборки проекта.
В следующем примере :
- Базовый путь к статическому веб-ресурсу —
_content/ClassLib
. - Имя сборки библиотеки классов —
ClassLib
.
Pages/Shared/_Layout.cshtml
(Razor Pages) или Views/Shared/_Layout.cshtml
(MVC):
<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">
Дополнительные сведения о RCL см. в следующих статьях:
- Многоразовый интерфейс Razor в библиотеках классов в ASP.NET Core
- Использование компонентов ASP.NET Core Razor из библиотеки классов Razor (RCL)
Сведения об изоляции CSS в Blazor см. в статье Изоляция CSS Blazor в ASP.NET Core.
Обработка запросов HEAD с помощью вызова резервного обработчика OnGet
Запросы HEAD
позволяют получать заголовки для определенного ресурса. В отличие от запросов GET
запросы HEAD
не возвращают текст ответа.
Обработчик OnHead
обычно создается и вызывается для выполнения запросов HEAD
:
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
Если обработчик OnHead
не определен, Razor Pages выполнит вызов обработчика OnGet
.
XSRF/CSRF и Razor Pages
В Razor Pages реализована проверка для защиты от подделки. FormTagHelper вставляет маркеры защиты от подделки в элементы HTML-форм.
Использование макетов, частичных реплик, шаблонов и вспомогательных функций тегов с Razor Pages
Pages работает со всеми функциями подсистемы просмотра Razor. Макеты, частичные реплики, шаблоны, вспомогательные функции тегов, _ViewStart.cshtml
и _ViewImports.cshtml
одинаково работают для обычных представлений Razor.
Давайте упростим нашу страницу с помощью некоторых из этих функций.
Добавьте страницу макета в Pages/Shared/_Layout.cshtml
:
<!DOCTYPE html>
<html>
<head>
<title>RP Sample</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<a asp-page="/Index">Home</a>
<a asp-page="/Customers/Create">Create</a>
<a asp-page="/Customers/Index">Customers</a> <br />
@RenderBody()
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>
Этот макет:
- управляет макетом каждой страницы (кроме страниц с отказом от макета);
- импортирует HTML-структуры, такие как JavaScript и таблицы стилей.
- Содержимое страницы Razor отображается в том месте, где вызывается
@RenderBody()
.
Дополнительные сведения см. здесь.
Свойство макета задается в Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Макет хранится в папке Pages/Shared. Pages ищет другие представления (макеты, шаблоны, частичные реплики) в иерархическом порядке, начиная с той папки, где находится текущая страница. Макет в папке Pages/Shared можно использовать на любой странице Razor, которая находится в папке Pages.
Файл макета следует поместить в папку Pages/Shared.
Корпорация Майкрософт рекомендует не размещать файл макета в папке Views/Shared. Views/Shared — это шаблон представлений MVC. Razor Pages опирается на иерархию папок, а не на условные обозначения путей.
Поиск представлений в Razor Pages охватывает папку Pages. Макеты, шаблоны и частичные реплики работают с контроллерами MVC и стандартными представлениями Razor.
Добавьте файл Pages/_ViewImports.cshtml
:
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@namespace
описывается далее в этом руководстве. Директива @addTagHelper
добавляет встроенные вспомогательные теги на все страницы в папке Pages.
Директива @namespace
, заданная на странице:
@page
@namespace RazorPagesIntro.Pages.Customers
@model NameSpaceModel
<h2>Name space</h2>
<p>
@Model.Message
</p>
Директива @namespace
задает пространство имен для страницы. Включать пространство имен в директиву @model
не требуется.
Если директива @namespace
содержится в файле _ViewImports.cshtml
, указанное пространство имен определяет префикс для созданного в Pages пространства имен, куда импортируется директива @namespace
. Созданное rest пространство имен (часть суффикса) — это разделенный точками относительный путь между папкой, содержащей и папкой, содержащей _ViewImports.cshtml
страницу.
Например, класс PageModel
в файле Pages/Customers/Edit.cshtml.cs
задает пространство имен явно:
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
public EditModel(AppDbContext db)
{
_db = db;
}
// Code removed for brevity.
Файл Pages/_ViewImports.cshtml
задает следующее пространство имен:
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Созданное пространство имен для файла Pages/Customers/Edit.cshtml
Razor Pages совпадает с пространством имен класса PageModel
.
@namespace
также работает со стандартными представлениями Razor.
Изучите файл представления Pages/Customers/Create.cshtml
:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Validation: customer name:</p>
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer!.Name"></span>
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
Обновленный файл представления Pages/Customers/Create.cshtml
с _ViewImports.cshtml
и предыдущим файлом макета:
@page
@model CreateModel
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
В приведенном выше коде _ViewImports.cshtml
импортировал пространство имен и вспомогательные функции тегов. Файл макета импортировал файлы JavaScript.
Начальный проект Razor Pages содержит Pages/_ValidationScriptsPartial.cshtml
, который обеспечивает проверку со стороны клиента.
Дополнительные сведения о частичных представлениях см. в статье Частичные представления в ASP.NET Core.
Формирование URL-адресов для страниц
На представленной выше странице Create
используется RedirectToPage
:
public class CreateModel : PageModel
{
private readonly Data.CustomerDbContext _context;
public CreateModel(Data.CustomerDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Customer? Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
if (Customer != null) _context.Customer.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Это приложение имеет следующую структуру файлов и папок:
Pages/
Index.cshtml
Privacy.cshtml
/Customers
Create.cshtml
Edit.cshtml
Index.cshtml
Страницы Pages/Customers/Create.cshtml
и Pages/Customers/Edit.cshtml
перенаправляются в Pages/Customers/Index.cshtml
после выполнения. Строка ./Index
— это относительное имя страницы, используемое для доступа к предыдущей странице. Он используется для создания URL-адресов страницы Pages/Customers/Index.cshtml
. Например:
Url.Page("./Index", ...)
<a asp-page="./Index">Customers Index Page</a>
RedirectToPage("./Index")
С помощью абсолютного имени страницы /Index
создаются URL-адреса страницы Pages/Index.cshtml
. Например:
Url.Page("/Index", ...)
<a asp-page="/Index">Home Index Page</a>
RedirectToPage("/Index")
Имя страницы — это путь к странице из корневой папки /Pages, включая начальный символ /
(например, /Index
). Предыдущие образцы создания URL-адреса обеспечивают расширенные параметры и функциональные возможности по сравнению с жестким заданием URL-адреса. Формирование URL-адресов включает маршрутизацию и позволяет генерировать и включать в код параметры в зависимости от того, как определяется маршрут в пути назначения.
Формирование URL-адресов для страниц поддерживает относительные имена. В приведенной ниже таблице показано, какая страница индекса выбирается с помощью разных параметров RedirectToPage
в Pages/Customers/Create.cshtml
.
RedirectToPage(x) | Стр. |
---|---|
RedirectToPage("/Index") | Pages/Index |
RedirectToPage("./Index"); | Pages/Customers/Index |
RedirectToPage("../Index") | Pages/Index |
RedirectToPage("Index") | Pages/Customers/Index |
RedirectToPage("Index")
, RedirectToPage("./Index")
и RedirectToPage("../Index")
— это относительные имена. Для получения имени целевой страницы параметр RedirectToPage
комбинируется с путем текущей страницы.
Привязка относительных имен полезна при создании сайтов со сложной структурой. Если относительные имена используются для связи между страницами в папке:
- Переименование папки не нарушает относительные ссылки.
- Ссылки не нарушаются, так как не содержат имя папки.
Чтобы выполнить перенаправление на страницу в другой области, укажите эту область:
RedirectToPage("/Index", new { area = "Services" });
Дополнительные сведения см. в статьях Области в ASP.NET Core и Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core.
Атрибут ViewData
Данные могут передаваться на страницу с помощью атрибута ViewDataAttribute. Значения свойств с атрибутом [ViewData]
хранятся в ViewDataDictionary и загружаются из него.
В следующем примере AboutModel
применяет атрибут [ViewData]
к свойству Title
:
public class AboutModel : PageModel
{
[ViewData]
public string Title { get; } = "About";
public void OnGet()
{
}
}
На странице About доступ к свойству Title
осуществляется как доступ к свойству модели.
<h1>@Model.Title</h1>
В макете заголовок считывается из словаря ViewData.
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
TempData
ASP.NET Core предоставляет TempData. Это свойство хранит данные до тех пор, пока они не будут прочитаны. Для проверки данных без удаления можно использовать методы Keep и Peek. TempData
удобно использовать для перенаправления, когда данные требуются больше, чем для одного запроса.
В следующем коде значение Message
задается с помощью TempData
:
public class CreateDotModel : PageModel
{
private readonly AppDbContext _db;
public CreateDotModel(AppDbContext db)
{
_db = db;
}
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
Представленная ниже разметка в файле Pages/Customers/Index.cshtml
отображает значение Message
с помощью TempData
.
<h3>Msg: @Model.Message</h3>
Модель страницы Pages/Customers/Index.cshtml.cs
применяет атрибут [TempData]
ко свойству Message
.
[TempData]
public string Message { get; set; }
Дополнительные сведения см. в разделе TempData.
Несколько обработчиков на страницу
Следующая страница формирует разметку для двух обработчиков с помощью вспомогательной функции тегов asp-page-handler
:
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></div>
<!-- <snippet_Handlers> -->
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
<!-- </snippet_Handlers> -->
</form>
</body>
</html>
Форма в предыдущем примере включает две кнопки отправки, каждая из которых отправляет данные на отдельный URL-адрес с помощью FormActionTagHelper
. Атрибут asp-page-handler
является дополнением к asp-page
. Атрибут asp-page-handler
формирует URL-адреса, ,которые используются для отправки данных в каждый из методов обработчиков, определенных страницей. asp-page
не задается, так как пример сопоставлен с текущей страницей.
Модель страницы :
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
namespace RazorPagesContacts.Pages.Customers
{
public class CreateFATHModel : PageModel
{
private readonly AppDbContext _db;
public CreateFATHModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostJoinListAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
public async Task<IActionResult> OnPostJoinListUCAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Customer.Name = Customer.Name?.ToUpperInvariant();
return await OnPostJoinListAsync();
}
}
}
В представленном выше коде используются именованные методы обработчика. Именованные методы обработчика создаются путем размещения определенного текста в имени после On<HTTP Verb>
и перед Async
(если есть). В приведенном выше примере использовались методы страницы OnPostJoinListAsync и OnPostJoinListUCAsync. Если убрать OnPost и Async, имена обработчиков будут выглядеть как JoinList
и JoinListUC
.
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
При использовании представленного выше кода URL-путь для отправки данных в OnPostJoinListAsync
будет выглядеть как https://localhost:5001/Customers/CreateFATH?handler=JoinList
. URL-путь для отправки данных в OnPostJoinListUCAsync
будет иметь вид https://localhost:5001/Customers/CreateFATH?handler=JoinListUC
.
Пользовательские маршруты
С помощью директивы @page
можно сделать следующее.
- Указать пользовательский маршрут к странице. Например, можно задать маршрут к странице "Сведения"
/Some/Other/Path
:@page "/Some/Other/Path"
. - Добавить сегменты к маршруту страницы по умолчанию. Например, к такому маршруту можно добавить сегмент item:
@page "item"
. - Добавить параметры к маршруту страницы по умолчанию. Например, для страницы с
@page "{id}"
может потребоваться параметр идентификатораid
.
Поддерживается путь относительно корня, заданный знаком тильды (~
) в начале пути. Например, @page "~/Some/Other/Path"
— это тоже самое, что и @page "/Some/Other/Path"
.
Если вы не хотите, чтобы в URL-адресе отображалась строка запроса ?handler=JoinList
, измените маршрут так, чтобы в качестве пути в URL-адресе указывалось имя обработчика. Для настройки маршрута добавьте после директивы @page
шаблон маршрута, заключенный в двойные кавычки.
@page "{handler?}"
@model CreateRouteModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>
При использовании представленного выше кода URL-путь для отправки данных в OnPostJoinListAsync
будет выглядеть как https://localhost:5001/Customers/CreateFATH/JoinList
. URL-путь для отправки данных в OnPostJoinListUCAsync
будет иметь вид https://localhost:5001/Customers/CreateFATH/JoinListUC
.
Символ ?
после handler
означает, что параметр маршрута является необязательным.
Совместное размещение файлов JavaScript (JS)
Совместное размещение файлов JavaScriptJS для страниц и представлений — удобный способ упорядочивания скриптов в приложении.
Совместное размещение файлов JS с использованием следующих соглашений о расширении имен файлов:
- Страницы приложений Razor Pages и представления приложений MVC:
.cshtml.js
. Примеры:Pages/Index.cshtml.js
для страницыIndex
приложения Razor Pages вPages/Index.cshtml
.Views/Home/Index.cshtml.js
для представленияIndex
приложения MVC вViews/Home/Index.cshtml
.
Совместно размещенные файлы JS являются общедоступными по пути к файлу в проекте:
Страницы и представления из файла скриптов с сортировкой в приложении:
{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
- Заполнитель
{PATH}
— это путь к странице, представлению или компоненту. - Заполнитель
{PAGE, VIEW, OR COMPONENT}
— это страница, представление или компонент. - Заполнитель
{EXTENSION}
соответствует расширению страницы, представления или компонента, либоrazor
илиcshtml
.
Пример Razor Pages:
Файл JS для страницы
Index
помещается в папкуPages
(Pages/Index.cshtml.js
) рядом со страницейIndex
(Pages/Index.cshtml
). На страницеIndex
на скрипт указана ссылка по пути в папкеPages
:@section Scripts { <script src="~/Pages/Index.cshtml.js"></script> }
- Заполнитель
Макет Pages/Shared/_Layout.cshtml
по умолчанию можно настроить для включения файлов с сортировкой JS , устраняя необходимость настройки каждой страницы по отдельности:
<script asp-src-include="@(ViewContext.View.Path).js"></script>
В примере скачивания используется приведенный выше фрагмент кода для включения файлов с сортировкой JS в макет по умолчанию.
При публикации приложения платформа автоматически перемещает скрипт в корневой каталог. В предыдущем примере скрипт перемещается в bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js
, где заполнитель {TARGET FRAMEWORK MONIKER}
замещен моникером целевой платформы (TFM). Относительный URL-адрес скрипта на странице Index
изменять не нужно.
При публикации приложения платформа автоматически перемещает скрипт в корневой каталог. В предыдущем примере скрипт перемещается в bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js
, где заполнитель {TARGET FRAMEWORK MONIKER}
замещен моникером целевой платформы (TFM). Относительный URL-адрес скрипта в компоненте Index
изменять не нужно.
Для скриптов, предоставленных библиотекой классов (RCL) Razor:
_content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
- Заполнитель
{PACKAGE ID}
— это идентификатор пакета RCL (или имя библиотеки для библиотеки классов, на которую ссылается приложение). - Заполнитель
{PATH}
— это путь к странице, представлению или компоненту. Если компонент Razor находится в корне RCL, сегмент пути не включается. - Заполнитель
{PAGE, VIEW, OR COMPONENT}
— это страница, представление или компонент. - Заполнитель
{EXTENSION}
соответствует расширению страницы, представления или компонента, либоrazor
илиcshtml
.
- Заполнитель
Расширенная конфигурация и параметры
Конфигурация и параметры в следующих разделах не требуются большинству приложений.
Для настройки расширенных параметров используйте перегрузку AddRazorPages, которая настраивает RazorPagesOptions:
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Используйте RazorPagesOptions, чтобы задавать корневой каталог для страниц и добавлять для них соглашения моделей приложений. Дополнительные сведения о соглашениях см. в разделе Соглашения об авторизации Razor Pages.
Сведения о предварительной компиляции представлений см. на странице Компиляция представлений Razor.
Указание местонахождения Razor Pages в корне каталога
По умолчанию Razor Pages находится в корне каталога /Pages. Добавьте WithRazorPagesAtContentRoot, чтобы указать, что Razor Pages находится в корневой папке содержимого (ContentRootPath) приложения:
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Указание местонахождения Razor Pages в пользовательском корневом каталоге
Добавьте WithRazorPagesRoot, чтобы указать, что Razor Pages находится в пользовательском корневом каталоге в приложении (укажите относительный путь):
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Дополнительные ресурсы
- Дополнительные общие сведения см. на странице Учебник. Начало работы с Razor Pages в ASP.NET Core.
- Атрибут authorize и Razor Pages
- Загрузить или просмотреть пример кода.
- Общие сведения об ASP.NET Core
- Справочник по синтаксису Razor для ASP.NET Core
- Области в ASP.NET Core
- Учебник. Начало работы с Razor Pages в ASP.NET Core
- Соглашения об авторизации Razor Pages в ASP.NET Core
- Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core
- Модульные тесты Razor Pages в ASP.NET Core
- Частичные представления в ASP.NET Core
- Visual Studio 2019 16.4 или более поздней версии с рабочей нагрузкой ASP.NET и разработка веб-приложений
- Пакет SDK для .NET Core 3.1
- Visual Studio 2019 16.8 или более поздней версии с рабочей нагрузкой ASP.NET и разработка веб-приложений
- Пакет SDK для .NET 5.0
Создание проекта Razor Pages
Подробные инструкции по созданию проекта Razor Pages см. в статье Начало работы с Razor Pages.
Razor Pages
Razor Страницы включены в Startup.cs
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
@page
<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>
Приведенный выше код выглядит как Razor файл представления, используемый в приложениях ASP.NET Core с контроллерами и представлениями. Он отличается от него только директивой @page
. Директива @page
превращает файл в действие MVC, а значит обрабатывает запросы напрямую, минуя контроллер. @page
должна быть первой директивой Razor на странице. @page
влияет на поведение всех остальных конструкций Razor. Имена файлов Razor Pages содержат суффикс .cshtml
.
Похожая страница с использованием класса PageModel
показана в следующих двух файлах. Файл Pages/Index2.cshtml
:
@page
@using RazorPagesIntro.Pages
@model Index2Model
<h2>Separate page model</h2>
<p>
@Model.Message
</p>
Модель страницы Pages/Index2.cshtml.cs
:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
namespace RazorPagesIntro.Pages
{
public class Index2Model : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
public void OnGet()
{
Message += $" Server time is { DateTime.Now }";
}
}
}
По соглашению файл класса имеет то же имя, PageModel
что Razor и файл page с .cs
добавленным. Например, предыдущая страница Razor Pages называется Pages/Index2.cshtml
. Файл, содержащий класс PageModel
, называется Pages/Index2.cshtml.cs
.
Сопоставления URL-адресов со страницами определяются расположением конкретной страницы в файловой системе. В приведенной ниже таблице показаны пути Razor Pages и соответствующие URL-адреса.
Имя файла и путь | Соответствующий URL |
---|---|
/Pages/Index.cshtml |
/ или /Index |
/Pages/Contact.cshtml |
/Contact |
/Pages/Store/Contact.cshtml |
/Store/Contact |
/Pages/Store/Index.cshtml |
/Store или /Store/Index |
Примечания:
- Среда выполнения по умолчанию ищет файлы Razor Pages в папке Pages.
- Если в URL-адресе не указана конкретная страница, по умолчанию открывается страница
Index
.
Создание простой формы
Razor Pages предназначена для упрощения реализации типовых шаблонов, которые используются в браузерах, при создании приложения. Привязки модели, вспомогательные функции тегов и вспомогательные методы HTML отлично работают со свойствами, определенными в классе Razor Pages. Рассмотрим страницу с простой формой связи для модели Contact
.
В представленных в этой статье примерах DbContext
инициализируется в файле Startup.cs.
Для работы с базой данных в памяти требуется пакет NuGet Microsoft.EntityFrameworkCore.InMemory
.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
services.AddRazorPages();
}
Модель данных:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string Name { get; set; }
}
}
Контекст базы данных:
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Models;
namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Customer> Customers { get; set; }
}
}
Файл представления Pages/Create.cshtml
:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
Модель страницы Pages/Create.cshtml.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using RazorPagesContacts.Models;
using System.Threading.Tasks;
namespace RazorPagesContacts.Pages.Customers
{
public class CreateModel : PageModel
{
private readonly CustomerDbContext _context;
public CreateModel(CustomerDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Как правило, класс PageModel
называется <PageName>Model
и находится в том же пространстве имен, что и страница.
Класс PageModel
позволяет разделять логику страницы и ее представление. Он определяет обработчики страницы для запросов, отправляемых на страницу, а также данные для ее визуализации. Это разделение позволяет:
- управлять зависимостями страниц с помощью внедрения зависимостей.
- Модульное тестирование
Страница содержит метод обработчика OnPostAsync
, который выполняется по запросам POST
(когда пользователь публикует форму). Можно добавить методы обработчика для любой HTTP-команды. Ниже перечислены наиболее распространенные обработчики.
OnGet
инициализирует нужное состояние для страницы. В приведенном выше коде методOnGet
отображает страницу RazorCreateModel.cshtml
.OnPost
обрабатывает отправку формы.
Суффикс имени Async
является необязательным, но часто применяется в соответствии с соглашением для асинхронных функций. Этот код типичен для Razor Pages.
Если вы знакомы с приложениями ASP.NET, использующими контроллеры и представления:
- Код
OnPostAsync
в предыдущем примере похож на стандартный код контроллера. - Большинство примитивов MVC, включая привязку модели, проверку и результаты действий, одинаково работают с контроллерами и Razor Pages.
Предыдущий метод OnPostAsync
:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Простая схема OnPostAsync
:
Проверка на наличие ошибок проверки.
- Если ошибок нет, сохранение данных и перенаправление.
- Если есть ошибки, отображение страницы с сообщениями проверки. Зачастую ошибки выявляются при проверке в клиенте и не передаются на сервер.
Файл представления Pages/Create.cshtml
:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
Отрисованный HTML из Pages/Create.cshtml
:
<p>Enter a customer name:</p>
<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum length of 10."
data-val-length-max="10" data-val-required="The Name field is required."
id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>
В приведенном выше коде, разместив форму:
С допустимыми данными:
Метод обработчика
OnPostAsync
вызывает вспомогательный метод RedirectToPage.RedirectToPage
возвращает экземпляр RedirectToPageResult.RedirectToPage
:- Является результатом действия.
- Аналогичен
RedirectToAction
илиRedirectToRoute
(используется в контроллерах и представлениях). - Настраивается для страниц. В приведенном выше примере он выполняет перенаправление на корневую страницу индекса (
/Index
). Более подробноRedirectToPage
рассматривается в разделе Создание URL для страниц.
С ошибками проверки, передаваемыми на сервер:
- Метод обработчика
OnPostAsync
вызывает вспомогательный метод Page.Page
возвращает экземпляр PageResult. ВозвращениеPage
аналогично тому, как действия в контроллерах возвращаютView
.PageResult
— тип возвращаемого значения по умолчанию для метода обработчика. Метод обработчика, вернувшийvoid
, визуализирует страницу. - В предыдущем примере публикация формы без значения приводит к тому, что ModelState.IsValid возвращает значение false. В этом примере на клиенте не отображаются ошибки проверки. Обработка ошибок проверки рассматривается далее в этом документе.
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } _context.Customers.Add(Customer); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); }
- Метод обработчика
С ошибками проверки, обнаруженными при проверке на стороне клиента:
- Данные не отправляются на сервер.
- Проверка на стороне клиента описана далее в этом документе.
Для указания согласия на привязку модели в свойстве Customer
используется атрибут [BindProperty]
:
public class CreateModel : PageModel
{
private readonly CustomerDbContext _context;
public CreateModel(CustomerDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
[BindProperty]
не следует использовать в моделях, содержащих свойства, которые не должны изменяться клиентом. Дополнительные сведения см. в этом разделе.
По умолчанию Razor привязывает свойства ко всем командам, кроме GET
. Привязка к свойствам устраняет необходимость в написании кода для преобразования данных HTTP в тип модели. Привязка уменьшает код за счет того, что для визуализации полей формы (<input asp-for="Customer.Name">
) и получения входных данных используется одно и то же свойство.
Предупреждение
В целях безопасности вам следует задать привязку данных запроса GET
к свойствам страничной модели. Проверьте введенные данные пользователя, прежде чем сопоставлять их со свойствами. Привязка GET
полезна при обращении к сценариям, использующим строку запроса или значения маршрутов.
Чтобы привязать свойство к запросам GET
, задайте для свойства SupportsGet
атрибута [BindProperty]
значение true
:
[BindProperty(SupportsGet = true)]
Дополнительные сведения: ASP.NET Core Community Standup: Bind on GET discussion (YouTube).
Pages/Create.cshtml
Просмотр файла представления:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
- В приведенном выше коде вспомогательная функция тега ввода
<input asp-for="Customer.Name" />
привязывает элемент<input>
HTML к выражению моделиCustomer.Name
. - Директива
@addTagHelper
обеспечивает доступность вспомогательных функций тегов.
Страница home
Index.cshtml
home— это страница:
@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Contacts home page</h1>
<form method="post">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var contact in Model.Customer)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<td>
<!-- <snippet_Edit> -->
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
<!-- </snippet_Edit> -->
<!-- <snippet_Delete> -->
<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
<!-- </snippet_Delete> -->
</td>
</tr>
}
</tbody>
</table>
<a asp-page="Create">Create New</a>
</form>
Связанный класс PageModel
(Index.cshtml.cs
):
public class IndexModel : PageModel
{
private readonly CustomerDbContext _context;
public IndexModel(CustomerDbContext context)
{
_context = context;
}
public IList<Customer> Customer { get; set; }
public async Task OnGetAsync()
{
Customer = await _context.Customers.ToListAsync();
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _context.Customers.FindAsync(id);
if (contact != null)
{
_context.Customers.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
}
Файл Index.cshtml
содержит следующую разметку:
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
Вспомогательный тег привязки <a /a>
использовал атрибут asp-route-{value}
для создания ссылки на страницу редактирования. Эта ссылка содержит данные о маршруте с идентификатором контактного лица. Например, https://localhost:5001/Edit/1
. Вспомогательные функции тегов позволяют серверному коду участвовать в создании и отображении HTML-элементов в файлах Razor.
Файл Index.cshtml
содержит разметку для создания кнопки удаления для каждого контакта клиента:
<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
Отображаемый HTML:
<button type="submit" formaction="/Customers?id=1&handler=delete">delete</button>
Во время обработки кнопки удаления в HTML ее formaction включает параметры для следующего:
- Идентификатор контакта клиента, указанный атрибутом
asp-route-id
. - Параметр
handler
, указанный атрибутомasp-page-handler
.
При выборе кнопки на сервер отправляется запрос формы POST
. По соглашению имя метода обработчика выбирается на основе значения параметра handler
в соответствии со схемой OnPost[handler]Async
.
Так как handler
— delete
в этом примере, метод обработчика OnPostDeleteAsync
используется для обработки запроса POST
. Если asp-page-handler
имеет другое значение, например remove
, выбирается метод обработчика с именем OnPostRemoveAsync
.
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _context.Customers.FindAsync(id);
if (contact != null)
{
_context.Customers.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
Метод OnPostDeleteAsync
:
- Получает
id
из строки запроса. - Отправляет в базу данных запрос контакта клиента с
FindAsync
. - Если контакт клиента найден, он удаляется, и база данных обновляется.
- Вызывает RedirectToPage для перенаправления на корневую страницу индекса (
/Index
).
Файл Edit.cshtml
@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
<div asp-validation-summary="All"></div>
<input asp-for="Customer.Id" type="hidden" />
<div>
<label asp-for="Customer.Name"></label>
<div>
<input asp-for="Customer.Name" />
<span asp-validation-for="Customer.Name"></span>
</div>
</div>
<div>
<button type="submit">Save</button>
</div>
</form>
Первая строка содержит директиву @page "{id:int}"
. Ограничение маршрутизации "{id:int}"
указывает, что страница должна принимать обращенные к ней запросы, которые содержат данные маршрутизации int
. Если запрос к странице не содержит данные о маршруте, которые можно конвертировать в int
, среда выполнения возвращает ошибку HTTP 404 (не найдено). Чтобы сделать идентификатор необязательным, добавьте ?
к ограничению маршрута:
@page "{id:int?}"
Файл Edit.cshtml.cs
:
public class EditModel : PageModel
{
private readonly CustomerDbContext _context;
public EditModel(CustomerDbContext context)
{
_context = context;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Customer = await _context.Customers.FindAsync(id);
if (Customer == null)
{
return RedirectToPage("./Index");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Customer).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw new Exception($"Customer {Customer.Id} not found!");
}
return RedirectToPage("./Index");
}
}
Проверка
Правила проверки:
- Декларативно задаются в классе Model.
- Применяются везде в приложении.
Пространство имен System.ComponentModel.DataAnnotations предоставляет набор встроенных атрибутов проверки, которые декларативно применяются к классу или свойству. Кроме того, DataAnnotations содержит атрибуты форматирования (такие как [DataType]
), которые обеспечивают форматирование и не предназначены для проверки.
Рассмотрим модель Customer
:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string Name { get; set; }
}
}
С помощью следующего Create.cshtml
файла представления:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Validation: customer name:</p>
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer.Name"></span>
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
Предыдущий код:
Включает jQuery и скрипты проверки jQuery.
Использует вспомогательные функции тегов
<div />
и<span />
для следующих задач:- Проверка на стороне клиента.
- Отображение ошибок при проверке.
Генерирует следующий HTML:
<p>Enter a customer name:</p> <form method="post"> Name: <input type="text" data-val="true" data-val-length="The field Name must be a string with a maximum length of 10." data-val-length-max="10" data-val-required="The Name field is required." id="Customer_Name" maxlength="10" name="Customer.Name" value="" /> <input type="submit" /> <input name="__RequestVerificationToken" type="hidden" value="<Antiforgery token here>" /> </form> <script src="/lib/jquery/dist/jquery.js"></script> <script src="/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
При публикации формы создания без значения имени в форме отображается сообщение об ошибке: "Поле имени является обязательным". Если на клиенте включен JavaScript, браузер отображает ошибку без отправки на сервер.
Атрибут [StringLength(10)]
создает data-val-length-max="10"
в отображаемом HTML-коде. data-val-length-max
не дает браузерам ввести больше заданной максимальной длины. Если для изменения и воспроизведения записи используется средство, например Fiddler, выполните следующие действия:
- С именем, превышающим 10.
- Возвращается сообщение об ошибке: "Имя поля должно быть строкой с максимальной длиной 10".
Рассмотрим следующую модель Movie
:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}
}
Атрибуты проверки определяют поведение для свойств модели, к которым они применяются:
Атрибуты
Required
иMinimumLength
указывают, что свойство должно иметь значение. Тем не менее, чтобы удовлетворить требованиям проверки, пользователю достаточно ввести пробел.Атрибут
RegularExpression
ограничивает набор допустимых для ввода символов. В приведенном выше коде в Genre:- должны использоваться только буквы;
- первая буква должна быть прописной; пробелы, цифры и специальные символы не допускаются.
В
RegularExpression
Rating:- первый символ должен быть прописной буквой;
- допускаются специальные символы и цифры, а также последующие пробелы. Значение "PG-13" допустимо для рейтинга, но недопустимо для жанра.
атрибут
Range
ограничивает значения указанным диапазоном.Атрибут
StringLength
задает максимальную и при необходимости минимальную длину строкового свойства.Типы значений (например,
decimal
,int
,float
,DateTime
) по своей природе являются обязательными и не требуют атрибута[Required]
.
На странице создания для модели Movie
отображаются ошибки с недопустимыми значениями:
Дополнительные сведения см. в разделе:
Обработка запросов HEAD с помощью вызова резервного обработчика OnGet
Запросы HEAD
позволяют получать заголовки для определенного ресурса. В отличие от запросов GET
запросы HEAD
не возвращают текст ответа.
Обработчик OnHead
обычно создается и вызывается для выполнения запросов HEAD
:
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
Если обработчик OnHead
не определен, Razor Pages выполнит вызов обработчика OnGet
.
XSRF/CSRF и Razor Pages
В Razor Pages реализована проверка для защиты от подделки. FormTagHelper вставляет маркеры защиты от подделки в элементы HTML-форм.
Использование макетов, частичных реплик, шаблонов и вспомогательных функций тегов с Razor Pages
Pages работает со всеми функциями подсистемы просмотра Razor. Макеты, частичные реплики, шаблоны, вспомогательные функции тегов, _ViewStart.cshtml
и _ViewImports.cshtml
одинаково работают для обычных представлений Razor.
Давайте упростим нашу страницу с помощью некоторых из этих функций.
Добавьте страницу макета в Pages/Shared/_Layout.cshtml
:
<!DOCTYPE html>
<html>
<head>
<title>RP Sample</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<a asp-page="/Index">Home</a>
<a asp-page="/Customers/Create">Create</a>
<a asp-page="/Customers/Index">Customers</a> <br />
@RenderBody()
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>
Этот макет:
- управляет макетом каждой страницы (кроме страниц с отказом от макета);
- импортирует HTML-структуры, такие как JavaScript и таблицы стилей.
- Содержимое страницы Razor отображается в том месте, где вызывается
@RenderBody()
.
Дополнительные сведения см. здесь.
Свойство макета задается в Pages/_ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Макет хранится в папке Pages/Shared. Pages ищет другие представления (макеты, шаблоны, частичные реплики) в иерархическом порядке, начиная с той папки, где находится текущая страница. Макет в папке Pages/Shared можно использовать на любой странице Razor, которая находится в папке Pages.
Файл макета следует поместить в папку Pages/Shared.
Корпорация Майкрософт рекомендует не размещать файл макета в папке Views/Shared. Views/Shared — это шаблон представлений MVC. Razor Pages опирается на иерархию папок, а не на условные обозначения путей.
Поиск представлений в Razor Pages охватывает папку Pages. Макеты, шаблоны и частичные реплики работают с контроллерами MVC и стандартными представлениями Razor.
Добавьте файл Pages/_ViewImports.cshtml
:
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@namespace
описывается далее в этом руководстве. Директива @addTagHelper
добавляет встроенные вспомогательные теги на все страницы в папке Pages.
Директива @namespace
, заданная на странице:
@page
@namespace RazorPagesIntro.Pages.Customers
@model NameSpaceModel
<h2>Name space</h2>
<p>
@Model.Message
</p>
Директива @namespace
задает пространство имен для страницы. Включать пространство имен в директиву @model
не требуется.
Если директива @namespace
содержится в файле _ViewImports.cshtml
, указанное пространство имен определяет префикс для созданного в Pages пространства имен, куда импортируется директива @namespace
. Созданное rest пространство имен (часть суффикса) — это разделенный точками относительный путь между папкой, содержащей и папкой, содержащей _ViewImports.cshtml
страницу.
Например, класс PageModel
в файле Pages/Customers/Edit.cshtml.cs
задает пространство имен явно:
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
public EditModel(AppDbContext db)
{
_db = db;
}
// Code removed for brevity.
Файл Pages/_ViewImports.cshtml
задает следующее пространство имен:
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Созданное пространство имен для файла Pages/Customers/Edit.cshtml
Razor Pages совпадает с пространством имен класса PageModel
.
@namespace
также работает со стандартными представлениями Razor.
Изучите файл представления Pages/Create.cshtml
:
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Validation: customer name:</p>
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer.Name"></span>
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
Обновленный файл представления Pages/Create.cshtml
с _ViewImports.cshtml
и предыдущим файлом макета:
@page
@model CreateModel
<p>Enter a customer name:</p>
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
В приведенном выше коде _ViewImports.cshtml
импортировал пространство имен и вспомогательные функции тегов. Файл макета импортировал файлы JavaScript.
Начальный проект Razor Pages содержит Pages/_ValidationScriptsPartial.cshtml
, который обеспечивает проверку со стороны клиента.
Дополнительные сведения о частичных представлениях см. в статье Частичные представления в ASP.NET Core.
Формирование URL-адресов для страниц
На представленной выше странице Create
используется RedirectToPage
:
public class CreateModel : PageModel
{
private readonly CustomerDbContext _context;
public CreateModel(CustomerDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Это приложение имеет следующую структуру файлов и папок:
Pages/
Index.cshtml
Privacy.cshtml
/Customers
Create.cshtml
Edit.cshtml
Index.cshtml
Страницы Pages/Customers/Create.cshtml
и Pages/Customers/Edit.cshtml
перенаправляются в Pages/Customers/Index.cshtml
после выполнения. Строка ./Index
— это относительное имя страницы, используемое для доступа к предыдущей странице. Он используется для создания URL-адресов страницы Pages/Customers/Index.cshtml
. Например:
Url.Page("./Index", ...)
<a asp-page="./Index">Customers Index Page</a>
RedirectToPage("./Index")
С помощью абсолютного имени страницы /Index
создаются URL-адреса страницы Pages/Index.cshtml
. Например:
Url.Page("/Index", ...)
<a asp-page="/Index">Home Index Page</a>
RedirectToPage("/Index")
Имя страницы — это путь к странице из корневой папки /Pages, включая начальный символ /
(например, /Index
). Предыдущие образцы создания URL-адреса обеспечивают расширенные параметры и функциональные возможности по сравнению с жестким заданием URL-адреса. Формирование URL-адресов включает маршрутизацию и позволяет генерировать и включать в код параметры в зависимости от того, как определяется маршрут в пути назначения.
Формирование URL-адресов для страниц поддерживает относительные имена. В приведенной ниже таблице показано, какая страница индекса выбирается с помощью разных параметров RedirectToPage
в Pages/Customers/Create.cshtml
.
RedirectToPage(x) | Стр. |
---|---|
RedirectToPage("/Index") | Pages/Index |
RedirectToPage("./Index"); | Pages/Customers/Index |
RedirectToPage("../Index") | Pages/Index |
RedirectToPage("Index") | Pages/Customers/Index |
RedirectToPage("Index")
, RedirectToPage("./Index")
и RedirectToPage("../Index")
— это относительные имена. Для получения имени целевой страницы параметр RedirectToPage
комбинируется с путем текущей страницы.
Привязка относительных имен полезна при создании сайтов со сложной структурой. Если относительные имена используются для связи между страницами в папке:
- Переименование папки не нарушает относительные ссылки.
- Ссылки не нарушаются, так как не содержат имя папки.
Чтобы выполнить перенаправление на страницу в другой области, укажите эту область:
RedirectToPage("/Index", new { area = "Services" });
Дополнительные сведения см. в статьях Области в ASP.NET Core и Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core.
Атрибут ViewData
Данные могут передаваться на страницу с помощью атрибута ViewDataAttribute. Значения свойств с атрибутом [ViewData]
хранятся в ViewDataDictionary и загружаются из него.
В следующем примере AboutModel
применяет атрибут [ViewData]
к свойству Title
:
public class AboutModel : PageModel
{
[ViewData]
public string Title { get; } = "About";
public void OnGet()
{
}
}
На странице About доступ к свойству Title
осуществляется как доступ к свойству модели.
<h1>@Model.Title</h1>
В макете заголовок считывается из словаря ViewData.
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
TempData
ASP.NET Core предоставляет TempData. Это свойство хранит данные до тех пор, пока они не будут прочитаны. Для проверки данных без удаления можно использовать методы Keep и Peek. TempData
удобно использовать для перенаправления, когда данные требуются больше, чем для одного запроса.
В следующем коде значение Message
задается с помощью TempData
:
public class CreateDotModel : PageModel
{
private readonly AppDbContext _db;
public CreateDotModel(AppDbContext db)
{
_db = db;
}
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
Представленная ниже разметка в файле Pages/Customers/Index.cshtml
отображает значение Message
с помощью TempData
.
<h3>Msg: @Model.Message</h3>
Модель страницы Pages/Customers/Index.cshtml.cs
применяет атрибут [TempData]
ко свойству Message
.
[TempData]
public string Message { get; set; }
Дополнительные сведения см. в разделе TempData.
Несколько обработчиков на страницу
Следующая страница формирует разметку для двух обработчиков с помощью вспомогательной функции тегов asp-page-handler
:
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></div>
<!-- <snippet_Handlers> -->
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
<!-- </snippet_Handlers> -->
</form>
</body>
</html>
Форма в предыдущем примере включает две кнопки отправки, каждая из которых отправляет данные на отдельный URL-адрес с помощью FormActionTagHelper
. Атрибут asp-page-handler
является дополнением к asp-page
. Атрибут asp-page-handler
формирует URL-адреса, ,которые используются для отправки данных в каждый из методов обработчиков, определенных страницей. asp-page
не задается, так как пример сопоставлен с текущей страницей.
Модель страницы :
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
namespace RazorPagesContacts.Pages.Customers
{
public class CreateFATHModel : PageModel
{
private readonly AppDbContext _db;
public CreateFATHModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostJoinListAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
public async Task<IActionResult> OnPostJoinListUCAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Customer.Name = Customer.Name?.ToUpperInvariant();
return await OnPostJoinListAsync();
}
}
}
В представленном выше коде используются именованные методы обработчика. Именованные методы обработчика создаются путем размещения определенного текста в имени после On<HTTP Verb>
и перед Async
(если есть). В приведенном выше примере использовались методы страницы OnPostJoinListAsync и OnPostJoinListUCAsync. Если убрать OnPost и Async, имена обработчиков будут выглядеть как JoinList
и JoinListUC
.
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
При использовании представленного выше кода URL-путь для отправки данных в OnPostJoinListAsync
будет выглядеть как https://localhost:5001/Customers/CreateFATH?handler=JoinList
. URL-путь для отправки данных в OnPostJoinListUCAsync
будет иметь вид https://localhost:5001/Customers/CreateFATH?handler=JoinListUC
.
Пользовательские маршруты
С помощью директивы @page
можно сделать следующее.
- Указать пользовательский маршрут к странице. Например, можно задать маршрут к странице "Сведения"
/Some/Other/Path
:@page "/Some/Other/Path"
. - Добавить сегменты к маршруту страницы по умолчанию. Например, к такому маршруту можно добавить сегмент item:
@page "item"
. - Добавить параметры к маршруту страницы по умолчанию. Например, для страницы с
@page "{id}"
может потребоваться параметр идентификатораid
.
Поддерживается путь относительно корня, заданный знаком тильды (~
) в начале пути. Например, @page "~/Some/Other/Path"
— это тоже самое, что и @page "/Some/Other/Path"
.
Если вы не хотите, чтобы в URL-адресе отображалась строка запроса ?handler=JoinList
, измените маршрут так, чтобы в качестве пути в URL-адресе указывалось имя обработчика. Для настройки маршрута добавьте после директивы @page
шаблон маршрута, заключенный в двойные кавычки.
@page "{handler?}"
@model CreateRouteModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>
При использовании представленного выше кода URL-путь для отправки данных в OnPostJoinListAsync
будет выглядеть как https://localhost:5001/Customers/CreateFATH/JoinList
. URL-путь для отправки данных в OnPostJoinListUCAsync
будет иметь вид https://localhost:5001/Customers/CreateFATH/JoinListUC
.
Символ ?
после handler
означает, что параметр маршрута является необязательным.
Расширенная конфигурация и параметры
Конфигурация и параметры в следующих разделах не требуются большинству приложений.
Для настройки расширенных параметров используйте перегрузку AddRazorPages, которая настраивает RazorPagesOptions:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});
}
Используйте RazorPagesOptions, чтобы задавать корневой каталог для страниц и добавлять для них соглашения моделей приложений. Дополнительные сведения о соглашениях см. в разделе Соглашения об авторизации Razor Pages.
Сведения о предварительной компиляции представлений см. на странице Компиляция представлений Razor.
Указание местонахождения Razor Pages в корне каталога
По умолчанию Razor Pages находится в корне каталога /Pages. Добавьте WithRazorPagesAtContentRoot, чтобы указать, что Razor Pages находится в корневой папке содержимого (ContentRootPath) приложения:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();
}
Указание местонахождения Razor Pages в пользовательском корневом каталоге
Добавьте WithRazorPagesRoot, чтобы указать, что Razor Pages находится в пользовательском корневом каталоге в приложении (укажите относительный путь):
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");
}
Дополнительные ресурсы
- Дополнительные общие сведения см. на странице Учебник. Начало работы с Razor Pages в ASP.NET Core.
- Атрибут authorize и Razor Pages
- Загрузить или просмотреть пример кода.
- Общие сведения об ASP.NET Core
- Справочник по синтаксису Razor для ASP.NET Core
- Области в ASP.NET Core
- Учебник. Начало работы с Razor Pages в ASP.NET Core
- Соглашения об авторизации Razor Pages в ASP.NET Core
- Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core
- Модульные тесты Razor Pages в ASP.NET Core
- Частичные представления в ASP.NET Core
ASP.NET Core