しばやん雑記

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

Razor Deep Dive (2)

こんばんわ、mvcConf @:Japan ではメイドさんを雇う願望を持っていたのがばれてしまった男しばやんです。それではまた Razor の深いところに潜ってみましょう。

それでは前回の続きとして System.Web.WebPages の中身をもうちょっと深く見ていきましょう。基本的には WebPages なので WebMatrix などの Web サイトプロジェクトで使うためのヘルパークラスや拡張メソッドが数多く用意されています。

拡張メソッドに関しては以前まとめましたので、そちらを参照してください。

System.Web.WebPages にある拡張メソッドまとめ - しばやん雑記

さて、Razor には @helper 記法を使って、簡単に Web ヘルパーを記述することが出来ますね。じゃあ @helper で定義した Web ヘルパーは実体としてはメソッドなんですが、戻り値の型を知ってますか?知らない場合はツールチップを確認してくださいね。

はい、HelperResult 型になってますね。そして HelperResult 型は System.Web.WebPages 内で定義されているので、実装を見てみましょうか。

public class HelperResult : IHtmlString {
    private readonly Action<TextWriter> _action;

    public HelperResult(Action<TextWriter> action) {
        if (action == null) {
            throw new ArgumentNullException("action");
        }
        _action = action;
    }

    public string ToHtmlString() {
        return ToString();
    }

    public override string ToString() {
        using (var writer = new StringWriter(CultureInfo.InvariantCulture)) {
            _action(writer);
            return writer.ToString();
        }
    }

    public void WriteTo(TextWriter writer) {
        _action(writer);
    }
}

IHtmlString を実装していますが、これは二重に HTML エスケープされるのを回避するためです。気になるのは、このクラスは Action しか保持していないことですね。コンストラクタでデリゲートを受け取って何をやってるのでしょうか?

わからない場合は Web ヘルパーがどのようにコンパイルされているか確認しましょう。ASP.NET はビューを ASPX/Razor から C# へ変換を行ってから DLL にコンパイルするようになっています。つまり、C# への変換が行われた段階のコードを確認すればいいわけです。コンパイル結果は AppData 内の Temp ディレクトリに入ってるので、頑張って探してください。

それでは mvcConf で使ったヘルパーのコンパイル結果を確認してみましょう。ちなみに以下のような単純なヘルパーを使いました。

@helper TestHelper(string str)
{
<text>@str</text>
}

上記の Web ヘルパーのコンパイル結果が以下の通りです。見やすくするためにプリプロセッサディレクティブを削除しました。

public System.Web.WebPages.HelperResult TestHelper(string str)
{
    return new System.Web.WebPages.HelperResult(__razor_helper_writer =>
    {
        WriteTo(@__razor_helper_writer, str);

        WriteLiteralTo(@__razor_helper_writer, "\r\n");
    });
}

ヘルパーの内部が丸ごとラムダ式に置き換えられて、テキストブロックは WriteLiteralTo、変数の展開は WriteTo の呼び出しに変換されています。そして引数はクロージャが使われていますね。見た感じでは実行順序によってはおかしくなるんじゃないかと思ったんですが、実験したところ大丈夫っぽかったです。

後は HelperResult を WriteTo するコードが追加されるので、指定した場所にヘルパーの実行結果が出力されるわけですね。

ちなみに Razor で HelperResult を返すコードは Web ヘルパー以外にあと一つあります。それは Templated Razor Delegate です。何かわからない方は、これも前にまとめたので参照してください。

Templated Razor Delegates が面白い - しばやん雑記

今回は Web ヘルパーと Templated Razor Delegate の仕組みについてのまとめになりましたね。

そろそろ Razor の一番深い部分に届くと良いなと思いつつ、やっぱり誰得記事すぎて次回があるかわからないので、また次回があれば次回お会いしましょう!