ASP.NET Core での Razor ページの概要
作成者: Rick Anderson、Dave Brock、Kirk Larkin
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
Razor ページを利用することで、ページのコーディングに今まで以上に集中できます。また、コントローラーとビューを使用する場合より生産的になります。
モデル ビュー コントローラーのアプローチを使用するチュートリアルをお探しの場合は、「Get started with ASP.NET Core MVC」 (ASP.NET Core MVC の概要) を参照してください。
このドキュメントでは、Razor ページの概要について説明します。 手順を追って説明するチュートリアルではありません。 セクションの一部を理解できない場合は、「Razor ページの概要」を参照してください。 ASP.NET Core の概要については、「ASP.NET Core の概要」を参照してください。
前提条件
- Visual Studio 2022 と ASP.NET と Web 開発ワークロード。
- .NET 6.0 SDK
Razor ページ プロジェクトを作成する
Razor ページ プロジェクトを作成する詳細な手順については、「Razor ページの概要」を参照してください。
Razor Pages
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 |
メモ:
- 既定では、ランタイムが "Pages" フォルダー内で Razor ページ ファイルを検索します。
Index
は、URL にページが含まれない場合の既定のページになります。
基本フォームを作成する
Razor ページは、アプリの構築時に Web ブラウザーで使用される一般的なパターンを実装しやすくするために設計されています。 モデル バインド、タグ ヘルパー、HTML ヘルパーは、Razor Page クラスで定義されたプロパティで機能します。 Contact
モデルの基本的な "お問い合わせ" フォームを実装するページを考察します。
このドキュメントのサンプルでは、Program.cs ファイルで 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
クラスでは、ページの表示からロジックを分離できます。 これは、ページに送信される要求のページ ハンドラーと、ページのレンダリングに使用されるデータを定義します。 この分離により可能になること:
このページには、(ユーザーがフォームを投稿したときに) POST
要求で実行される OnPostAsync
"ハンドラー メソッド" があります。 任意の HTTP 動詞のハンドラー メソッドを追加できます。 最も一般的なハンドラーは次のとおりです。
- ページに必要な状態を初期化するための
OnGet
。 前のコードでは、OnGet
メソッドによりCreate.cshtml
Razor ページが表示されます。 - フォームの送信を処理するための
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">
) からレンダリングして入力を受け入れます。
警告
セキュリティ上の理由から、ページ モデルのプロパティに対して 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
でタグ ヘルパーを使用可能にします。
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}
属性を使用して編集ページへのリンクが生成されました。 リンクには、連絡先 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。asp-page-handler
属性によって指定されたhandler
。
ボタンが選択されると、フォームの POST
要求がサーバーに送信されます。 慣例により、ハンドラー メソッドの名前はスキーム OnPost[handler]Async
に従った handler
パラメーターの値に基づいて選択されます。
この例では 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
を使用してデータベースから顧客の連絡先を照会します。- 顧客の連絡先が見つからない場合、それは削除されており、データベースが更新されています。
- ルート インデックス ページ (
/Index
) にリダイレクトされるように、RedirectToPage を呼び出します。
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);
}
}
検証
検証規則:
- はモデル クラスで指定 (宣言) されます。
- はアプリ内のあらゆる場所で適用されます。
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
ファイルの名前に一致するコンパニオン .cshtml.css
ファイルに CSS スタイルを配置します。 次の例では、Index.cshtml
ページまたはビューにのみ適用される CSS スタイルが、Index.cshtml.css
ファイルによって提供されます。
Pages/Index.cshtml.css
(Razor Pages) または Views/Index.cshtml.css
(MVC):
h1 {
color: red;
}
CSS の分離は、ビルド時に発生します。 CSS セレクターは、アプリのページまたはビューによってレンダリングされたマックアップに一致するよう、フレームワークによって書き換えられます。 書き換えられた CSS スタイルは、静的なアセット {APP ASSEMBLY}.styles.css
としてバンドルされ、生成されます。 プレースホルダー {APP ASSEMBLY}
は、プロジェクトのアセンブリ名です。 バンドルされた CSS スタイルへのリンクは、アプリのレイアウトに配置されます。
アプリの Pages/Shared/_Layout.cshtml
(Razor Pages) または Views/Shared/_Layout.cshtml
(MVC) の <head>
コンテンツでは、バンドルされた CSS スタイルへのリンクが存在することを確認します。
<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />
次の例では、アプリのアセンブリ名は WebApp
です。
<link rel="stylesheet" href="WebApp.styles.css" />
スコープ付き CSS ファイルで定義されているスタイルは、一致するファイルのレンダリング出力にのみ適用されます。 前の例では、アプリ内の他の場所で定義されている h1
CSS 宣言はすべて、Index
の見出しスタイルと競合しません。 CSS スタイルのカスケードおよび継承の規則は、スコープ付き CSS ファイルに対して有効のままです。 たとえば、Index.cshtml
ファイル内の <h1>
要素に直接適用されるスタイルは、Index.cshtml.css
内のスコープ付き CSS ファイルのスタイルをオーバーライドします。
Note
バンドルが発生したときに CSS スタイルの分離を保証するために、Razor コード ブロックでの CSS のインポートはサポートされていません。
CSS の分離は、HTML 要素にのみ適用されます。 CSS の分離は、タグ ヘルパーではサポートされていません。
バンドルされた CSS ファイル内では、各ページ、ビュー、または Razor コンポーネントが、b-{STRING}
の形式のスコープ識別子に関連付けられています。ここで、{STRING}
プレースホルダーは、フレームワークによって生成された 10 文字の文字列です。 次の例では、Razor Pages アプリの Index
ページ内にある、前述の <h1>
要素のスタイルを示しています。
/* /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}
プレースホルダーは、Razor Pages アプリ用の Pages
、または 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 ファイルとアプリのrestのアセットを _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
要求から応答本文は返されません。
通常、HEAD
要求に対して OnHead
ハンドラーが作成され、呼び出されます。
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
OnHead
ハンドラーが定義されていない場合、Razor ページは OnGet
ハンドラーの呼び出しにフォールバックします。
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 構造をインポートします。
@RenderBody()
が呼び出されるところで Razor ページの内容が表示されます。
詳細については、レイアウトに関するページを参照してください。
Layout プロパティは Pages/_ViewStart.cshtml
で設定されています。
@{
Layout = "_Layout";
}
レイアウトは、Pages/Shared フォルダーにあります。 ページは現在のページと同じフォルダーから開始して、階層的に他のビュー (レイアウト、テンプレート、パーシャル) を検索します。 "Pages/Shared" フォルダー内のレイアウトは、"Pages" フォルダー配下の任意の 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
ディレクティブをインポートするページで生成された名前空間のプレフィックスを提供します。 生成された名前空間の 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 ページの生成された名前空間は、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.cshtml
Privacy.cshtml
/Customers
Create.cshtml
Edit.cshtml
Index.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")
ページ名は、先頭の /
を含む、ルート /Pages フォルダーからページへのパスです (たとえば /Index
)。 先述の URL 生成サンプルでは、URL のハードコーディングに関する拡張オプションと機能が提供されます。 URL の生成はルーティングを使用し、ターゲット パスで定義されたルート方法に従って、パラメーターの生成とエンコードができます。
ページの URL 生成は、相対名をサポートします。 次の表に、Pages/Customers/Create.cshtml
の異なる RedirectToPage
パラメーターで選択されたインデックス ページを示します。
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 の区分」と「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
は、複数の要求に対してデータが必要な場合のリダイレクトに役立ちます。
次のコードは、TempData
を使用して Message
の値を設定します。
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
ファイル内の次のマークアップは、TempData
を使用して Message
の値を表示します。
<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
ディレクティブを次に使用します:
- カスタム ルートをページに指定します。 たとえば、[バージョン情報] ページへのルートを
@page "/Some/Other/Path"
を使用して/Some/Other/Path
に設定することができます。 - ページの既定のルートにセグメントを追加します。 たとえば、"item" セグメントを
@page "item"
を使用してページの既定のルートに追加することができます。 - ページの既定のルートにパラメーターを追加します。 たとえば、
@page "{id}"
を含むページに 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>
上記のコードを使用すると、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
。 例:Pages/Index.cshtml
にある Razor Pages アプリのIndex
ページに対するPages/Index.cshtml.js
。Views/Home/Index.cshtml
にある MVC アプリのIndex
ビューに対するViews/Home/Index.cshtml.js
。
併置された JS ファイルは、"プロジェクト内のファイルへのパス" を使用してパブリックにアドレス指定できます。
アプリ内の併置されたスクリプト ファイルのページとビュー:
{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
{PATH}
プレースホルダーは、ページ、ビュー、またはコンポーネントへのパスです。{PAGE, VIEW, OR COMPONENT}
プレースホルダーは、ページ、ビュー、またはコンポーネントです。{EXTENSION}
プレースホルダーは、ページ、ビュー、またはコンポーネントの拡張子 (razor
またはcshtml
) と一致します。
Razor ページの例:
Index
ページの JS ファイルは、Index
ページ (Pages/Index.cshtml
) と共にPages
フォルダー (Pages/Index.cshtml.js
) に配置されます。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
) と一致します。
詳細な構成と設定
次のセクションの構成と設定はほとんどのアプリで必要ありません。
高度なオプションを構成するには、RazorPagesOptions を構成する AddRazorPages オーバーロードを使用します。
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 をカスタム ルート ディレクトリに指定する
(相対パスを指定して) Razor ページをアプリのカスタム ルート ディレクトリに置くように指定するには、WithRazorPagesRoot を追加します。
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.0 SDK
Razor ページ プロジェクトを作成する
Razor ページ プロジェクトを作成する詳細な手順については、「Razor ページの概要」を参照してください。
Razor Pages
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 |
メモ:
- 既定では、ランタイムが "Pages" フォルダー内で Razor ページ ファイルを検索します。
Index
は、URL にページが含まれない場合の既定のページになります。
基本フォームを作成する
Razor ページは、アプリの構築時に Web ブラウザーで使用される一般的なパターンを実装しやすくするために設計されています。 モデル バインド、タグ ヘルパー、および HTML ヘルパーはすべて、Razor ページ クラスで定義されたプロパティで "機能します"。 Contact
モデルの基本的な "お問い合わせ" フォームを実装するページを考察します。
このドキュメントのサンプルでは、Startup.cs ファイルで 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
クラスでは、ページの表示からロジックを分離できます。 これは、ページに送信される要求のページ ハンドラーと、ページのレンダリングに使用されるデータを定義します。 この分離により可能になること:
このページには、(ユーザーがフォームを投稿したときに) POST
要求で実行される OnPostAsync
"ハンドラー メソッド" があります。 任意の HTTP 動詞のハンドラー メソッドを追加できます。 最も一般的なハンドラーは次のとおりです。
- ページに必要な状態を初期化するための
OnGet
。 前のコードでは、OnGet
メソッドによりCreateModel.cshtml
Razor ページが表示されます。 - フォームの送信を処理するための
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">
) からレンダリングして入力を受け入れます。
警告
セキュリティ上の理由から、ページ モデルのプロパティに対して 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
でタグ ヘルパーを使用可能にします。
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}
属性を使用して編集ページへのリンクが生成されました。 リンクには、連絡先 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。asp-page-handler
属性によって指定されたhandler
。
ボタンが選択されると、フォームの POST
要求がサーバーに送信されます。 慣例により、ハンドラー メソッドの名前はスキーム OnPost[handler]Async
に従った handler
パラメーターの値に基づいて選択されます。
この例では 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
を使用してデータベースから顧客の連絡先を照会します。- 顧客の連絡先が見つからない場合、それは削除されており、データベースが更新されています。
- ルート インデックス ページ (
/Index
) にリダイレクトされるように、RedirectToPage を呼び出します。
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");
}
}
検証
検証規則:
- はモデル クラスで指定 (宣言) されます。
- はアプリ内のあらゆる場所で適用されます。
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
要求から応答本文は返されません。
通常、HEAD
要求に対して OnHead
ハンドラーが作成され、呼び出されます。
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
OnHead
ハンドラーが定義されていない場合、Razor ページは OnGet
ハンドラーの呼び出しにフォールバックします。
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 構造をインポートします。
@RenderBody()
が呼び出されるところで Razor ページの内容が表示されます。
詳細については、レイアウトに関するページを参照してください。
Layout プロパティは Pages/_ViewStart.cshtml
で設定されています。
@{
Layout = "_Layout";
}
レイアウトは、Pages/Shared フォルダーにあります。 ページは現在のページと同じフォルダーから開始して、階層的に他のビュー (レイアウト、テンプレート、パーシャル) を検索します。 "Pages/Shared" フォルダー内のレイアウトは、"Pages" フォルダー配下の任意の 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
ディレクティブをインポートするページで生成された名前空間のプレフィックスを提供します。 生成された名前空間の 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 ページの生成された名前空間は、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.cshtml
Privacy.cshtml
/Customers
Create.cshtml
Edit.cshtml
Index.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")
ページ名は、先頭の /
を含む、ルート /Pages フォルダーからページへのパスです (たとえば /Index
)。 先述の URL 生成サンプルでは、URL のハードコーディングに関する拡張オプションと機能が提供されます。 URL の生成はルーティングを使用し、ターゲット パスで定義されたルート方法に従って、パラメーターの生成とエンコードができます。
ページの URL 生成は、相対名をサポートします。 次の表に、Pages/Customers/Create.cshtml
の異なる RedirectToPage
パラメーターで選択されたインデックス ページを示します。
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 の区分」と「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
は、複数の要求に対してデータが必要な場合のリダイレクトに役立ちます。
次のコードは、TempData
を使用して Message
の値を設定します。
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
ファイル内の次のマークアップは、TempData
を使用して Message
の値を表示します。
<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
ディレクティブを次に使用します:
- カスタム ルートをページに指定します。 たとえば、[バージョン情報] ページへのルートを
@page "/Some/Other/Path"
を使用して/Some/Other/Path
に設定することができます。 - ページの既定のルートにセグメントを追加します。 たとえば、"item" セグメントを
@page "item"
を使用してページの既定のルートに追加することができます。 - ページの既定のルートにパラメーターを追加します。 たとえば、
@page "{id}"
を含むページに 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>
上記のコードを使用すると、OnPostJoinListAsync
に送信される URL パスは https://localhost:5001/Customers/CreateFATH/JoinList
になります。 OnPostJoinListUCAsync
に送信される URL パスは https://localhost:5001/Customers/CreateFATH/JoinListUC
です。
handler
の後の ?
は、ルート パラメーターが省略可能なことを意味します。
詳細な構成と設定
次のセクションの構成と設定はほとんどのアプリで必要ありません。
高度なオプションを構成するには、RazorPagesOptions を構成する AddRazorPages オーバーロードを使用します。
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 をカスタム ルート ディレクトリに指定する
(相対パスを指定して) Razor ページをアプリのカスタム ルート ディレクトリに置くように指定するには、WithRazorPagesRoot を追加します。
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