miso_soup3 Blog

主に ASP.NET 関連について書いています。

@using () {...} を使ったカスタムHtmlヘルパーの作り方

この記事で知れること

  • using (@Html.BeginForm()) {} が何をしているのかわかる
  • using で書けるカスタムHtmlヘルパーを作れる

まえがき

using を使ったHtmlヘルパーといえば、@Html.BeginForm()です。
Formタグを出力したいとき、Viewでこのように書くと…

@using (Html.BeginForm())
{
	<input type="submit" value="追加!" />
}

このように出力されます。

<form action="/" method="post">
	<input type="submit" value="追加!" />
</form>

今日は、このusingを使った独自のHtmlヘルパーを作り、
へーBeginFormってこうなっているんだーと、確認してみます。

このように書くと…、

@using (Html.BeginRead())
{
    <p>
        吾輩は猫である。名前はまだ無い。
    </p>
}

このように出力されるHtmヘルパーを作成します。
ここからここまで読んだヘルパーです。

<span>----------------ここから↓</span>
    <p>
        吾輩は猫である。名前はまだ無い。
    </p>
<span>----------------↑ここまで読んだ</span>

Html.BeginForm()の中身

@using (Html.BeginForm())は何をやっているのか、MVCのソースと合わせて確認します。

@using () { } は、C#の構文であるusingを、Razorで記述したものです。
using ()の中は、IDisposableを実装したクラスでなければいけません。

Html.BeginForm()メソッド は、IDisposableを実装したクラスを返しています。
メソッド実行時にformの開始タグが出力され、
IDisposableを実装したクラスのDispose()メソッドで、閉じタグが出力されます。

こんな感じ。

MVCのソースで確認

MVCのソースを見て確認してみます。
FormExtensions.cs
Html.BeginForm()メソッドの実際の処理は、このFormExtensionsクラスの1番下にあります。

FormタグをhtmlHelper.ViewContext.Writer.Write(..)で出力し、
IDisposable実装したMvcFormクラスを返しています。

MvcFormクラスのDispose()メソッドを見てみます
MvcForm.cs
メソッド内で、FormExtensions.EndForm(_viewContext);
が呼ばれているので、先ほどのクラスに戻ります。
(前はここで_writer.Write("");と書かれていました)

開始タグと同じように、出力タグも出力されています。

カスタムHtmlヘルパー実装

IDisposableを実装したクラスを返すHtmlヘルパーを実装し、
開始と終了時に、Writer.Write("<..>"); が呼ばれるようにすればOKです。

コードはこんな感じになりました。

public static class ReadingHelper
{
    public static void BeginReadTagWrite(this HtmlHelper htmlHelper)
    {
        var tagBuilder = new TagBuilder("span");
        tagBuilder.SetInnerText("----------------ここから↓");
            
        htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.Normal));
    }

    public static void EndReadTagWrite(this HtmlHelper htmlHelper)
    {
        var tagBuilder = new TagBuilder("span");
        tagBuilder.SetInnerText("----------------↑ここまで読んだ");

        htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.Normal));
    }

    public static DisposableHelper BeginRead(this HtmlHelper htmlHelper)
    {
        Action start = () => BeginReadTagWrite(htmlHelper);
        Action end = () => EndReadTagWrite(htmlHelper);

        start();
        return new DisposableHelper(end);
    }
}

public class DisposableHelper : IDisposable
{
    public DisposableHelper(Action end)
    {
        _end = end;
    }

    private bool _disposed;
    private Action _end;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            _disposed = true;
            _end();
        }
    }
}

実用性無し?

でも、このusingを使ったカスタムHtmlヘルパー、実用性はあるのでしょうか…。
Formタグの場合は、送信時に中のinput要素の値を送る、という機能が関係しているからこそ、
このusingが生きているのだと思います。

上の例のように、ただ出力するだけのusingのヘルパーは作る必要がない…と思います。
インデント1コ消費しちゃいますし。
何か良い例はないかな〜と探していたのですが、見つけることができませんでした。


ここからここまで読んだヘルパーさん

実装は、下のサイトを参考にしました。
Custom html helpers: Create helper with “using” statement support

追記

素晴らしい使い方がありました!!
ASP.NET MVC で Twitter Bootstrap を使ってみた (4)