Twitter で酢酸先生が ASP.NET MVC でのビューの使い回しについて悩んでいたので、久しぶりに ASP.NET についてブログを書きます。
しばやんサイトに無限クロールして地獄を味合わせるか…。あ、asp,net mvcで複数のページの任意の場所にカスタムコントロールっていうかdivの塊(ページャーとかパンくずとか)を差し込みたいんだけど、複数のcshtmlにコピペしてるとメンテ不能になりそうなので良い方法ない?
— 酢酸(さくさん) (@ch3cooh) May 1, 2014
こういったケースの場合、ASP.NET MVC では部分ビューや子アクション、そして Razor のセクションを使うと便利に書けます。それぞれ微妙に機能が異なるので分けて説明します。
Partial / RenderPartial
別ファイルとして用意されたビューを指定された位置にレンダリングします。MvcHtmlString を返すか、直接レスポンスのストリームに書きこむかの違いで 2 種類のメソッドが存在しています。
@* MvcHtmlString を返すので @ が必須 *@ @Html.Partial("_Pager", Model.PagingList) @{ // コードブロックでのみ可能 Html.RenderPartial("_Pager", Model.PagingList) }
そして読み込まれるビューは現在のビューと同じディレクトリか、Shared ディレクトリ内に置いておくとそれが利用されます。
<!-- 本当はここで Model の値を元にページャーを組み立てる --> <div> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> </div>
ちなみに優先順位は当然ながら現在と同じディレクトリ > Shared ディレクトリになります。ページングで使う場合には部分ビュー側でモデルとして組み立てるのに必要な情報を、親のビューから渡す方法が普通かなと思います。
Action / RenderAction
別のアクションを実行し、その結果として返される HTML を指定された位置にレンダリングします。これも Partial / RenderPartial と同じように MvcHtmlString を返すか、直接レスポンスのストリームに書きこむかの違いで 2 種類のメソッドが存在しています。
@* 10 件分バナーを取得 *@ @Html.Action("Index", "Banner", new { count = 10 }) @{ // コードブロックでのみ可能 Html.RenderAction("Index", "Banner", new { count = 10 }) }
そして呼び出されるアクションは普通のアクションと何ら変わりませんが、PartialView を返すのと ChildActionOnly 属性を付けておいた方が良いぐらいですね。
public class BannerController : Controller { [ChildActionOnly] public ActionResult Index(int count = 5) { var context = new ApplicationContext(); var model = context.Banners.Take(count).ToList(); return PartialView(model); } }
PartialView を返すとレイアウトを無視してくれるので、部分ビューを作成した場合にはこれを使う用にした方が良いです。あと、ChildActionOnly 属性は名前の通り、子アクションからしか呼び出せないアクションであることを指定します。
何らかの処理が絡む場合には子アクションを使う場合が多い気がします。
@section / RenderSection
これは ASP.NET MVC の機能ではなく Razor 側の機能です。今までは親のビューが別のビューを読み込むといった形でしたが、セクションの場合は親のビューに子側から出力したい情報を制御する形になります。
一度は見たことがあると思うのが JavaScript の読み込みコードです。ページごとに異なる JavaScript を埋め込む必要がある場合には、親であるレイアウトに RenderSection メソッドの呼び出しだけを追加し、内容に関しては子ビューに任せる形になります。
<head> <script src="jquery.min.js"></script> @RenderSection("Scripts", false) </head>
親では RenderSection メソッドを "Scripts" という引数で呼び出しているので、子側でも同じ名前でセクションを定義します。
@section Scripts { <script> $(function() { // 何かする }); </script> }
これで実行時には jQuery の読み込みのすぐ下で script タグが埋め込まれ、ページごとの動作を実現できるようになります。あと、パンくずリストを機械的に出力できない場合には、セクションを使って実装するケースが多かったです。
RenderPage
これも ASP.NET MVC の機能ではなく Razor 側の機能です。機能としては Partial / RenderPartial に近いですが、Shared ディレクトリを認識しない点が大きな違いです。
@RenderPage("~/Views/Home/Common.cshtml")
特徴としては呼び出し時にパラメータを渡せることでしょうか。
@RenderPage("~/Views/Home/Common.cshtml", 1, 2, 3)
このように RenderPage メソッドの 2 つ目以降にパラメータを追加すると、読み込まれるビューの PageData プロパティから値を取得できます。
@* 1 - 2 - 3 と出力される *@ @PageData[0] - @PageData[1] - @PageData[2]
まあ、これは Web Pages を使った開発の時に使う機能だと思ってもらえれば問題ないと思います。
おまけ
昔に近いことをブログに書いてました。もうちょい実際に使う時向けな内容なので一緒にどうぞ。
セクションと子アクションを使って高度なレイアウトを実現する - しばやん雑記
ちなみに子アクションと OutputCache を組み合わせて使う場合には、ASP.NET MVC 5.1 以上で行った方がバグに悩まされずに済みます。