ASP.NET Core Razor Pages の勉強備忘録 PageHandlerについて

.NET Core 3.0がリリースされて、ASP.NET Core Blazorなども盛り上がっている様子です。
しかし残念ながら私のASP.NET Coreの知識はRazor Pagesの登場時点で止まっています。

mrgchr.hatenablog.com

最新技術にキャッチアップするために、後れを取ったところから確認しなおす必要があります。

環境

Page Handlerについて

例えば、次のようなRazorPage, PageModel, Modelを用意します。

<!-- Pages/Sample.cshtml -->
@page "{handler?}"
@model WebApplication2.Pages.SampleModel
@{
  ViewData["Title"] = "Sample";
}
<h2>@ViewData["Title"].</h2>
<h3>@Model.Message</h3>

<form method="post" asp-page-handler="create">
  <label asp-for="Person.Id"></label>
  <input asp-for="Person.Id" type="number"/>
  <br/>
  <label asp-for="Person.FirstName"></label>
  <input asp-for="Person.FirstName"/>
  <br />
  <label asp-for="Person.LastName"></label>
  <input asp-for="Person.LastName"/>
  <br />
  <input type="submit" value="Submit" />
</form>
// Pages/Sample.cshtml.cs
[AutoValidateAntiforgeryToken]
public class SampleModel : PageModel
{
  public string Message { get; set; }

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

  public void OnGet()
  {
    Message = "Your application description page.";
  }

  public void OnPostCreate()
  {
    var str = @$"Id:{Person.Id}, FirstNaem:{Person.FirstName}, LastName:{Person.LastName}";
    Message = str;
  }
// Models/Person.cs
public class Person
{
  public int Id { get; set; }

  public string FirstName { get; set; }

  public string LastName { get; set; }
}

RazorPageのform内の属性method="post" asp-page-handler="create"が、PageModelのハンドラーメソッドOnPostCreate()との対応しています。
On{Verb}{HandlerName}(Async)という構造になっています。

docs.microsoft.com

従来のASP.NET MVCのようなHTML出力がなされています。
(action="/Sample/create"となっているのは、先頭で@page "{handler?}"と指定したことによるものです。) f:id:mrgchr:20190925210755p:plain

FormをSubmitしたときサーバーサイドでOnPostCreate()が発火されます。

f:id:mrgchr:20190925211243p:plain

Personプロパティには、[BindProperty]指定がしてあるため、なんともイイ感じに入力した値が格納されています。

ちなみに、ハンドラーメソッドOnPostCreate()にPerson型パラメータを与えてOnPostCreate(Person person)などとしてもForm入力値は反映されません。
OnPostCreate([FromForm]Person person)とすることで解消できますが、[BindProperty]したプロパティにも同じ値が格納されるのでどちらかで良いかもしれません。

f:id:mrgchr:20190925211659p:plain

インテリセンスを効かせたい

もちろん、これはこれで良いのですが一つ大きな不満があります。

<form method="post" asp-page-handler="create">

このasp-page-handler="create"の"create"は、インテリセンスの守備範囲外なので、ハンドラーメソッド名と一致するかが実行時にしか分からないという点です。
そういえば、過去にも同じような不満を持っていました。

mrgchr.hatenablog.com

故に無理やり対応させます。

ダメだったものたち

ActionNameはダメでした。

  //NG. ActionNameでは思い通りにいかなかった。
  [ActionName("CreatePerson")]
  public void OnPostCreate()
  {
    var str = @$"Id:{Person.Id}, FirstNaem:{Person.FirstName}, LastName:{Person.LastName}";
    Message = str;
  }

HttpPostはもっとダメでした。

  //NG. HttpPostも思い通りに動かないし、RazorPageでは使えない旨の警告が出る。
  [HttpPost("CreatePerson")]
  public void OnPostCreate()
  {
    var str = @$"Id:{Person.Id}, FirstNaem:{Person.FirstName}, LastName:{Person.LastName}";
    Message = str;
  }

結局前回と同じような手段に

いよいよどうしようもないので、前回と同じ手段を取ります。

// Pages/Sample.cshtml.cs

  public void OnPostCreate()
  {
    var str = @$"Id:{Person.Id}, FirstNaem:{Person.FirstName}, LastName:{Person.LastName}";
    Message = str;
  }

  public static class Handlers
  {
    public static readonly string OnPostCreate = "Create";
  }
}
<!-- Pages/Sample.cshtml -->
@page "{handler?}"
@model WebApplication2.Pages.SampleModel
@using Handlers = WebApplication2.Pages.SampleModel.Handlers
@{
  ViewData["Title"] = "Sample";
}
<h2>@ViewData["Title"].</h2>
<h3>@Model.Message</h3>

<form method="post" asp-page-handler="@Handlers.OnPostCreate">
  <label asp-for="Person.Id"></label>
  <input asp-for="Person.Id" type="number"/>
  <br/>
  <label asp-for="Person.FirstName"></label>
  <input asp-for="Person.FirstName"/>
  <br />
  <label asp-for="Person.LastName"></label>
  <input asp-for="Person.LastName"/>
  <br />
  <input type="submit" value="Submit" />
</form>

RazorPage側でインテリセンスを効かせるという当初の目的は達成できますが、この方法も根本的な解決にはなっていません。

  1. Handlers.OnPostCreateの値とハンドラーメソッドOnPostCreateのハンドラー名が一致していることを単体テストなどで保証しないといけない
  2. テストケースを自動的に作成するためには、Handlers.OnPostCreateの名前とハンドラーメソッドOnPostCreateの名前も一致していることが必要
  3. RazorPageでHandlers.OnPostCreateの存在をガン無視したら何の価値もない

と言ったところでしょうか。

PageHanlderは便利なのかもしれませんが、実行時までミス発見が遅れるのは勘弁願いたいですね。
それと少ないかもしれませんが、パスに-(ハイフン)などを含めたい場合なども、この場合はどうすることもできません。
あと、WebForms感がそこはかとなくただよいます。