ASP.NET Core での Razor ページの概要

Rick Anderson および Ryan Nowak

Razor ページを利用することで、ページのコーディングに今まで以上に集中できます。また、コントローラーとビューを使用する場合より生産的になります。

モデル ビュー コントローラーのアプローチを使用するチュートリアルをお探しの場合は、「Get started with ASP.NET Core MVC」 (ASP.NET Core MVC の概要) を参照してください。

このドキュメントでは、Razor ページの概要について説明します。 手順を追って説明するチュートリアルではありません。 セクションの一部を理解できない場合は、「Razor ページの概要」を参照してください。 ASP.NET Core の概要については、「ASP.NET Core の概要」を参照してください。

必須コンポーネント

Razor ページ プロジェクトを作成する

Razor ページ プロジェクトを作成する詳細な手順については、「Razor ページの概要」を参照してください。

Razor Pages

Razor ページは "Startup.cs" で有効になっています。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

基本ページを検討します。

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

上記のコードは、コントローラーとビューを含んだ 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 ヘルパー メソッドが呼び出されます。 RedirectToPageRedirectToPageResult のインスタンスを返します。 RedirectToPage:

      • はアクションの結果です。
      • は、(コントローラーやビューで使用される) RedirectToActionRedirectToRoute に似ています。
      • はページ用にカスタマイズされています。 上記のサンプルでは、ルート インデックス ページ (/Index) にリダイレクトします。 RedirectToPage については、「ページの URL の生成」セクションで詳しく説明されています。
  • サーバーに検証エラーが渡される:

    • OnPostAsync ハンドラー メソッドにより Page ヘルパー メソッドが呼び出されます。 PagePageResult のインスタンスを返します。 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 でタグ ヘルパーを使用可能にします。

ホーム ページ

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>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <button type="submit" asp-page-handler="delete"
                                asp-route-id="@contact.Id">delete
                        </button>
                    </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 ファイルには、次のマークアップが含まれています。

<td>

<a /a> アンカー タグ ヘルパーでは、asp-route-{value} 属性を使用して編集ページへのリンクが生成されました。 リンクには、連絡先 ID とともにルート データが含まれています。 たとえば、https://localhost:5001/Edit/1 のようにします。 タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

Index.cshtml ファイルには、各顧客の連絡先の削除ボタンを作成するマークアップが含まれています。

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
<button type="submit" asp-page-handler="delete"

レンダリングされた HTML:

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

HTML で削除ボタンがレンダリングされる場合、その formaction には次のパラメーターが含まれています。

  • asp-route-id 属性によって指定された顧客の連絡先 ID。
  • asp-page-handler 属性によって指定された handler

ボタンが選択されると、フォームの POST 要求がサーバーに送信されます。 慣例により、ハンドラー メソッドの名前はスキーム OnPost[handler]Async に従った handler パラメーターの値に基づいて選択されます。

この例では handlerdelete であるため、OnPostDeleteAsync ハンドラー メソッドを使用して POST 要求が処理されます。 asp-page-handlerremove などの別の値に設定されている場合、名前が 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 属性により、文字列プロパティの最大長が設定されます。任意で最小長も設定できます。

  • 値の型 (decimalintfloatDateTime など) は本質的に必須ではなく、[Required] 属性を必要としません。

Movie モデルの [作成] ページには、エラーと無効な値が表示されます。

複数 jQuery クライアント側検証エラーが表示されたムービー ビュー フォーム

詳細については次を参照してください:

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 ディレクティブをインポートするページで生成された名前空間のプレフィックスを提供します。 生成された名前空間の残りの部分 (サフィックスの部分) は、 _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>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </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 の前 (ある場合) のテキストを取得して作成されます。 前の例では、ページ メソッドは OnPost JoinList Async と OnPost JoinListUC Async です。 OnPostAsync を削除すると、ハンドラー名は JoinListJoinListUC になります。

<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>Name: <input asp-for="Customer.Name" /></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");
}

その他の技術情報

警告

Visual Studio 2017 を使用している場合、Visual Studio で動作しない .NET Core SDK のバージョンについては、dotnet/sdk issue #3124 を参照してください。

Razor ページ プロジェクトを作成する

Razor ページ プロジェクトを作成する詳細な手順については、「Razor ページの概要」を参照してください。

Razor Pages

Razor ページは "Startup.cs" で有効になっています。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Includes support for Razor Pages and controllers.
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}

基本ページを検討します。

@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 コンストラクトの動作に影響します。

PageModel クラスを使用している類似したページが、次の 2 つのファイルにあります。 Pages/Index2.cshtml ファイル:

@page
@using RazorPagesIntro.Pages
@model IndexModel2

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Pages/Index2.cshtml.cs ページ モデル:

using Microsoft.AspNetCore.Mvc.RazorPages;
using System;

namespace RazorPagesIntro.Pages
{
    public class IndexModel2 : 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 が初期化されます。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesContacts.Data;

namespace RazorPagesContacts
{
    public class Startup
    {
        public IHostingEnvironment HostingEnvironment { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<AppDbContext>(options =>
                              options.UseInMemoryDatabase("name"));
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

データ モデル:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Data
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(100)]
        public string Name { get; set; }
    }
}

db コンテキスト:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Pages/Create.cshtml ビュー ファイル:

@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" />
    </form>
</body>
</html>

Pages/Create.cshtml.cs ページ モデル:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages
{
    public class CreateModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }
    }
}

規則により、PageModel クラスは <PageName>Model と呼ばれ、ページと同じ名前空間にあります。

PageModel クラスでは、ページの表示からロジックを分離できます。 これは、ページに送信される要求のページ ハンドラーと、ページのレンダリングに使用されるデータを定義します。 この分離により可能になること:

このページには、(ユーザーがフォームを投稿したときに) POST 要求で実行される OnPostAsync "ハンドラー メソッド" があります。 任意の HTTP 動詞のハンドラー メソッドを追加できます。 最も一般的なハンドラーは次のとおりです。

  • ページに必要な状態を初期化するための OnGetOnGet サンプル。
  • フォームの送信を処理するための OnPost

Async 名前付けサフィックスは省略可能ですが、非同期関数の規則でよく使用されます。 上記のコードは、Razor ページでは一般的です。

コントローラーとビューを利用する ASP.NET アプリに慣れている場合:

  • 前の例の OnPostAsync コードは、一般的なコントローラー コードに似ています。
  • モデル バインド検証検証 アクションの結果などのほとんどの MVC プリミティブは共有されます。

上記の OnPostAsync メソッド:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _db.Customers.Add(Customer);
    await _db.SaveChangesAsync();
    return RedirectToPage("/Index");
}

OnPostAsync の基本的な流れは次のとおりです。

検証エラーを確認します。

  • エラーがない場合は、データを保存し、リダイレクトします。
  • エラーがある場合は、検証メッセージとともにページをもう一度表示します。 クライアント側の検証は、従来の ASP.NET Core MVC アプリケーションと同じです。 多くの場合、検証エラーはクライアントで検出され、サーバーには送信されません。

データが正常に入力されると、OnPostAsync ハンドラー メソッドが RedirectToPage ヘルパー メソッドを呼び出して RedirectToPageResult のインスタンスを返します。 RedirectToPage は、RedirectToActionRedirectToRoute と同じような新しいアクション結果ですが、ページ用にカスタマイズされています。 上記のサンプルでは、ルート インデックス ページ (/Index) にリダイレクトします。 RedirectToPage については、「ページの URL の生成」セクションで詳しく説明されています。

送信されたフォームに検証エラー (サーバーに渡される) があると、OnPostAsync ハンドラー メソッドが Page ヘルパー メソッドを呼び出します。 PagePageResult のインスタンスを返します。 Page を返すのは、コントローラーのアクションが View を返す方法に似ています。 PageResult はハンドラー メソッドの既定の戻り値の型です。 void を返すハンドラー メソッドがページをレンダリングします。

Customer プロパティは [BindProperty] 属性を使用してモデル バインドにオプトインします。

public class CreateModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateModel(AppDbContext db)
    {
        _db = db;
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        return RedirectToPage("/Index");
    }
}

既定では、Razor ページはプロパティを非 GET 動詞とのみバインドします。 プロパティをバインドすることで、記述すべきコードの量を削減できます。 同じプロパティを使用してバインドすることでコードを減らし、フィールド (<input asp-for="Customer.Name">) からレンダリングして入力を受け入れます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET ディスカッション (YouTube) でのバインド」を参照してください。

ホーム ページ (Index.cshtml):

@page
@model RazorPagesContacts.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customers)
            {
                <tr>
                    <td>@contact.Id</td>
                    <td>@contact.Name</td>
                    <td>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>
                        <button type="submit" asp-page-handler="delete" 
                                asp-route-id="@contact.Id">delete</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <a asp-page="./Create">Create</a>
</form>

関連付けられた PageModel クラス (Index.cshtml.cs):

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Pages
{
    public class IndexModel : PageModel
    {
        private readonly AppDbContext _db;

        public IndexModel(AppDbContext db)
        {
            _db = db;
        }

        public IList<Customer> Customers { get; private set; }

        public async Task OnGetAsync()
        {
            Customers = await _db.Customers.AsNoTracking().ToListAsync();
        }

        public async Task<IActionResult> OnPostDeleteAsync(int id)
        {
            var contact = await _db.Customers.FindAsync(id);

            if (contact != null)
            {
                _db.Customers.Remove(contact);
                await _db.SaveChangesAsync();
            }

            return RedirectToPage();
        }
    }
}

Index.cshtml ファイルには、各連絡先の編集リンクを作成するために次のマークアップが含まれています。

<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> アンカー タグ ヘルパーでは、asp-route-{value} 属性を使用して編集ページへのリンクが生成されました。 リンクには、連絡先 ID とともにルート データが含まれています。 たとえば、https://localhost:5001/Edit/1 のようにします。 タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。 タグ ヘルパーは @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers によって有効になります

Pages/Edit.cshtml ファイル:

@page "{id:int}"
@model RazorPagesContacts.Pages.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@{
    ViewData["Title"] = "Edit Customer";
}

<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?}"

Pages/Edit.cshtml.cs ファイル:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnGetAsync(int id)
        {
            Customer = await _db.Customers.FindAsync(id);

            if (Customer == null)
            {
                return RedirectToPage("/Index");
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Attach(Customer).State = EntityState.Modified;

            try
            {
                await _db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                throw new Exception($"Customer {Customer.Id} not found!");
            }

            return RedirectToPage("/Index");
        }
    }
}

Index.cshtml ファイルには、各顧客の連絡先の削除ボタンを作成するマークアップも含まれています。

<button type="submit" asp-page-handler="delete" 
        asp-route-id="@contact.Id">delete</button>

HTML で削除ボタンがレンダリングされる場合、その formaction には次のパラメーターが含まれています。

  • asp-route-id 属性によって指定された顧客の連絡先 ID。
  • asp-page-handler 属性によって指定された handler

顧客の連絡先 ID 1 でレンダリングされた削除ボタンの例を示します。

<button type="submit" formaction="/?id=1&amp;handler=delete">delete</button>

ボタンが選択されると、フォームの POST 要求がサーバーに送信されます。 慣例により、ハンドラー メソッドの名前はスキーム OnPost[handler]Async に従った handler パラメーターの値に基づいて選択されます。

この例では handlerdelete であるため、OnPostDeleteAsync ハンドラー メソッドを使用して POST 要求が処理されます。 asp-page-handlerremove などの別の値に設定されている場合、名前が OnPostRemoveAsync のハンドラー メソッドが選択されます。 次のコードは、OnPostDeleteAsync ハンドラーを示しています。

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _db.Customers.FindAsync(id);

    if (contact != null)
    {
        _db.Customers.Remove(contact);
        await _db.SaveChangesAsync();
    }

    return RedirectToPage();
}

OnPostDeleteAsync メソッド:

  • クエリ文字列から id を受け入れます。 Index.cshtml ページ ディレクティブにルーティング制約 "{id:int?}" が含まれていた場合、id はルート データから取得されることがあります。 id のルート データは https://localhost:5001/Customers/2 のように URI で指定されます。
  • FindAsync を使用してデータベースから顧客の連絡先を照会します。
  • 顧客の連絡先が見つかった場合、その連絡先は顧客の連絡先の一覧から削除されています。 データベースが更新されます。
  • ルート インデックス ページ (/Index) にリダイレクトされるように、RedirectToPage を呼び出します。

必要に応じてページのプロパティをマークする

PageModel 上のプロパティは、必須属性を使ってマークできます。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        [Required(ErrorMessage = "Color is required")]
        public string Color { get; set; }

        public IActionResult OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            // Process color.

            return RedirectToPage("./Index");
        }
    }
}

詳細については、モデルの検証に関するページを参照してください。

OnGet ハンドラー フォールバックを使用した HEAD 要求の処理

HEADHEAD 要求を使用すると、特定のリソースに対するヘッダーを取得できます。 GET 要求とは異なり、HEAD 要求から応答本文は返されません。

通常、HEAD 要求に対して OnHead ハンドラーが作成され、呼び出されます。

public void OnHead()
{
    HttpContext.Response.Headers.Add("HandledBy", "Handled by OnHead!");
}

ASP.NET Core 2.1 以降では、OnHead ハンドラーが定義されていない場合、Razor ページは OnGet ハンドラーの呼び出しにフォールバックします。 この動作は、Startup.ConfigureServices での SetCompatibilityVersion への呼び出しによって有効になります。

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

既定のテンプレートでは、ASP.NET Core 2.1 および 2.2 で SetCompatibilityVersion の呼び出しが生成されます。 SetCompatibilityVersion は実質的に Razor ページのオプション AllowMappingHeadRequestsToGetHandlertrue に設定します。

SetCompatibilityVersion とのすべての動作にオプトインするのではなく、明示的に 特定の 動作にオプトインすることもできます。 次のコードでは、OnGet ハンドラーに HEAD 要求をマップできるようにすることにオプトインしています。

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.AllowMappingHeadRequestsToGetHandler = true;
    });

XSRF/CSRF および Razor ページ

偽造防止検証のためにコードを記述する必要はありません。 偽造防止トークンの生成と検証は、自動的に Razor ページに含まれます。

Razor ページでのレイアウト、パーシャル、テンプレート、およびタグ ヘルパーの使用

ページは、Razor ビュー エンジンのすべての機能で動作します。 レイアウト、パーシャル、テンプレート、タグ ヘルパー、" _ViewStart.cshtml"、" _ViewImports.cshtml" は、従来の Razor ビューと同じように動作します。

これらの機能の一部を利用してこのページをまとめてみましょう。

レイアウト ページPages/Shared/_Layout.cshtml に追加します。

<!DOCTYPE html>
<html>
<head> 
    <title>Razor Pages Sample</title>      
</head>
<body>    
   <a asp-page="/Index">Home</a>
    @RenderBody()  
    <a asp-page="/Customers/Create">Create</a> <br />
</body>
</html>

レイアウトは次のことを行います。

  • (ページでレイアウトを止めない限り) 各ページのレイアウトを制御します。
  • JavaScript やスタイルシートなどの HTML 構造をインポートします。

詳細については、レイアウトのページを参照してください。

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>

ディレクティブは、ページの名前空間を設定します。 @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.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" />
    </form>
</body>
</html>

更新された Pages/Create.cshtml ビュー ファイル:

@page
@model CreateModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" />
    </form>
</body>
</html>

Razor ページのスタート プロジェクトには、クライアント側の検証をフックする "Pages/_ValidationScriptsPartial.cshtml" が含まれています。

部分ビューの詳細については、「ASP.NET Core の部分ビュー」を参照してください。

ページの URL の生成

上に示した Create ページでは、RedirectToPage を使用します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _db.Customers.Add(Customer);
    await _db.SaveChangesAsync();
    return RedirectToPage("/Index");
}

アプリには次のファイル/フォルダー構造があります。

  • /Pages

    • Index.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

成功すると、Pages/Customers/Create.cshtml ページと Pages/Customers/Edit.cshtml ページが Pages/Index.cshtml にリダイレクトされます。 文字列 /Index は前のページにアクセスするための URI の一部です。 文字列 /Index は、Pages/Index.cshtml ページへの URI を生成するために使用できます。 次に例を示します。

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">My 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 の区分」を参照してください。

ViewData 属性

データは ViewDataAttribute とのページに渡すことができます。 [ViewData] 属性を持つコントローラーまたは Razor ページのモデルのプロパティの値は、ViewDataDictionary に格納してそこから読み込むことができます。

次の例では、AboutModel[ViewData] でマークされた Title プロパティが含まれています。 Title プロパティは、[About] ページのタイトルに設定されます。

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>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </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 の前 (ある場合) のテキストを取得して作成されます。 前の例では、ページ メソッドは OnPost JoinList Async と OnPost JoinListUC Async です。 OnPostAsync を削除すると、ハンドラー名は JoinListJoinListUC になります。

<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>Name: <input asp-for="Customer.Name" /></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 の後の ? は、ルート パラメーターが省略可能なことを意味します。

構成と設定

高度なオプションを構成するには、MVC ビルダーで拡張メソッド AddRazorPagesOptions を使用します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
        .AddRazorPagesOptions(options =>
        {
            options.RootDirectory = "/MyPages";
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        });
}

現在は、RazorPagesOptions を使用してページのルート ディレクトリを設定したり、ページのアプリケーション モデルの規則を追加したりすることができます。 将来、この方法でより多くの機能拡張を可能にしたいと考えています。

ビューをプリコンパイルするには、Razor ビューのコンパイルに関するページをご覧ください。

サンプル コードをダウンロードまたは表示します

この概要に基づく、Razor Pages の概要に関するページをご覧ください

Razor Pages をコンテンツのルートに指定する

Razor Pages のルートは既定で /Pages ディレクトリです。 WithRazorPagesAtContentRootAddMvc に追加して、Razor Pages をアプリのコンテンツ ルート (ContentRootPath) に置くように指定します。

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        ...
    })
    .WithRazorPagesAtContentRoot();

Razor Pages をカスタム ルート ディレクトリに指定する

(相対パスを指定して) Razor Pages をアプリのカスタム ルート ディレクトリに指定するには、WithRazorPagesRootAddMvc に追加します。

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        ...
    })
    .WithRazorPagesRoot("/path/to/razor/pages");

その他の技術情報