ビュー・ヘルパーの自作
以上、ここまで見てきても分かるように、ASP.NET MVCではビュー開発を支援するビュー・ヘルパーをあまた提供している。しかし、実際にアプリケーションを開発していくうえでは、「こんなヘルパーも欲しい」と感じる局面も少なくないはずだ。
そこで本稿後半では、ビュー・ヘルパーを自作する方法について解説する。
■シンプルなビュー・ヘルパーを定義する
まずは最もシンプルな形でビュー・ヘルパーを作成してみよう。例えば以下は、与えられたURL(url)と代替テキスト(alt)を基に<img>タグを生成するImageTagメソッドの例だ。
using System.Web;
using System.Web.Mvc;
namespace MvcTemplate.Helpers
{
public static class WingsHelper
{
public static IHtmlString ImageTag(string url, string alt)
{
return MvcHtmlString.Create(string.Format(
"<img src=\"{0}\" alt=\"{1}\" title=\"{1}\" />",
HttpUtility.HtmlAttributeEncode(url),
HttpUtility.HtmlAttributeEncode(alt)
));
}
}
}
|
Namespace Helpers
Public Module WingsHelper
Public Function ImageTag(ByVal url As String, ByVal alt As String) As IHtmlString
Return MvcHtmlString.Create(String.Format(
"<img src=""{0}"" alt=""{1}"" title=""{1}"" />",
HttpUtility.HtmlAttributeEncode(url),
HttpUtility.HtmlAttributeEncode(alt)
))
End Function
End Module
End Namespace
|
|
リスト10 ImageTagヘルパーを定義したコード(上:WingsHelper.cs、下:WingsHelper.vb) |
ビュー・ヘルパーを定義したクラスは、慣例的に「~/Helpers」フォルダに作成しているが、これといって規約があるわけではない。 |
ImageTagメソッドに注目してみると、ビュー・ヘルパーとはいっても何ら特別な構文があるわけではなく、単なるstaticメソッド(Visual Basicはモジュール・メソッド)であることが分かる。引数として受け取ったurl(画像URL)、alt(代替テキスト)を基に<img>タグを整形し、戻り値として返しているだけだ。
1点だけ、戻り値は(String型ではなく)IHtmlString型として返している点に注意してほしい。前回も触れたように、IHtmlString型は「HTMLエンコード済みであること」を表すオブジェクトで、ASP.NETに対して「文字列をエンコードしてはならない」ことを通知するものだ。String型のままでは、戻り値に含まれるタグはRazor側でエスケープ処理されてしまうので注意してほしい*3。String型をIHtmlString型に変換するには、MvcHtmlString.Createメソッドを呼び出すだけだ。
*3 もちろん、Razor側でHtml.Rawメソッドを呼び出しても構わないが、それは望ましい状態ではない。
|
実際に、ImageTagヘルパーを呼び出し、正しく動作することも確認しておこう。
@using MvcTemplate.Helpers;
@WingsHelper.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎")
|
@Imports MvcTemplateVb
@WingsHelper.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎")
|
|
リスト11 ImageTagヘルパーを呼び出すためのコード(上:C#、下:VB) |
<img src="http://www.wings.msn.to/image/wings.jpg"
alt="サーバーサイド技術の学び舎" title="サーバーサイド技術の学び舎" />
|
|
リスト12 実行時にリスト11により出力されるHTMLコード |
■ビュー・ヘルパーの名前空間を登録する
もっとも、個別のビュー・テンプレートでusing/Import命令を呼び出すのは、あまりスマートな方法ではない。ビュー・ヘルパーは複数のビュー・テンプレートで共有するであろうことを考えても、あらかじめ名前空間をアプリケーションに登録しておくのが望ましい。
Razorで共通して利用する名前空間を登録するには、「~/Views/Web.config」から<pages>−<namespaces>−<add>要素を追加すればよい(アプリケーション・ルート直下のWeb.configではない点に要注意)。
<system.web.webPages.razor>
……中略……
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
……中略……
<add namespace="System.Web.Routing" />
<add namespace="MvcTemplate.Helpers"/>
</namespaces>
</pages>
</system.web.webPages.razor>
|
|
リスト13 ビュー・ヘルパーの名前空間を登録するコード(Web.config) |
この状態で、試しにリスト11からusing/Import命令を取り除いてみると、同じくビュー・ヘルパーが正しく呼び出され、MvcTemplate.Helpers名前空間が認識されていることが確認できるはずだ。
■拡張メソッドとしてビュー・ヘルパーを定義する
リスト10の例では、新たなヘルパー・クラスWingsHelperに対してヘルパー・メソッドを定義したが、ヘルパー・クラスが無制限に増えていくのは望ましいことではない。開発者がそれぞれにヘルパー・クラスを定義した結果、利用者がそれぞれの所属を意識して、「WingsHelper.〜」「MicrosoftHelper.〜」のように書かなければならないとしたら、使い勝手も良くない。
そこで登場するのが、拡張メソッドという機能だ。拡張メソッドとは、既存のクラスに対して(継承によらず)メソッドだけを追加する仕組みのこと。拡張メソッドを利用することで、いまあるクラスに手を加えることなく、あたかも元からあったメソッドであるかのように新たなメソッドを追加できるわけだ*4。拡張メソッドについては、「C#開発者のための拡張メソッド入門」、「VB開発者のための拡張メソッド入門」などの記事も参照されたい。
*4 実は、標準のビュー・ヘルパーもその多くは拡張メソッドとして定義されている。
|
以下は、リスト10のImageTagヘルパーの宣言開始部分を拡張メソッドとして書き換えた例である。
public static IHtmlString ImageTag(this HtmlHelper helper, string url, string alt)
|
<System.Runtime.CompilerServices.Extension()>
Public Function ImageTag(ByVal helper As HtmlHelper, ByVal url As String, ByVal alt As String) As IHtmlString
|
|
リスト14 リスト10をHtmlHelperクラスの拡張メソッドとして書き換えたコード(上:WingsHelper.cs、下:WingsHelper.vb) |
拡張メソッドであることの条件は、以下の2点である。
(C#の場合)
- staticクラスのstaticメソッドであること
- 第1引数として、thisキーワードを付けて拡張するクラスを指定すること(この場合であれば、ビュー・ヘルパーを取りまとめる、おおもとのクラスであるHtmlHelperクラス)
(Visual Basicの場合)
- モジュールに属するメソッドであること
- 第1引数として、拡張するクラスを指定すること(この場合であれば、ビュー・ヘルパーを取りまとめる、おおもとのクラスであるHtmlHelperクラス)
ImageTagヘルパーの場合、すでにリスト10でstaticメソッド/モジュール・メソッドとして定義されているので、後は第1引数を追加してやるだけでよい。この状態で、ビュー・テンプレートからImageTagメソッドを呼び出すには、以下のようなコードを書けばよい。
@Html.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎")
|
|
リスト15 リスト14で修正したImageTagヘルパーを呼び出すコード |
MvcTemplate.Helpers.WingeHelperクラスのメンバであるにも関わらず、HtmlHelper(Html)経由でImageTagメソッドを呼び出せている点に注目してほしい。
なお、拡張メソッドを利用する場合も、拡張メソッドを定義した名前空間(この場合はMvcTemplate.Helpers名前空間)をインポートしておく必要がある。さもないと、拡張メソッドの実体をASP.NETが検索できないためだ。ただし本稿では、すでにWeb.configで名前空間を登録済みであるので、ビュー・テンプレートにusing/Import命令の記述は不要だ。
■TagBuilderクラスによるヘルパーの実装
ここまでの例では、タグ文字列を文字列として組み立ててきたが、複雑なタグを生成する局面ではコードも冗長になりがちであるし、何より属性値や本体テキストのエスケープ処理が漏れる原因にもなる。
そこでASP.NET MVCでは、タグ文字列を生成するためにTagBuilderというクラスを用意している。TagBuilderクラスを利用することで、文字列加工よりも直感的に、かつ、複雑なタグもよりシンプルなコードでタグ文字列を生成することが可能になる。
例えば以下は、ImageTagメソッドを、TagBuilderクラスを使って書き換えた例だ。なお、書き換えるに当たって第3引数として任意のHTML属性を表すhtmlAttrs(匿名型)を受け取れるように修正している。
public static IHtmlString ImageTag(this HtmlHelper helper, string url, string alt, object htmlAttrs)
{
// <img>タグを生成
var img = new TagBuilder("img");
// src、alt属性を追加
img.MergeAttribute("src", url);
img.MergeAttribute("alt", alt);
// 引数htmlAttrsを基に、そのほかの属性を追加
img.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttrs));
// 以上の設定を基にHTML文字列を出力
return MvcHtmlString.Create(img.ToString(TagRenderMode.SelfClosing));
}
|
<System.Runtime.CompilerServices.Extension()>
Public Function ImageTag(ByVal helper As HtmlHelper, ByVal url As String, ByVal alt As String, ByVal htmlAttrs As Object) As IHtmlString
' <img>タグを生成
Dim img As New TagBuilder("img")
' src、alt属性を追加
img.MergeAttribute("src", url)
img.MergeAttribute("alt", alt)
' 引数htmlAttrsを基に、そのほかの属性を追加
img.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttrs))
' 以上の設定を基にHTML文字列を出力
Return MvcHtmlString.Create(img.ToString(TagRenderMode.SelfClosing))
End Function
|
|
リスト16 TagBuilderクラスを利用して書き換えたImageTagヘルパー(上:WingsHelper.cs、下:WingsHelper.vb) |
TagBuilderはとてもシンプルなクラスで、おおよそ以下の手順でタグを生成できる。
- コンストラクタでタグ名を指定
- MergeAttribute/MergeAttributesメソッドで属性を追加
- ToStringメソッドでタグ文字列を出力
太字部分のAnonymousObjectToHtmlAttributesメソッドは、匿名型をIDictionary型に変換するためのメソッドだ(同時にプロパティ名に含まれるアンダースコアをダッシュに変換する)。MergeAttributesメソッドは引数として匿名型を受け取ることができないので、このようにあらかじめ変換処理が必要になる。
ToStringメソッドの引数では、タグの出力形式を指定できる。指定可能な値は、以下のとおり。
設定値 |
概要 |
Normal |
開始タグ〜出力タグを出力 |
StartTag |
開始タグのみを出力 |
EndTag |
終了タグのみを出力 |
SelfClosing |
空要素(<tag />)を出力 |
|
表3 ToStringメソッドの引数(TagRenderMode列挙体のメンバ) |
|
ただし、ToStringメソッドの戻り値は単なるString型であるので、先ほどと同じくMvcHtmlString.CreateメソッドでIHtmlStringオブジェクトに変換しておく必要がある。
それではさっそく、修正したヘルパーを呼び出してみよう。
@Html.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎", new { height=50, width=150 })
|
@Html.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎", New With {.height = 50, .width = 150})
|
|
リスト17 リスト16で修正したImageTagヘルパーを呼び出すコード |
<img alt="サーバーサイド技術の学び舎" height="50"
src="http://www.wings.msn.to/image/wings.jpg" width="150" />
|
|
リスト18 実行時にリスト17により出力されるHTMLコード |
■Razor構文でビュー・ヘルパーを生成する
もう1つ。ビュー・ヘルパーは、Razor構文の「@helper命令」を利用して定義することも可能だ。例えば、以下は先ほど作成したImageTagヘルパーをRazor構文で定義した例である。
@helper ImageTag(string url, string alt, object htmlAttrs) {
<img src="@url" alt="@alt"
@foreach (var item in System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttrs))
{
<text>@item.Key = "@item.Value"</text>
}
/>
}
|
@Helper ImageTag(ByVal url As String, ByVal alt As String, ByVal htmlAttrs As Object)
@<img src="@url" alt="@alt"
@For Each item In System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttrs)
@<text>@item.Key = "@item.Value"</text>
Next
/>
End Helper
|
|
リスト19 Razor構文で書き直したImageTagヘルパー(上:Wings.cshtml、下:Wings.vbhtml) |
@helper命令の構文は、以下のとおりだ。
@helper ヘルパー名(引数, ……) {
ヘルパーの本体
}
|
@Helper ヘルパー名(引数, ……)
ヘルパーの本体
End Helper
|
|
リスト20 @helper命令の構文(上:C#、下:VB) |
@helper命令を頭に冠した後は、ほぼ標準的なC#、Visual Basicのメソッド定義と同じ要領でコードを記述できることが分かるだろう。標準的なメソッドと異なる点は、
- 戻り値を持たない(=ヘルパー配下で出力コンテンツを記述する)
- @helperブロック配下ではRazor構文を利用できる
という2点だ。また、作成したコードは、アプリケーション・ルート配下のApp_Codeフォルダに、(例えば)Wings.cshtml/Wings.vbhtmlのような名前で配置する必要がある。このように定義したヘルパーは、「ベース名.ヘルパー名(引数, ……)」の形式で呼び出せる*5。
*5 特定のビューでしか利用しないのであれば、ビュー・テンプレート本体に記述してもよい。その場合は、「ヘルパー名(引数, ……)」で呼び出せる。
|
@Wings.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎", new { height=50, width=150 })
|
@Wings.ImageTag("http://www.wings.msn.to/image/wings.jpg",
"サーバーサイド技術の学び舎", New With {.height = 50, .width = 150})
|
|
リスト21 リスト19で定義したImageTagヘルパーを呼び出すコード(上:C#、下:VB) |
非コンテンツ部分(=VB/C#のコード)が多くなってくると、@helper構文はかえってコードが読みにくくなる恐れもあるが、コンテンツ主体のヘルパーでは積極的に活用していくとよいだろう。
■
以上、今回はビュー・ヘルパーについてさらに踏み込んで、DisplayFor/EditorForヘルパーと、ヘルパーの自作について解説した。次回はRazor編の最終回として、アプリケーション全体のデザインを共通化するレイアウトや部分ビューについて解説する予定である。