だるろぐ

明日できることは、今日しない。

寄り道: Rails の Flash っぽい機能を WebMatrix で使いたい(2) ―― @helper と @functions とわたし

寄り道: Rails の Flash っぽい機能を WebMatrix で使いたい - だるろぐ の話は続く。

# ~/_AppCode/Flash.cshtml

@helper Read()
/* Razor を書く */ {
    if (Session["flash"] == null) { return; }
    // Razor では if 文の {} を省略できない。これ、マメな。
    
    <div class="message info"><p>@Session["flash"]</p></div>
    Session["flash"] = null;
}

@functions
/* 通常の C# 構文を書く */ {
    public static void Write(string value)
    {
        Session["flash"] = value;
    }
}

この @helper ってなんなんだろうな。便利なのはいいけれど、中身がわからないのは気持ち悪い。まずは Visual Studio のツールチップでのぞいてみた。

f:id:daruyanagi:20120830023803p:plain

とりあえず、 @helper Hoge(args) {……}public static HelperResult Hoge(args) {……} (引数をとって HelperResult を返す+なんらかの処理)ということのようだ。実際、 @helper Read() は @functions で表すこともできないことはない。

@functions
{
    public static HelperResult Read()
    {
        return new HelperResult(w =>
        {
            if (Session["flash"] == null) { return; }

            /* <div class="message info"><p>
                   @Session["flash"]
               </p></div> */
            WriteLiteralTo(w, "<div class=\"message info\"><p>");
            WriteTo(w, Session["flash"]); 
            WriteLiteralTo(w, "</p></div>");

            Session["flash"] = null;
        });
    }
}

というわけで書いてみたのがこれ(参考:Razor Deep Dive (2) - しばやん雑記)。たぶんきっと内部ではこういうことをしているんだと思う(ラムダの引数 w は TextWriter型)。

f:id:daruyanagi:20120830034439p:plain

HelperPage(http://msdn.microsoft.com/en-us/library/system.web.webpages.helperpage(v=vs.111).aspx)っていうのは、WebPage を継承していて「Represents a base class for pages that is used when ASP.NET compiles a .cshtml or .vbhtml file and that exposes page-level and application-level properties and methods.」なんだそうな。

あと、WriteLiteralTo() はエスケープされず、 WriteTo() はエスケープされないようだ。これで、Razor で書いた HTML タグはそのまま、外から与えられた変数(@Session["flash"])だけ無毒化される。ほへぇ。

@functions
{
    public static HtmlString Read()
    {
        if (Session["flash"] == null) return null;
        
        var result = new HtmlString(
            string.Format(
                @"<div class=""message info""><p>{0}</p></div>",
                Session["flash"]
            )
        );

        Session["flash"] = null;

        return result;
    }
}

今までこんなの書いて「動いたヒャッハー!」とか思ってたけど、ただ HtmlString 型の戻り値を Razor が変数として処理してくれたいたから見かけ上ちゃんと動いていただけなんだな。

ちなみに、~/_AppCode で Hoge.cshtml を書くと、その内容は namespace ASP { public class Hoge: HelperPage } になる。 @functions にはそのクラスの実装になっているみたいだな。で、普通 @... {} 以外の部分はレンダリング(RenderBody()とか)されるときに変数が逐次解釈されながら TextWriter へ書き込まれるんだけど、 _AppCode はレンダリングで呼ばれることがないから書いても無駄。

# ~/App_Code/Flash.cs

using System.Web;
using System.Web.WebPages;

namespace ASP
{
    public class Flash: HelperPage
    {
        public static void Write(string value)
        {
            Session["flash"] = value;
        }

        public static HelperResult Read()
        {
            return new HelperResult(w =>
            {
                if (Session["flash"] == null) return;

                WriteLiteralTo(w, "<div class=\"message info\"><p>");
                WriteTo(w, Session["flash"]);
                WriteLiteralTo(w, "</p></div>");

                Session["flash"] = null;
            });
        }
    }
}

今回のヘルパーを cshtml ではなく cs で表現してみた。あっているかどうかイマイチ不安だけれども、ちゃんと動いてるみたい。

とりあえず、今のところはそういう理解にしておこう。