技術日記
技術調査記録Edit

ASP.NET MVCで独自の(ビューの)ヘルパー(メソッド)を追加したい。ググると出てきたのが、[C#] #13. カスタム HTML ヘルパーの作成 | ASP.NET MVC のチュートリアル。2010年5月20日のドキュメントだから、賞味期限的にも大丈夫そう。

このドキュメントを見る限りは、ヘルパーといっても基本的には単に文字列を返すC#のスタティックメソッドをImport Namespaceでビューから見えるようにしてやっているだけか。

サンプル コード 5 のクラスは、Label() という名前の HtmlHelper クラスに拡張メソッドを追加します
とあるけど、これは「サンプルコード5(のクラス)は、HtmlHelperクラスにLabel()という名前の拡張メソッドを追加します」だよな。

既存のHtmlHelperクラスに拡張メソッドを追加するために、LabelExtensionsというスタティッククラスを定義し、その中のLabel()メソッドの第1パラメータをthis HtmlHelper helperとすることで、そのメソッドが既存のHtmlHelperクラスの拡張メソッドであることを宣言しているから、最初のサンプルでは、

[sourcecode language="c#"]
<%@ Import Namespace="MvcApplication1.Helpers" %>
<%=@LabelHelper.Label("firstName", "First Name: ") %> 
[/sourcecode]
みたいに書いていたものを、
[sourcecode language="c#"]
<%@ Import Namespace="MvcApplication1.Helpers" %>
<%=Html.Label("firstName", "First Name: ") %>
[/sourcecode]
みたいに書けるよってことだね。

ちょっとした拡張ならば後者の方が良さそうなんで、後者の方法で書いてみることにする。

[sourcecode language="c#"]
using System.Web.Mvc;

namespace MyApp.Helpers { public static class HelperExtensions { public static string Summarize(this HtmlHelper helper, string text, int length = 80, string suffix = "..") { if (text.Length <= length) { return text; } string result = text.Substring(0, length - suffix.Length) + suffix; return result; } } } [/sourcecode]

こんな感じで、@Html.Summarize(text)とかすると、80文字以上の長いテキストを切り詰めて表示してくれる。

ちなみにRazorでの<%@ Import Namespace = "MyApp.Helpers" %>相当の命令は、単純に@using MyApp.Helpers;でよかった。

続いて、通常HTML表示している内容を、API向けにXMLで出力する方法。XMLはテンプレートで出力するんで、IndexXml.cshtmlとかXML出力用のテンプレートを用意し、アクションメソッドの中で、

[sourcecode language="c#"]
if (format == "xml") {
  Response.ContentType = "text/xml";
  return View("IndexXml", data);
}
[/sourcecode]
みたいな感じにした。テンプレートは元々PHPで書いていたものをコピーしてきて、Razor向けに書き換え。PHPで書いたテンプレートをRazorで書き直したら、Razorの方がだいぶ書きやすかった。型付けされたモデルクラスのおかげでVisualStudio上で自動補完も効くし。

これでOKかと思いきや。実際にはだめ。というのはよく考えたらレイアウトファイル(マスターページ)が適用されてしまうから、XMLの前後にHTMLのレイアウトページが出力されてしまう。レイアウト出力の抑制ってどう書くんだ?

ひとまずテンプレートファイルの中で、@{Layout = null;}をしておくと、レイアウトの抑制はできた。けど、アクションメソッドの方でコントロールしたいな。

一応ViewResultのオプションでマスターページ名を指定できるみたいなんで、

[sourcecode language="c#"]
return View("IndexXml", null, data);
[/sourcecode]
とかやってみたんだけど、デフォルトのレイアウトが適用されてしまう。テンプレートの中でLayout = null;はいけるのに、マスターページとしてnullを渡したんじゃだめなのか。しょうがないから、_LayoutNothing.cshtmlとかいう名前で、
[sourcecode language="c#"]
@RenderBody();
[/sourcecode]
みたいに何もしないレイアウトファイルを用意して、
[sourcecode language="c#"]
return View("IndexXml", "_LayoutNothing", data);
[/sourcecode]
としたら一応思い通りに動いた。なんか今ひとつ釈然としないけれども、まあいいか。

ちなみにこの辺を調べるためにドキュメントをあさっていたら、System.Mvc以下にJsonResultというAPI向けにJSON出力するためのクラスが用意されていた。これってXML出力用は用意されていないの? XML出力したい場合は通常のシリアライザー使って書けってことなのかな?

Published At2011-11-10 20:39Updated At2011-11-10 20:39