しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core MVC の Tag Helpers でフォームを作成する時の個人的なまとめ

何だかんだで一番苦労するのがフォームを作成する時なので、後から見て思い出せるように Tag Helpers を使って書く時の基本的なメモをまとめておきます。

ASP.NET Core MVC 2.0 以降は Razor がアップデートされて変わりそうですが、とりあえず 1.1 対象で。

asp-for は Expression 扱い

Visual Studio で書いている時には文字列にしか見れないですが、Tag Helpers の asp-for は式になってるので単純なプロパティ名だけじゃなくて、複雑な式も指定することが出来ます。

Mvc/InputTagHelper.cs at c27b07ef3f8b07dfe1d1de38a8d7bf2d6a9298f4 · aspnet/Mvc · GitHub

なので Html Helper と同じように式を書けます。移行も比較的簡単に行えそうです。

@Html.TextBoxFor(m => m.Product[0].Name)

<input type="text" asp-for="Product[0].Name" />

実際に出力された HTML は以下の通りです。ちゃんと意図した通りの結果になっています。

f:id:shiba-yan:20170730230100p:plain

最近は配列を使う場面は少ないと思いますが、構造化されたモデルを使うことはあると思うので、そういう場合にも Tag Helper を使うとシンプルに書くことが出来ます。

クライアントサイド検証を無効にする

ASP.NET Core MVC のデフォルトでは jQuery Validation などを使ったクライアントサイド検証が有効化されています。試しに以下のような Razor を書いて実行してみます。

<form asp-controller="User" asp-action="Login" method="post">
    
    <div asp-validation-summary="ModelOnly"></div>

    <input asp-for="Email" />
    <span asp-validation-for="Email"></span>

    <input asp-for="Password" />
    <span asp-validation-for="Password"></span>

    <button>Login</button>

</form>

出力された HTML には data-* 属性を使ったエラーメッセージが埋め込まれています。

f:id:shiba-yan:20170725212635p:plain

クライアントサイド検証が必要ない場面もあるので、そういった時には Startup クラスで無効化することが出来ます。このオプションはちょっとわかりにくい場所にあります。

AddMvc の後に AddViewOptions を追加して ClientValidationEnabled を false にします。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc()
            .AddViewOptions(options =>
            {
                options.HtmlHelperOptions.ClientValidationEnabled = false;
            });
}

Tag Helper を使っていても HtmlHelperOptions なのは少し違和感がありますが、この設定を入れた後に HTML を確認すると data-* 属性が出力されなくなったことが確認できます。

f:id:shiba-yan:20170725212509p:plain

エラーメッセージ表示用の span だけは残るのが気になります。本来なら要らないはずです。

select に Enum を利用する

ASP.NET MVC には EnumDropDownListFor という Html Helper がありますが、Tag Helper には該当する機能がないので Html.GetEnumSelectList<TEnum> を使って SelectListItem を作成します。

enum に対しては Display 属性を使って表示用の名前を追加することが出来ます。

public enum ProductKind
{
    [Display(Name = "オプション1")]
    Option1,
    [Display(Name = "オプション2")]
    Option2,
    [Display(Name = "オプション3")]
    Option3
}

実際に Tag Helper で使う場合には大体以下のようになると思います。asp-items に SelectListItem のコレクションを指定すると、いい感じに出力してくれます。

<select asp-for="Products[0].Kind" asp-items="Html.GetEnumSelectList<ProductKind>()"></select>

ブラウザでの表示は以下のようになります。asp-for で指定したプロパティに値を設定しておけば、その項目が予め選択された状態になるので便利です。

f:id:shiba-yan:20170730232802p:plain

複数選択の場合などは自前で SelectListItem を作る必要がありそうです。

Radio Button を利用する

ASP.NET MVC 5 の頃から作りにくかった Radio Button ですが、Core MVC でも状況はあまり変わっていないようでした。type = radio の場合には asp-for 以外に value の設定が必須です。

生成される id が全て同じ値になってしまうのもこれまで通りです。なので label の for 属性で指定することは出来ないので、label で input を囲んでしまいます。

<label>
    Option1
    <input type="radio" asp-for="Products[0].Kind" value="@(ProductKind.Option1)"/>
</label>
<label>
    Option2
    <input type="radio" asp-for="Products[0].Kind" value="@(ProductKind.Option2)" />
</label>
<label>
    Option3
    <input type="radio" asp-for="Products[0].Kind" value="@(ProductKind.Option3)" />
</label>

これで一応 Radio Button としては動作してくれます。asp-for で指定したプロパティに値を設定しておくと、ちゃんとチェックが入った状態になります。

f:id:shiba-yan:20170730235424p:plain

今の Html Helper や Tag Helper の範囲では良い解決策が無さそうなので、これまで通り自前でガリガリ HTML を書くか専用の Tag Helper を用意するかしか方法はなさそうです。

個人的には Web Forms の Repeater みたいな Tag Helper があれば解決しそうだと思いました。

関係ないですが、Damian 氏の TagHelperPack が結構面白かったので紹介しておきます。