いがにんのぼやき

WEBエンジニアのブログ。IT、WEB、バンド、アニメ。

.Net Core MVCでTag Helperを自作する

docs.microsoft.com

.Net Core MVCのRazorではHTML Helperとは別にTag Helperというものが使えるようになっている。
そのTag Helperを自作したときのメモ。

作り方

public class PriceTagHelper : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "span";
    }
}

実質これだけ。
これでView側のcshtmlに@addTagHelper *,[アセンブリ名]と記述するだけで使用できる。
ちゃんと公式に書いてあるんだけど勝手にここを名前空間を指定するものと思ってハマった。

こんな感じ。

@addTagHelper *,SampleCoreWeb
<price>10000</price>

出力結果

<span>10000</span>

カスタマイズ

上記はただタグを差し替えているだけなのでこれだけじゃ使う意味はない。
なのでカスタマイズしていく。

例えばお金を扱うことにしよう。
よくある要件でお金の値を3桁ごとのカンマ区切りにして末尾に円を付けるとする。
実際どうするかって言うとこうなる。

public class PriceTagHelper : TagHelper
{
    public decimal Value { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "span";
        output.TagMode = TagMode.StartTagAndEndTag;
        
        var yen = Value.ToString("C").Substring(1);
        output.Content.SetContent($"{yen}円");
    }
}
<price value="10000" />

出力結果

<span>10,000円</span>

output.TagMode = TagMode.StartTagAndEndTagというのは<hoge />のような開始終了タグが一つになっているときに、HTMLでは開始終了タグを別にそれぞれ出力するために指定している。
TagHelperを継承したクラスにプロパティを宣言するとそのプロパティ名のケバブケースを属性として扱うことができる。
なのでここではvalue属性を使ってdecimalの値を入れることが出来る。

開始終了を単一のタグにするか分けるのか

今回のような値を入れて終わりという要件であれば開始終了が単一のタグの形式でいいのかなと思う。
開始終了のタグが別である場合というのは、中に文章など落とし込みたい時などで使えるのかな。
一応さらっと今回のケース(カンマ区切りはしてないけど)も載せておく。

public class PriceTagHelper : TagHelper
{
    public decimal Value { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "span";
        var content = await output.GetChildContentAsync();
        output.Content.SetContent($"{content.GetContent()}円");
    }
}
<price>10000</price>

カンマ区切り以外は同じ結果が出力される。

<span>10,000円</span>

単一のタグに属性を付与する利点

  • 値を複数付与できる
  • モデルを入れたりも可能
  • 補完が効く

大体属性をうまく使ったほうが利点が大きい。
そもそも組み合わせることも可能なのでうまく使い分けたり組み合わせていきたいところ。

属性の補完が効くのはとてもいい。

f:id:igatea:20180531002111p:plain