読者です 読者をやめる 読者になる 読者になる

だるろぐ

とてもだるだるした日記です http://about.daruyanagi.jp/

お知らせ

WebMatrix + ASP.NET Web Pages でキレイにコーディングしたい(2)

ASP.net Web Pages WebMatrix

あと、 @RenderPage("_Footer.cshtml") は @RenderFooter() などと記述できるとカッコいいな。「フッターはテーマフォルダ直下の“_Footer.cshtml”に書く」。なるべく規約ベースで。これも簡単にできそうだ。

WebMatrix + ASP.NET Web Pages でキレイにコーディングしたい - だるろぐ

これをやってみた。なんていうか、“_Footer.cshtml”なんて固定値、あんまりよく目にするところに書いておきたくない。

f:id:daruyanagi:20120808030122p:plain

拡張メソッド

まずは拡張メソッドを試してみた。 WebPage クラスがあたかも最初から RenderFooter() をもっていたかのように見せかけるのが目的。“App_Code”フォルダを掘って、そのなかに C# クラスファイル(.cs)を作成する。 RenderBody() のシグネチャを参考にこういうのを作ってみた。

using System.Web.WebPages;

static public class WebPageExtensions
{
    static public HelperResult RenderFooter(
        this WebPage target, params object[] data)
    {
        return target.RenderPage("_Footer.cshtml", data);
    }
}

すると、 cshtml ファイルで @this.RenderFooter() という感じで呼べる。

:
:
        <div id="site-content">
            <article id="site-body">
@RenderBody()
            </article>
            <aside id="site-sidebar">
@RenderPage("_SideBar.cshtml") <!-- 古い書き方 -->
            </aside>
        </div>

        <footer id="site-footer">
@this.RenderFooter() <!-- 新しい書き方 -->
        </footer>
    </body>
</html>

そうなんだ、 this が要るんだ。 @RenderBody() みたいに this を使わずに呼びたかったけれど、これはどうしようもないっぽい。

Func<HelperResult>

次に考えたのは、 Func<HelperResult> を使うこと。最初の @{……} セクションで RenderFooter を定義しておけば、 this なしの @RenderFooter() で使えるはず。

<!DOCTYPE html>

@{
    App.Title = App.Title ?? "Untitled Application";
    App.Language = App.Language ?? "en";
    App.Encoding = App.Encoding ?? "utf-8";
    Page.Title = Page.Title ?? "Untitled Page";

    Func<HelperResult> RenderFooter =
        () => RenderPage("_Footer.cshtml");
}

<html lang="@App.Language">
    <head>
:
:

cshtml ファイルはこんなかんじになる。

:
:
        <div id="site-content">
            <article id="site-body">
@RenderBody()
            </article>
            <aside id="site-sidebar">
@RenderPage("_SideBar.cshtml") <!-- 古い書き方 -->
            </aside>
        </div>

        <footer id="site-footer">
@RenderFooter() <!-- 新しい書き方 -->
        </footer>
    </body>
</html>

目的は達成したけれど、これはこれでどうなんだろう。とりあえず今のところ単純なラムダ式でなんとかなっているけれど、たとえばRenderFooter でエラー処理を追加する場合(“_Footer.cshtml”がない場合がありえる)を考えると、「レイアウトファイルを簡潔にしたい」という目的からはだいぶ外れてくる。

RenderFooter をページの初期化に使う“_PageStart.cshtml”へ逃がそうかと思ったけれど、それもダメそうだし。結局、“_PageStart.cshtml”へ退避できるものだけ退避させて、あとはこんな感じにした。

<!DOCTYPE html>

@{
    Func<HelperResult> RenderHeader =
        () => RenderPage("_Header.cshtml");
    Func<HelperResult> RenderNavigation =
        () => RenderPage("_Navigation.cshtml");
    Func<HelperResult> RenderSideBar =
        () => RenderPage("_SideBar.cshtml");
    Func<HelperResult> RenderFooter =
        () => RenderPage("_Footer.cshtml");
}

<html lang="@App.Language">
    <head>
:
:

マジックワードを一元管理できるだけでも、まぁ、いいかな。

ちなみに @{……} を DOCTYPE 宣言のあとに書くように変えたのは、 XML ドキュメントを返すときとの統一性を考えて。むかし、 DOCTYPE 宣言の前に @{……} を書いて無駄な改行が入ってしまい、ちゃんと解釈してもらえなかったことがあったので。

おまけ

調べている途中でみつけたのだけれど、これおもしろいな。

@{
    Func<dynamic, object> b = @<strong>@item</strong>;
}
<span>This sentence is @b("In Bold").</span>

手元で試したらちゃんと動いたし。

Note that the delegate that’s generated is a Func. Also, the @item parameter is a special magic parameter. These delegates are only allowed one such parameter, but the template can call into that parameter as many times as it needs.

Templated Razor Delegates

なんでこうなるのかイマイチよくわからんけど……。 @ って結局なんなんだ(@w@!