作成者: Rick Anderson、Dave Brock、Kirk Larkin
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
Warning
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
Important
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
Razor ページを利用することで、ページのコーディングに今まで以上に集中できます。また、コントローラーとビューを使用する場合より生産的になります。
モデル ビュー コントローラーのアプローチを使用するチュートリアルをお探しの場合は、「Get started with ASP.NET Core MVC」 (ASP.NET Core MVC の概要) を参照してください。
この記事では、ページに重点を置いた Web アプリケーションの構築に Razor Pages を有効にするアーキテクチャ、概念、パターンについて説明します。 Razor Pages のしくみ、その主要なコンポーネント、実装のベスト プラクティスについて説明します。 詳細な手順を使用した実践的な学習を希望する場合は、「 チュートリアル: ASP.NET Core を使用して Razor Pages Web アプリを作成する」を参照してください。 ASP.NET Core の概要については、「ASP.NET Core の概要」を参照してください。
Prerequisites
- Visual Studio 2022 と ASP.NET と Web 開発ワークロード。
- .NET 6 SDK
Razor ページ プロジェクトを作成する
Razor ページ
Razor Pages は 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 により、Razor Pages のエンドポイントが IEndpointRouteBuilder に追加されます。
@page
<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>
上記のコードは、コントローラーとビューを含んだ ASP.NET Core アプリで使われる Razor ビュー ファイルによく似ています。 違いは、@page ディレクティブにあります。
@page はファイルを MVC アクションにします。つまり、コントローラーを経由せずに要求を直接処理します。
@page はページ上で最初の Razor ディレクティブである必要があります。
@page はその他の Razor コンストラクトの動作に影響します。
Razor ページのファイル名には ".cshtml" サフィックスが付きます。
PageModel クラスを使用している類似したページが、次の 2 つのファイルにあります。
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 ページ ファイルと同じ名前に " .cs" が付加された名前になります。 たとえば、上の Razor ページは Pages/Index2.cshtml になります。
PageModel クラスを含むファイル名は、Pages/Index2.cshtml.cs になります。
URL パスのページへの関連付けは、ファイル システム内のページの場所によって決定されます。 次の表に、Razor ページ パスと一致 URL を示します。
| ファイル名とパス | 一致する URL |
|---|---|
/Pages/Index.cshtml |
/ または /Index |
/Pages/Contact.cshtml |
/Contact |
/Pages/Store/Contact.cshtml |
/Store/Contact |
/Pages/Store/Index.cshtml |
/Store または /Store/Index |
Notes:
- 既定では、ランタイムが "Razor" フォルダー内で ページ ファイルを検索します。
-
Indexは、URL にページが含まれない場合の既定のページになります。
基本フォームを作成する
Razor ページは、アプリの構築時に Web ブラウザーで使用される一般的なパターンを実装しやすくするために設計されています。
モデル バインド、タグ ヘルパー、HTML ヘルパーは、Razor Page クラスで定義されたプロパティで機能します。
Contact モデルの基本的な "お問い合わせ" フォームを実装するページを考察します。
このドキュメントのサンプルでは、DbContext ファイルで が初期化されます。
インメモリ データベースには、Microsoft.EntityFrameworkCore.InMemory NuGet パッケージが必要です。
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; }
}
}
db コンテキスト:
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メソッドによりCreate.cshtmlRazor ページが表示されます。 - フォームの送信を処理するための
OnPost。
Async 名前付けサフィックスは省略可能ですが、非同期関数の規則でよく使用されます。 上記のコードは、Razor ページでは一般的です。
コントローラーとビューを利用する ASP.NET アプリに慣れている場合:
- 前の例の
OnPostAsyncコードは、一般的なコントローラー コードに似ています。 - モデル バインド、検証、アクションの結果など、MVC プリミティブのほとんどは Controllers ページや Razor ページと同じように動作します。
上記の 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>
Pages/Customers/Create.cshtml から表示される 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>
前のコードで投稿したフォーム:
有効なデータ:
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">) からレンダリングして入力を受け入れます。
Warning
セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。
GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。
GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。
[BindProperty(SupportsGet = true)]
詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET ディスカッション (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" />によって HTML<input>要素がCustomer.Nameモデル式にバインドされます。 -
@addTagHelperでタグ ヘルパーを使用可能にします。
ホーム ページ
Index.cshtml はホーム ページです。
@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} 属性を使用して編集ページへのリンクが生成されました。 リンクには、連絡先 ID とともにルート データが含まれています。 たとえば、https://localhost:5001/Edit/1 のようにします。
タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
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属性によって指定された顧客の連絡先 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 (見つかりません) エラーが返されます。 ID を省略するには、次のように ? をルート制約に追加します。
@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);
}
}
Validation
検証規則:
- はモデル クラスで指定 (宣言) されます。
- はアプリ内のあらゆる場所で適用されます。
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>
名前値なしで Create フォームを投稿すると、フォームに "The Name field is required." (名前フィールドは必須です。) というエラー メッセージが表示されます。 JavaScript がクライアントで有効になっている場合、サーバーに投稿されず、エラーがブラウザーに表示されます。
[StringLength(10)] 属性によって、レンダリングされた HTML で data-val-length-max="10" が生成されます。
data-val-length-max により、指定の最大長を超える入力がブラウザーで禁止されます。
Fiddler のようなツールを投稿の編集と返信に使用する場合:
- 名前の長さ値が 10 を超えています。
- "The field Name must be a string with a maximum length of 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"評価":- 最初の文字が大文字である必要があります。
- 後続のスペースでは、特殊文字と数字が使用できます。 "PG-13" は評価に対して有効ですが、"Genre" に対しては失敗します。
Range属性は、指定した範囲内に値を制限します。StringLength属性により、文字列プロパティの最大長が設定されます。任意で最小長も設定できます。値の型 (
decimal、int、float、DateTimeなど) は本質的に必須ではなく、[Required]属性を必要としません。
Movie モデルの [作成] ページには、エラーと無効な値が表示されます。
詳細については次を参照してください:
CSS の分離
CSS スタイルを個々のページ、ビュー、コンポーネントに分離して、次のものを減らすか回避します。
- 維持が困難なグローバル スタイルへの依存関係。
- 入れ子になったコンテンツでのスタイルの競合。
ページまたはビューの "スコープ付き CSS ファイル" を追加するには、.cshtml.css ファイルの名前に一致するコンパニオン .cshtml ファイルに CSS スタイルを配置します。 次の例では、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 Pages) または Razor (MVC) の Views/Shared/_Layout.cshtml コンテンツでは、バンドルされた CSS スタイルへのリンクが存在することを確認します。
<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />
次の例では、アプリのアセンブリ名は WebApp です。
<link rel="stylesheet" href="WebApp.styles.css" />
スコープ付き CSS ファイルで定義されているスタイルは、一致するファイルのレンダリング出力にのみ適用されます。 前の例では、アプリ内の他の場所で定義されている h1 CSS 宣言はすべて、Index の見出しスタイルと競合しません。 CSS スタイルのカスケードおよび継承の規則は、スコープ付き CSS ファイルに対して有効のままです。 たとえば、<h1> ファイル内の Index.cshtml 要素に直接適用されるスタイルは、Index.cshtml.css 内のスコープ付き CSS ファイルのスタイルをオーバーライドします。
Note
バンドルが発生したときに CSS スタイルの分離を保証するために、Razor コード ブロックでの CSS のインポートはサポートされていません。
CSS の分離は、HTML 要素にのみ適用されます。 CSS の分離は、タグ ヘルパーではサポートされていません。
バンドルされた CSS ファイル内では、各ページ、ビュー、または Razor コンポーネントが、b-{STRING} の形式のスコープ識別子に関連付けられています。ここで、{STRING} プレースホルダーは、フレームワークによって生成された 10 文字の文字列です。 次の例では、<h1> Pages アプリの Index ページ内にある、前述の Razor 要素のスタイルを示しています。
/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}
バンドルされているファイルから CSS スタイルが適用されている Index ページには、スコープ識別子が HTML 属性として追加されます。
<h1 b-3xxtam6d07>
識別子は、アプリに固有です。 ビルド時、規則 {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css でプロジェクト バンドルが作成されます。ここで、プレースホルダー {STATIC WEB ASSETS BASE PATH} は静的な Web アセットのベース パスです。
NuGet パッケージや Razor クラス ライブラリなどの他のプロジェクトを利用する場合、バンドル ファイルは次のようになります。
- CSS インポートを使用してスタイルを参照する。
- スタイルを使用するアプリの静的な Web アセットとして公開されない。
CSS プリプロセッサのサポート
CSS プリプロセッサは、変数、入れ子、モジュール、mixin、継承などの機能を利用することで CSS 開発を改善するのに役立ちます。 CSS の分離は、SASS や LESS などの CSS プリプロセッサをネイティブにサポートしませんが、ビルド プロセス中に、フレームワークにより CSS セレクターが書き換えられる前にプリプロセッサのコンパイルが行われる限り、CSS プリプロセッサの統合はシームレスです。 たとえば、Visual Studio を使用して、Visual Studio タスク ランナー エクスプローラーで既存のプリプロセッサ コンパイルをビルド前タスクとして構成します。
AspNetCore.SassCompiler などの多くのサードパーティ製 NuGet パッケージは、CSS の分離が発生する前に、ビルド プロセスの開始時に SASS または SCSS ファイルをコンパイルできます。追加の構成は必要ありません。
CSS の分離の構成
CSS 分離では、既存のツールやワークフローに依存関係がある場合など、いくつかの高度なシナリオでの構成が可能です。
スコープ識別子の形式をカスタマイズする
"このセクションの {Pages|Views} プレースホルダーは、Pages Pages アプリ用の Razor、または MVC アプリ用の Views のいずれかです。 "
既定では、スコープ識別子には、b-{STRING} の形式が使用されます。ここで、{STRING} プレースホルダーは、フレームワークによって生成される 10 文字の文字列です。 スコープ識別子の形式をカスタマイズするには、プロジェクト ファイルを目的のパターンに更新します。
<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
前の例では、Index.cshtml.css 用に生成された 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>
静的な Web アセットのベース パスを変更する
スコープ付き CSS ファイルは、アプリのルートで生成されます。 プロジェクト ファイルでは、StaticWebAssetBasePath プロパティを使用して既定のパスを変更します。 次の例では、スコープ付き CSS ファイルとその他のアプリのアセットを _content パスに配置します。
<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>
自動バンドルを無効にする
フレームワークでスコープ付きファイルを公開し、それを実行時に読み込む方法をオプトアウトするには、DisableScopedCssBundling プロパティを使用します。 このプロパティを使用する場合、obj ディレクトリからの CSS ファイルの分離、それらの公開、および実行時の読み込みを、他のツールまたはプロセスが担当することを意味します。
<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>
Razor クラス ライブラリ (RCL) のサポート
Razor クラス ライブラリ (RCL) により分離スタイルが提供される場合、<link> タグの href 属性は {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css を指します。ここで、プレースホルダーは次のとおりです。
-
{STATIC WEB ASSET BASE PATH}: 静的な Web 資産のベース パス。 -
{PACKAGE ID}: ライブラリのパッケージ識別子。 プロジェクト ファイルでパッケージ識別子が指定されていない場合、パッケージ識別子の既定値は、プロジェクトのアセンブリ名になります。
次に例を示します。
- 静的な Web 資産のベース パスは
_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 の詳細については、次の記事を参照してください。
Blazor の CSS の分離については、「ASP.NET Core Blazor の CSS の分離」をご覧ください。
OnGet ハンドラー フォールバックを使用した HEAD 要求の処理
HEAD 要求により、特定のリソースのヘッダーを取得できます。
GET 要求とは異なり、HEAD 要求から応答本文は返されません。
通常、OnHead 要求に対して HEAD ハンドラーが作成され、呼び出されます。
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
Razor ハンドラーが定義されていない場合、OnGet ページは OnHead ハンドラーの呼び出しにフォールバックします。
XSRF/CSRF および Razor ページ
Razor ページは、偽造防止検証によって保護されます。 FormTagHelper により HTML フォーム要素に偽造防止トークンが挿入されます。
Razor ページでのレイアウト、パーシャル、テンプレート、およびタグ ヘルパーの使用
ページは、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>
- (ページでレイアウトを止めない限り) 各ページのレイアウトを制御します。
- JavaScript やスタイルシートなどの HTML 構造をインポートします。
-
Razor が呼び出されるところで
@RenderBody()ページの内容が表示されます。
詳細については、レイアウトに関するページを参照してください。
Layout プロパティは Pages/_ViewStart.cshtml で設定されています。
@{
Layout = "_Layout";
}
レイアウトは、Pages/Shared フォルダーにあります。 ページは現在のページと同じフォルダーから開始して、階層的に他のビュー (レイアウト、テンプレート、パーシャル) を検索します。 "Pages/Shared" フォルダー内のレイアウトは、"Razor" フォルダー配下の任意の ページから使用できます。
レイアウト ファイルは Pages/Shared フォルダーに入ります。
レイアウト ファイルを Views/Shared フォルダー内に配置しないことをお勧めします。 Views/Shared は MVC ビュー パターンです。 Razor ページは、パス規則ではなく、フォルダー階層に依存することを意図しています。
Razor ページからのビュー検索には、"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 に含まれていると、指定した名前空間が @namespace ディレクティブをインポートするページで生成された名前空間のプレフィックスを提供します。 生成された名前空間の残りの部分 (サフィックス部分) は、_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 ページの生成された名前空間は、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/_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.cshtmlPrivacy.cshtml/Customers
Create.cshtmlEdit.cshtmlIndex.cshtml
成功すると、Pages/Customers/Create.cshtml ページと Pages/Customers/Edit.cshtml ページが Pages/Customers/Index.cshtml にリダイレクトされます。 文字列 ./Index は、前のページにアクセスするために使用される相対ページ名です。 これは、Pages/Customers/Index.cshtml ページへの URI を生成するために使われます。 例えば次が挙げられます。
Url.Page("./Index", ...)<a asp-page="./Index">Customers Index Page</a>RedirectToPage("./Index")
絶対ページ名 /Index は、Pages/Index.cshtml ページへの URL を生成するために使われます。 例えば次が挙げられます。
Url.Page("/Index", ...)<a asp-page="/Index">Home Index Page</a>RedirectToPage("/Index")
ページ名は、先頭の を含む、ルート / フォルダーからページへのパスです (たとえば /Index)。 先述の URL 生成サンプルでは、URL のハードコーディングに関する拡張オプションと機能が提供されます。 URL の生成はルーティングを使用し、ターゲット パスで定義されたルート方法に従って、パラメーターの生成とエンコードができます。
ページの URL 生成は、相対名をサポートします。 次の表に、RedirectToPage の異なる Pages/Customers/Create.cshtml パラメーターで選択されたインデックス ページを示します。
| RedirectToPage(x) | Page |
|---|---|
| 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 の区分」と「ASP.NET Core での Razor ページのルートとアプリの規則」をご覧ください。
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 タグ ヘルパーを使用して 2 つのハンドラーにマークアップが生成されます。
@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>
前の例のフォームには、それぞれが FormActionTagHelper を使用して異なる URL に送信する 2 つの送信ボタンがあります。
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" />
上記のコードを使用すると、OnPostJoinListAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH?handler=JoinList になります。
OnPostJoinListUCAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH?handler=JoinListUC です。
カスタム ルート
@page ディレクティブを次に使用します:
- カスタム ルートをページに指定します。 たとえば、[バージョン情報] ページへのルートを
/Some/Other/Pathを使用して@page "/Some/Other/Path"に設定することができます。 - ページの既定のルートにセグメントを追加します。 たとえば、"item" セグメントを
@page "item"を使用してページの既定のルートに追加することができます。 - ページの既定のルートにパラメーターを追加します。 たとえば、
idを含むページに ID パラメーター@page "{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>
上記のコードを使用すると、OnPostJoinListAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH/JoinList になります。
OnPostJoinListUCAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH/JoinListUC です。
? の後の handler は、ルート パラメーターが省略可能なことを意味します。
JavaScript (JS) ファイルの併置
ページとビューの JavaScript (JS) ファイルの併置は、アプリでスクリプトを整理する便利な方法です。
JS ファイルを併置するには、次のファイル名拡張子の規則を使用します。
-
Razor Pages アプリのページと MVC アプリのビュー:
.cshtml.js。 Examples:-
Pages/Index.cshtml.jsにあるIndexPages アプリの Razor ページに対するPages/Index.cshtml。 -
Views/Home/Index.cshtml.jsにある MVC アプリのIndexビューに対するViews/Home/Index.cshtml。
-
併置された JS ファイルは、"プロジェクト内のファイルへのパス" を使用してパブリックにアドレス指定できます。
アプリ内の併置されたスクリプト ファイルのページとビュー:
{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js-
{PATH}プレースホルダーは、ページ、ビュー、またはコンポーネントへのパスです。 -
{PAGE, VIEW, OR COMPONENT}プレースホルダーは、ページ、ビュー、またはコンポーネントです。 -
{EXTENSION}プレースホルダーは、ページ、ビュー、またはコンポーネントの拡張子 (razorまたはcshtml) と一致します。
Razor ページの例:
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 ファイルを含めます。
アプリが公開されると、スクリプトはフレームワークによって Web ルートに自動的に移動されます。 前の例では、スクリプトは bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js に移動されます。{TARGET FRAMEWORK MONIKER} プレースホルダーはターゲット フレームワーク モニカー (TFM) です。
Index ページ内のスクリプトの相対 URL を変更する必要はありません。
アプリが公開されると、スクリプトはフレームワークによって Web ルートに自動的に移動されます。 前の例では、スクリプトは bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js に移動されます。{TARGET FRAMEWORK MONIKER} プレースホルダーはターゲット フレームワーク モニカー (TFM) です。
Index コンポーネント内のスクリプトの相対 URL を変更する必要はありません。
Razor クラス ライブラリ (RCL) によって提供されるスクリプトの場合:
_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 ページの承認規則」を参照してください。
ビューをプリコンパイルするには、Razor ビューのコンパイルに関するページをご覧ください。
Razor Pages をコンテンツのルートに指定する
Razor Pages のルートは既定で /Pages ディレクトリです。 WithRazorPagesAtContentRoot を追加して、Razor ページをアプリのコンテンツ ルート (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 を追加します。
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 の概要に関するページをご覧ください
- 属性と Razor ページを承認する
- サンプル コードのダウンロードまたは表示
- ASP.NET Core の概要
- ASP.NET Core の Razor 構文リファレンス
- ASP.NET Core の区分
- チュートリアル: ASP.NET Core の Razor Pages の概要
- RazorASP.NET Core での ページ認可規則
- RazorASP.NET Core での ページのルートとアプリの規則
- RazorASP.NET Core での Pages の単体テスト
- ASP.NET Core の部分ビュー
- ASP.NET および Web 開発ワークロードを含む Visual Studio 2019 16.4 以降
- .NET Core 3.1 SDK
- ASP.NET および Web 開発ワークロードを含む Visual Studio 2019 16.8 以降
- .NET 5 SDK
Razor ページ プロジェクトを作成する
Razor ページ
Razor Pages は 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>
上記のコードは、コントローラーとビューを含んだ ASP.NET Core アプリで使われる Razor ビュー ファイルによく似ています。 違いは、@page ディレクティブにあります。
@page はファイルを MVC アクションにします。つまり、コントローラーを経由せずに要求を直接処理します。
@page はページ上で最初の Razor ディレクティブである必要があります。
@page はその他の Razor コンストラクトの動作に影響します。
Razor ページのファイル名には ".cshtml" サフィックスが付きます。
PageModel クラスを使用している類似したページが、次の 2 つのファイルにあります。
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 ページ ファイルと同じ名前に " .cs" が付加された名前になります。 たとえば、上の Razor ページは Pages/Index2.cshtml になります。
PageModel クラスを含むファイル名は、Pages/Index2.cshtml.cs になります。
URL パスのページへの関連付けは、ファイル システム内のページの場所によって決定されます。 次の表に、Razor ページ パスと一致 URL を示します。
| ファイル名とパス | 一致する URL |
|---|---|
/Pages/Index.cshtml |
/ または /Index |
/Pages/Contact.cshtml |
/Contact |
/Pages/Store/Contact.cshtml |
/Store/Contact |
/Pages/Store/Index.cshtml |
/Store または /Store/Index |
Notes:
- 既定では、ランタイムが "Razor" フォルダー内で ページ ファイルを検索します。
-
Indexは、URL にページが含まれない場合の既定のページになります。
基本フォームを作成する
Razor ページは、アプリの構築時に Web ブラウザーで使用される一般的なパターンを実装しやすくするために設計されています。
モデル バインド、タグ ヘルパー、および HTML ヘルパーはすべて、 ページ クラスで定義されたプロパティで "Razor"。
Contact モデルの基本的な "お問い合わせ" フォームを実装するページを考察します。
このドキュメントのサンプルでは、DbContext ファイルで が初期化されます。
インメモリ データベースには、Microsoft.EntityFrameworkCore.InMemory NuGet パッケージが必要です。
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; }
}
}
db コンテキスト:
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メソッドによりCreateModel.cshtmlRazor ページが表示されます。 - フォームの送信を処理するための
OnPost。
Async 名前付けサフィックスは省略可能ですが、非同期関数の規則でよく使用されます。 上記のコードは、Razor ページでは一般的です。
コントローラーとビューを利用する ASP.NET アプリに慣れている場合:
- 前の例の
OnPostAsyncコードは、一般的なコントローラー コードに似ています。 - モデル バインド、検証、アクションの結果など、MVC プリミティブのほとんどは Controllers ページや Razor ページと同じように動作します。
上記の 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>
Pages/Create.cshtml から表示される 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>
前のコードで投稿したフォーム:
有効なデータ:
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">) からレンダリングして入力を受け入れます。
Warning
セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。
GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。
GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。
[BindProperty(SupportsGet = true)]
詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET ディスカッション (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" />によって HTML<input>要素がCustomer.Nameモデル式にバインドされます。 -
@addTagHelperでタグ ヘルパーを使用可能にします。
ホーム ページ
Index.cshtml はホーム ページです。
@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} 属性を使用して編集ページへのリンクが生成されました。 リンクには、連絡先 ID とともにルート データが含まれています。 たとえば、https://localhost:5001/Edit/1 のようにします。
タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
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属性によって指定された顧客の連絡先 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 (見つかりません) エラーが返されます。 ID を省略するには、次のように ? をルート制約に追加します。
@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");
}
}
Validation
検証規則:
- はモデル クラスで指定 (宣言) されます。
- はアプリ内のあらゆる場所で適用されます。
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>
名前値なしで Create フォームを投稿すると、フォームに "The Name field is required." (名前フィールドは必須です。) というエラー メッセージが表示されます。 JavaScript がクライアントで有効になっている場合、サーバーに投稿されず、エラーがブラウザーに表示されます。
[StringLength(10)] 属性によって、レンダリングされた HTML で data-val-length-max="10" が生成されます。
data-val-length-max により、指定の最大長を超える入力がブラウザーで禁止されます。
Fiddler のようなツールを投稿の編集と返信に使用する場合:
- 名前の長さ値が 10 を超えています。
- "The field Name must be a string with a maximum length of 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"評価":- 最初の文字が大文字である必要があります。
- 後続のスペースでは、特殊文字と数字が使用できます。 "PG-13" は評価に対して有効ですが、"Genre" に対しては失敗します。
Range属性は、指定した範囲内に値を制限します。StringLength属性により、文字列プロパティの最大長が設定されます。任意で最小長も設定できます。値の型 (
decimal、int、float、DateTimeなど) は本質的に必須ではなく、[Required]属性を必要としません。
Movie モデルの [作成] ページには、エラーと無効な値が表示されます。
詳細については次を参照してください:
OnGet ハンドラー フォールバックを使用した HEAD 要求の処理
HEAD 要求により、特定のリソースのヘッダーを取得できます。
GET 要求とは異なり、HEAD 要求から応答本文は返されません。
通常、OnHead 要求に対して HEAD ハンドラーが作成され、呼び出されます。
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
Razor ハンドラーが定義されていない場合、OnGet ページは OnHead ハンドラーの呼び出しにフォールバックします。
XSRF/CSRF および Razor ページ
Razor ページは、偽造防止検証によって保護されます。 FormTagHelper により HTML フォーム要素に偽造防止トークンが挿入されます。
Razor ページでのレイアウト、パーシャル、テンプレート、およびタグ ヘルパーの使用
ページは、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>
- (ページでレイアウトを止めない限り) 各ページのレイアウトを制御します。
- JavaScript やスタイルシートなどの HTML 構造をインポートします。
-
Razor が呼び出されるところで
@RenderBody()ページの内容が表示されます。
詳細については、レイアウトに関するページを参照してください。
Layout プロパティは Pages/_ViewStart.cshtml で設定されています。
@{
Layout = "_Layout";
}
レイアウトは、Pages/Shared フォルダーにあります。 ページは現在のページと同じフォルダーから開始して、階層的に他のビュー (レイアウト、テンプレート、パーシャル) を検索します。 "Pages/Shared" フォルダー内のレイアウトは、"Razor" フォルダー配下の任意の ページから使用できます。
レイアウト ファイルは Pages/Shared フォルダーに入ります。
レイアウト ファイルを Views/Shared フォルダー内に配置しないことをお勧めします。 Views/Shared は MVC ビュー パターンです。 Razor ページは、パス規則ではなく、フォルダー階層に依存することを意図しています。
Razor ページからのビュー検索には、"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 に含まれていると、指定した名前空間が @namespace ディレクティブをインポートするページで生成された名前空間のプレフィックスを提供します。 生成された名前空間の残りの部分 (サフィックス部分) は、_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 ページの生成された名前空間は、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/_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.cshtmlPrivacy.cshtml/Customers
Create.cshtmlEdit.cshtmlIndex.cshtml
成功すると、Pages/Customers/Create.cshtml ページと Pages/Customers/Edit.cshtml ページが Pages/Customers/Index.cshtml にリダイレクトされます。 文字列 ./Index は、前のページにアクセスするために使用される相対ページ名です。 これは、Pages/Customers/Index.cshtml ページへの URI を生成するために使われます。 例えば次が挙げられます。
Url.Page("./Index", ...)<a asp-page="./Index">Customers Index Page</a>RedirectToPage("./Index")
絶対ページ名 /Index は、Pages/Index.cshtml ページへの URL を生成するために使われます。 例えば次が挙げられます。
Url.Page("/Index", ...)<a asp-page="/Index">Home Index Page</a>RedirectToPage("/Index")
ページ名は、先頭の を含む、ルート / フォルダーからページへのパスです (たとえば /Index)。 先述の URL 生成サンプルでは、URL のハードコーディングに関する拡張オプションと機能が提供されます。 URL の生成はルーティングを使用し、ターゲット パスで定義されたルート方法に従って、パラメーターの生成とエンコードができます。
ページの URL 生成は、相対名をサポートします。 次の表に、RedirectToPage の異なる Pages/Customers/Create.cshtml パラメーターで選択されたインデックス ページを示します。
| RedirectToPage(x) | Page |
|---|---|
| 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 の区分」と「ASP.NET Core での Razor ページのルートとアプリの規則」をご覧ください。
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 タグ ヘルパーを使用して 2 つのハンドラーにマークアップが生成されます。
@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>
前の例のフォームには、それぞれが FormActionTagHelper を使用して異なる URL に送信する 2 つの送信ボタンがあります。
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" />
上記のコードを使用すると、OnPostJoinListAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH?handler=JoinList になります。
OnPostJoinListUCAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH?handler=JoinListUC です。
カスタム ルート
@page ディレクティブを次に使用します:
- カスタム ルートをページに指定します。 たとえば、[バージョン情報] ページへのルートを
/Some/Other/Pathを使用して@page "/Some/Other/Path"に設定することができます。 - ページの既定のルートにセグメントを追加します。 たとえば、"item" セグメントを
@page "item"を使用してページの既定のルートに追加することができます。 - ページの既定のルートにパラメーターを追加します。 たとえば、
idを含むページに ID パラメーター@page "{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>
上記のコードを使用すると、OnPostJoinListAsync に送信される URL パスは https://localhost:5001/Customers/CreateFATH/JoinList になります。
OnPostJoinListUCAsync に送信される URL パスは 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 ページの承認規則」を参照してください。
ビューをプリコンパイルするには、Razor ビューのコンパイルに関するページをご覧ください。
Razor Pages をコンテンツのルートに指定する
Razor Pages のルートは既定で /Pages ディレクトリです。 WithRazorPagesAtContentRoot を追加して、Razor ページをアプリのコンテンツ ルート (ContentRootPath) に置くように指定します。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();
}
Razor Pages をカスタム ルート ディレクトリに指定する
(相対パスを指定して) WithRazorPagesRoot ページをアプリのカスタム ルート ディレクトリに置くように指定するには、Razor を追加します。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");
}
その他のリソース
- この概要に基づく、Razor Pages の概要に関するページをご覧ください
- 属性と Razor ページを承認する
- サンプル コードのダウンロードまたは表示
- ASP.NET Core の概要
- ASP.NET Core の Razor 構文リファレンス
- ASP.NET Core の区分
- チュートリアル: ASP.NET Core の Razor Pages の概要
- RazorASP.NET Core での ページ認可規則
- RazorASP.NET Core での ページのルートとアプリの規則
- RazorASP.NET Core での Pages の単体テスト
- ASP.NET Core の部分ビュー
ASP.NET Core