Umbraco で Razor を使ってプログラミング

Umbraco 4.10+ の MVC Razor については、WikiUmbraco MVC についての方にメモしています。以下は、Umbraco 4.6 で導入された WebPages Razor についてのメモです。

Umbraco 4.7 では、Razor が改良されて本格的に利用できるようになっています。Razor を使うと手軽に Umbraco の機能を拡張することができます。

Umbraco で Razor を使う基本

Umbraco で Razor を使う場合、テンプレートに直接記述することもできるし、スクリプトファイルに記述することもできます。

テンプレートに直接記述する場合は、以下の例のように Razor の記述を umbraco:Macro タグで挟んでやります。

<umbraco:Macro runat="server" language="razor">
  @DateTime.Now;
</umbraco:Macro>

umbraco:Macro タグに Cache プロパティを追加することによりキャッシュを効かせることもできます。単位は秒で、以下の例のように記述すれば、10秒間キャッシュされます。

<umbraco:Macro runat="server" language="razor" Cache="10">
  @DateTime.Now;
</umbraco:Macro>

スクリプトファイルに記述する場合は、Developer セクションの Scripting Files を選択して、右クリックして、Create を選択すると、以下のようなダイアログが表示されます。Choose a language では、C#(Cshtml) と Visual Basic(Vbhtml)を選択することができます。Choose a template では、数個のテンプレートが用意されています。ここでは例として、Site Map を選択してみました。
razor01

すると SiteMap.cshtml が macroScripts フォルダーに作成され、その内容は以下のようになります。

@inherits umbraco.MacroEngines.DynamicNodeContext

<em>DynamicNode:</em><br/>
@helper traverse(dynamic node){
  var maxLevelForSitemap = 4;
  var values = new Dictionary<string,object>();
  values.Add("maxLevelForSitemap", maxLevelForSitemap);

  var items = node.Children.Where("umbracoNaviHide != true; Level <= maxLevelForSitemap", values);
  <ul>
    @foreach (var item in items) {
      <li><a href="@item.Url/">@item.Name</a>
        @traverse(item)
      </li>
    }
  </ul>
}
<div id="sitemap">
  @traverse(@Model.AncestorOrSelf())
</div>

Razor スクリプトができたら、Macro に組み込みます。Razor スクリプトの作成の時に「Create Macro」のチェックをしていた場合は、すでにマクロは作成されています。「Create Macro」をチェックしていない場合でも、マクロに組み込むのは簡単で、Developer セクションの「Macro」を選択して、右クリックして、「Create」を選択する名前を入力するダイアログが表示されるので適当な名前を入力して「Create」ボタンをクリックします。すると下の図のようにマクロのプロパティの設定画面が表示されるので、「or script fle」の項目の「Browse scripting files on server...」のドロップダウンボタンをクリックするとスクリプトファイルの一覧が表示されるのでそれを選択するだけです。

マクロは、既定でテンプレートに挿入して利用することができますが、今回のサンプルのように作成した Razor スクリプトをドキュメントに挿入して利用したい場合は、「Use in editor」にチェックをいれます。また、Cache Period に数値を入力することによりキャッシュを有効にすることもできます。
razor04.png

テンプレートでマクロを挿入したい場合は、挿入したい位置にカーソルを移動して、編集画面の上部にあるツールバーのマクロ(赤丸)ボタンをクリックして、挿入したいマクロを選択するだけです。
razor05.png

また、ドキュメントで挿入したい場合も同じように、挿入したい位置にカーソルを移動して、編集画面の上部にあるツールバーのマクロ(赤丸)ボタンをクリックして、挿入したいマクロを選択するだけです。
razor06.png

※ Visual Studio で Razor の開発環境を構築

Razor をスクリプトファイルに記述する場合は、Visual Studio 2010 又は Visual Web Developer 2010 Express を使うと、インテリセンス及びデバッグの機能を利用することができます。VS を使うというと難しいと思う人も多いのですが、以下のようにすれば簡単に環境を構築することができます。

VS で Razor のインテリセンス等を有効にするためには、ASP.NET MVC 3 が必要です。ASP.NET MVC 3 をインストールしていない場合は、Web PI でインストールするか、Microsoft の ダウンロードのページからファイルをダウンロードしてインストールしておきます。

VS を起動して、「新規作成」-> 「プロジェクト」(VWDの場合は「新しいプロジェクト」)で、「ASP.NET 空の Web アプリケーション」を作成します。次に、作成しておいた Umbraco のサイトのファイルをすべてコピーして、VS で作成した Web アプリケーションのフォルダーに上書きコピーします。VS のソリューションエクスプローラーでは、以下の図のようになり、ほとんどすべてのファイルはプロジェクトに含まれていません。それでも、デバッグ開始をすると、Umbraco のサイトが起動します。
razor02

SiteMap.cshtml を VS のエディターで表示させると、以下の図のように、インテリセンスも効くし、ブレークポイントも設定することができるようになります。ただし、変数が Dynamic のものについては、インテリセンスは効きませんが、変数を DynamicNode や DynamicNodeList 等適切な型で定義してやればインテリセンスを効かすことができます。なお、cshtml ファイルは、プロジェクトに含めていませんが、インテリセンスやブレークポイントを設定してデバッグをすることができます。
razor03

Umbraco で Razor を使ってみる

2011年1月にWebMatrix が公開されて、Razor 構文を用いた ASP.NET Web ページの開発ができるようになりましたが、Umbraco でも Razor 構文を使ってシステム拡張ができるようになりました。Umbraco の場合は、既に CMS の機能を持っているので、WebMatrix だけで Razor を使うより、はるかに高機能なことが簡単にできます。

Umbraco の Razor では、WebMatrix で Razor を使うのと同じ razor エンジンを使っており基本的には同じことができますが、Umbraco の Razor で便利なのは MVC モデルのように Model プロパティが用意されており、それを使ってコンテンツにアクセスできるということです。Umbraco の Model プロパティは、基本的にはカレントノードを表します。例えば、以下の式でカレントノードの名前や Url を取得することができます。

  • @Model.Name (カレントノードの名前)
  • @Model.Url(カレントノードのUrl)
  • @Model.Id(カレントノードのId)

Document Type のプロパティも @Model.bodyText のようにして取得することができます。

カレントノードの親ノードや子ノードも以下のようにして取得できます。

  • @Model.Child(子ノードのリスト)
  • @Model.Parent(親ノード)
  • @Model.AncestorOrSelf()(トップノード)

Umbraco の Razor 関係の資料は、Umbraco documentation wikiReference Razor snippets にまとめられているので、詳細を知りたい場合はそれを参考にしてください。

ここでは、Umbraco で Razor を使う例として、umbTopNavigation.xslt と同じ機能をもつナビゲーションを作成してみます。最初の例は、ホームページの子ページをすべて表示するナビゲーションです。

@inherits umbraco.MacroEngines.DynamicNodeContext

@{
  var homepage = Model.AncestorOrSelf();
  <ul id="topNavigation">
  <li class="home"><a href="/">Home</a></li>
  @foreach (var c in homepage.Children)
  {
      <li><a class="navigation" href="@c.Url/"><span>@c.Name</span></a></li>
  }
  </ul>
}

次に、umbracoNaviHide が true の場合は、ナビゲーションから除外するようにします。ページの Document Type のプロパティに umbracoNaviHide が存在しない場合に、c.umbracoNaviHide は、DynamicNull オブジェクトを返してくるので、下の例では、まず DynamicNull でないかどうかの確認をしています。

@inherits umbraco.MacroEngines.DynamicNodeContext
@using umbraco.MacroEngines

@{
  var homepage = Model.AncestorOrSelf();
  <ul id="topNavigation">
  <li class="home"><a href="/">Home</a></li>
  @foreach (var c in homepage.Children)
  {
      if (c.umbracoNaviHide is DynamicNull || c.umbracoNaviHide == false)
      {
        <li><a class="navigation" href="@c.Url/"><span>@c.Name</span></a></li>
      }
  }
  </ul>
}

umbracoNaviHide が true の場合にナビゲーションから除外するのは、if 文ではなく、下の例のように Where を使う方が簡単なようです。Where は、ページの Document Type のプロパティに umbracoNaviHide が存在しない場合でも、そのノードをひらってくるので上の例と同じになります。

@inherits umbraco.MacroEngines.DynamicNodeContext

@{
  var homepage = Model.AncestorOrSelf();
  <ul id="topNavigation">
  <li class="home"><a href="/">Home</a></li>
  @foreach (var c in homepage.Children.Where("umbracoNaviHide==false"))
  {
        <li><a class="navigation" href="@c.Url/"><span>@c.Name</span></a></li>
  }
  </ul>
}

次に、ナビゲーションがカレント表示に対応するように修正してみました。Razor では計算式の中にある全ての文字列が必ず HTML エンコードされるようになっています。それで、Html.Raw() ヘルパーを使って、HTML 文字列をそのまま出力するようにしています。

@inherits umbraco.MacroEngines.DynamicNodeContext

@{
  var homepage = Model.AncestorOrSelf();
  var current = Model.AncestorOrSelf(2);
  <ul id="topNavigation">
  <li @(Model.Level == 1?Html.Raw("class=\"home current\""):Html.Raw("home"))><a href="/">Home</a></li>
  @foreach (var c in homepage.Children.Where("umbracoNaviHide==false"))
  {
        <li @(c.Id == current.Id?Html.Raw("class=\"current\""):Html.Raw(""))><a class="navigation" href="@c.Url/"><span>@c.Name</span></a></li>
  }
  </ul>
}

Umbraco API を利用した Umbraco の操作

上のの例では、Model プロパティを使った例でしたが、その場合はプロパティが読み取り専用のため、Umbraco のサイトを操作することはできません。Umbrco を操作したい場合には、Umbraco API を使用します。Umbraco API を使えば、Umbraco を自由に操作することができます。Umbraco API についてのリファレンスは、Umbraco documentation wikiReferenceAPI Cheatsheet にあります。このリファレンスは、英語ですがサンプルもあって読みやすいので興味があればみてください。

以下の例では、フォームに名前と内容を入力して送信ボタンをクリックすると、入力した名前のドキュメントが作成されるという例です。Starter Kit で simple を選択した場合は、「Getting Started」の子として新規ドキュメントが作成されます。環境が違う場合は、DocumentType.GetByAlias() と Document.MakeNew() のパラメータを適宜変更してください。

@using umbraco.BusinessLogic;
@using umbraco.cms.businesslogic.web;

<form method="post" action="">
<p>名前:@Html.TextBox("Title")</p>
<p>内容:@Html.TextArea("BodyText")</p>
<p><input type="submit" value="送信" /></p>
</form>
 @{
     if (IsPost)
     {
         //名前と内容のデータを取得
         string Title = Request.Form["Title"];
         string BodyText = Request.Form["BodyText"];
         if(Title != "")
         {
             //作成する Document Type を Alias で設定。
             DocumentType dt = DocumentType.GetByAlias("umbTextpage");
             //ドキュメントを作成するユーザーを設定する。
             User author = umbraco.BusinessLogic.User.GetUser(0);

             //名前、Document Type、ユーザー、ドキュメントのID を指定して、ドキュメントの子として新規ドキュメントを作成。
             //Umbraco のルートドキュメントの ID は、-1 である。
             Document doc = Document.MakeNew(Title, dt, author, 1051);
             doc.getProperty("bodyText").Value = BodyText;

             //ドキュメントの公開を行う。
             doc.Publish(author);

             //キャッシュの更新を行う。
             umbraco.library.UpdateDocumentCache(doc.Id);
    
             //画面にドキュメントの作成に成功したことを表示。
             <p>
                @Title のドキュメントを作成しました<br />
             </p>
         }
    }
}

この例からもわかると思うのですが、Umbraco API を使えば、Web ページ内のある名前を別の名前に変更するというようなことを自動化するとかいろんなことが比較的簡単にできてしまいます。Umbraco ほど簡単にプログラムからコンテンツを操作することができる CMS はないと思います。