NerdDinnerチュートリアル

NerdDinnerステップ7:パーシャルとマスター・ページ

Scott Guthrie 著/Chica
2010/02/19

 本記事は、Microsoftの本社副社長であり、ASP.NETやSilverlightなどの開発チームを率いるScott Guthrie氏が公開している「NerdDinner Tutorial」を翻訳したものです。氏の許可を得て転載しています。

[これは無償の"NerdDinner"アプリケーション・チュートリアルのステップ7で、ASP.NET MVCを使用して、小さいながらも完全なWebアプリケーションを構築する手順を紹介しています。]

 ASP.NET MVCが採用するデザイン哲学の1つが、“Do Not Repeat Yourself”原則です(通常“DRY”といわれている)。DRYデザインはコードやロジックの重複を削除する手助けをするので、最終的にはアプリケーションを素早く構築でき、簡単に保守できるようになります。

 すでにNerdDinnerのいくつかのシナリオでDRYの原則が適用されているのを見てきました。次のような例です。検証ロジックがモデル層内に実装されているため、コントローラでEditおよびCreateの両方のケースにわたって適用されます。“NotFound”ビュー・テンプレートをEdit/Details/Deleteアクション・メソッドにまたがって再利用しています。ビュー・テンプレートで規約ベースの名前付けパターンを使用しているため、Viewヘルパー・メソッドを呼び出したときに、名前を明示的に指定する必要がありません。また、EditやCreateアクションの両方のケースでDinnerFormViewModelクラスを再利用しています。

 では、コードの重複を削除するためにも、ビュー・テンプレート内で“DRYの原則”が適用できる方法を見てみましょう。

EditおよびCreateビュー・テンプレートを再訪

 現在、夕食会のフォームUIを表示するために2つの異なるビュー・テンプレート、“Edit.aspx”および“Create.aspx”を使用しています。簡単にそれらの外観を比較して、どれだけ似ているか明らかにしてみましょう。以下はCreateフォームの様子です。


図1

 そして、これが“Edit”フォームの様子です。


図2

 あまり違いはありませんね? タイトルとヘッダの文字以外、そのフォームのレイアウトや入力コントロールは同じです。

 “Edit.aspx”と“Create.aspx”のビュー・テンプレートを開くと、まったく同じフォームのレイアウトと入力コントロールのコードが含まれていることが分かります。この重複は、新しい夕食会のプロパティを導入したり変更したりするときに、常に2度変更しなければならないことを意味しており、それは好ましくありません。

パーシャル・ビュー・テンプレートの使用

 ASP.NET MVCは“パーシャル・ビュー”テンプレートを定義する機能をサポートしており、ページの一部分に対して、ビューの描画ロジックをカプセル化するために使用できます。“パーシャル”はビューの描画ロジックを一度定義すれば、アプリケーションにわたって複数の場所で、それを再利用できるという便利な方法を提供します。

 Edit.aspxおよびCreate.aspxビュー・テンプレートの重複を“DRY化”させるのに、“DinnerForm.ascx”という名前のパーシャル・ビュー・テンプレートを作成して、両方に共通のフォーム・レイアウトや入力要素をカプセル化します。これを行うには、/Views/Dinnersディレクトリ上で右クリックして、“[Add]−[View]”メニュー・コマンドを選択します。


図3

 これにより“Add View”ダイアログを表示します。ダイアログ内で、その新しいビューを“DinnerForm”という名前にして、“Create a pertial view(パーシャル・ビューを新規作成)”チェックボックスを選択し、DinnerFormViewModelクラスをそれに引き渡すことを示します(図4)。


図4

 “Add”ボタンをクリックすると、Visual Studioは新しい“DinnerForm.ascx”ビュー・テンプレートを“\Views\Dinners”ディレクトリ内に作成します。

 そして、Edit.aspx/Create.aspxビュー・テンプレートから、新しい“DinnerForm.ascx”パーシャル・ビュー・テンプレートに、重複するフォーム・レイアウト/入力コントロールのコードをコピー/ペーストします。

<%= Html.ValidationSummary("Please correct the errors and try again.") %>

<% using (Html.BeginForm()) { %>

  <fieldset>
    <p>
      <label for="Title">Dinner Title:</label>
      <%= Html.TextBox("Title", Model.Dinner.Title) %>
      <%=Html.ValidationMessage("Title", "*") %>
    </p>
    <p>
      <label for="EventDate">Event Date:</label>
      <%= Html.TextBox("EventDate", Model.Dinner.EventDate) %>
      <%= Html.ValidationMessage("EventDate", "*") %>
    </p>
    <p>
      <label for="Description">Description:</label>
      <%= Html.TextArea("Description", Model.Dinner.Description) %>
      <%= Html.ValidationMessage("Description", "*") %>
    </p>
    <p>
      <label for="Address">Address:</label>
      <%= Html.TextBox("Address", Model.Dinner.Address) %>
      <%= Html.ValidationMessage("Address", "*") %>
    </p>
    <p>
      <label for="Country">Country:</label>
      <%= Html.DropDownList("Country", Model.Countries) %>
      <%= Html.ValidationMessage("Country", "*") %>
    </p>
    <p>
      <label for="ContactPhone">Contact Phone #:</label>
      <%= Html.TextBox("ContactPhone", Model.Dinner.ContactPhone) %>
      <%= Html.ValidationMessage("ContactPhone", "*") %>
    </p>

    <p>
      <input type="submit" value="Save"/>
    </p>
  </fieldset>

<% } %>

 その後、DinnerFormパーシャル・テンプレートを呼び出し、そのフォームの重複部分を削除するように、EditおよびCreateのビュー・テンプレートを修正します。これは、ビュー・テンプレート内で、Html.RenderPartial("DinnerForm")を呼び出せば行えます。

■Create.aspx


<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
  Host a Dinner
</asp:Content>

<asp:Content ID="Create" ContentPlaceHolderID="MainContent" runat="server">

  <h2>Host a Dinner</h2>

  <% Html.RenderPartial("DinnerForm"); %>

</asp:Content>

■Edit.aspx



<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
  Edit: <%=Html.Encode(Model.Dinner.Title) %>
</asp:Content>

<asp:Content ID="Edit" ContentPlaceHolderID="MainContent" runat="server">

  <h2>Edit Dinner</h2>

  <% Html.RenderPartial("DinnerForm"); %>

</asp:Content>

 Html.RenderPartialメソッドを呼び出すときには、必要なパーシャル・テンプレートのパス(例えば、“~Views/Dinners/DinnerForm.ascx”)を明示的に指定できます。ところで上記のコードでは、ASP.NET MVCのコンベンション・ベースの名前付けパターンを利用しているため、描画するそのパーシャルの名前として”DinnerForm“を指定しているだけです。このようにした場合、ASP.NET MVCは最初にコンベンション・ベースのビューのディレクトリを見ます(DinnersControllerの場合、/Views/Dinnersになります)。もしそこにパーシャル・テンプレートが見つからない場合は、/Views/Sharedディレクトリを探します。

 パーシャル・ビューの名前だけでHtml.RenderPartialメソッドが呼び出されたときには、ASP.NET MVCは、ビュー・テンプレートの呼び出しで使用されているのと同じModelとViewDataディクショナリ・オブジェクトをパーシャル・ビューへ引き渡します。それとは別に、別のModelオブジェクトやViewDataディクショナリをパーシャル・ビューに引き渡して利用できるようにするためのオーバーロードされたバージョンのHtml.RenderPartialメソッドもあります。これは、完全なModel/ViewModelのサブセットを引き渡したいだけの場合に便利です。

サイド・トピック:なぜ<%= %>の代わりに<% %>なのか?

 上記コードで気付かれたかもしれない、ささいな点ですが、Html.RenderPartialメソッドを呼び出すときに、<%= %>ブロックの代わりに<% %>ブロックを使用しています。

 ASP.NETの<%= %>ブロックは、開発者が特定の値を描画することを示しています(例えば、<%= "Hello" %>は“Hello”と描画されます)。代わって、<% %>ブロックは開発者がコードを実行することを示しており、それらの中で描画される出力は、すべて明示的に行わなければなりません(例えば、<% Response.Write("Hello") %>)。

 上記のHtml.RenderPartialコードと<% %>ブロックを使用している理由は、Html.RenderPartialメソッドは文字列を返さず、代わりに、呼び出しているビュー・テンプレートの出力ストリームに、直接そのコンテンツを出力するためです。これはパフォーマンスの効率性の理由から行っており、そうすることで、(潜在的に非常に大きい)一時的な文字列オブジェクトを作る必要がなくなります。これはメモリの使用を減少させ、アプリケーション全体の処理能力を改善します。

 Html.RenderPartialメソッドを使用する場合の、よくある間違いの1つが、<% %>ブロックの中にあるために、その呼び出しの最後にセミコロンを付け忘れることです。例えば、このコードはコンパイル・エラーになります。

<% Html.RenderPartial("DinnerForm") %>

 次のように書く必要があります。

<% Html.RenderPartial("DinnerForm"); %>

 これは、<% %>ブロックが内蔵型のコード文だからです。そして、C#コードを使用するときには、ステートメントはセミコロンで終了させる必要があります。

コードの明確化にパーシャル・ビュー・テンプレートを使用

 “DinnerForm”パーシャル・ビュー・テンプレートを作成して、複数の場所でビューの描画ロジックが重複しないようにしました。これがパーシャル・ビュー・テンプレートを作成する最も多い理由です。

 1つの場所でしか呼ばれない場合でも、パーシャル・ビューの作成が妥当である場合も、たまにあります。非常に複雑なビュー・テンプレートでは、それらのビューの描画ロジックが抽出され、1つ以上のうまく名前付けされたパーシャル・テンプレートに分割されていると、より読みやすくなる場合がよくあります。

 例えば、プロジェクトにあるSite.masterファイルからの以下のコード・スニペット(まもなく取り上げます)を考えてみてください。コードは比較的簡単に読めます。これは、画面の右上にあるログイン/ログアウトのリンクを表示するロジックが“LogOnUserControl”パーシャル内でカプセル化されているというのが理由の一部です。

<div id="header">
  <div id="title">
    <h1>My MVC Application</h1>
  </div>

  <div id="logindisplay">
    <% Html.RenderPartial("LogOnUserControl"); %>
  </div>

  <div id="menucontainer">

    <ul id="menu">
      <li><%=Html.ActionLink("Home", "Index", "Home")%></li>
      <li><%=Html.ActionLink("About", "About", "Home")%></li>
    </ul>
  </div>
</div>

 ビュー・テンプレート内のHTML/コードのタグを理解しようとして混乱したときはいつでも、その中のいくつかを抽出し、うまく名前付けしたパーシャル・ビューにリファクタリングすれば、クリアにできないかどうか検討してみてください。

マスター・ページ

 パーシャル・ビューのサポートだけでなく、ASP.NET MVCは“マスター・ページ”テンプレートの作成機能もサポートしており、サイトで共有のレイアウトやトップレベルのHTMLを定義するために使用できます。そのため、コンテンツのプレースホルダ・コントロールをマスター・ページに追加でき、ビューによってオーバーライドされる、あるいは“埋めつく”される置換可能なエリアとして認識されます。これはアプリケーションにわたって共有のレイアウトを適用する、非常に効果的な(そしてDRYな)方法を提供します。

 デフォルトでは、新しいASP.NET MVCプロジェクトに、自動的にマスター・ページのテンプレートが追加されます。このマスター・ページは“Site.master”という名前で、\Views\Shared\フォルダにあります。


図5

 デフォルトのSite.masterファイルは以下のようになっています。先頭では、ナビゲーション用のメニューを持った、サイトの外側のHTMLを定義しています。それには2つの置換可能なコンテンツのプレースホルダ・コントロールがあり、1つがタイトル用で、もう1つはページの主コンテンツと置き換えられます。


<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">
  <title>
     <asp:ContentPlaceHolder ID="TitleContent" runat="server" />
  </title>
   <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

<body>
  <div class="page">

    <div id="header">
      <div id="title">
        <h1>My MVC Application</h1>
      </div>

      <div id="logindisplay">
        <% Html.RenderPartial("LogOnUserControl"); %>
      </div>

      <div id="menucontainer">

        <ul id="menu">
          <li><%=Html.ActionLink("Home", "Index", "Home")%></li>
          <li><%=Html.ActionLink("About", "About", "Home")%></li>
        </ul>

      </div>
    </div>

    <div id="main">
      <asp:ContentPlaceHolder ID="MainContent" runat="server" />
    </div>
  </div>
</body>
</html>

 NerdDinnerアプリケーションで作成したビュー・テンプレートのすべて(“List”、“Details”、“Edit”、“Create”、“NotFound”など)が、このSite.masterテンプレートに基づいています。これは、“Add View”ダイアログを使用してビューを作成したときに、先頭にある<% @ Page %>ディレクティブへデフォルトで追加されている“MasterPageFile”属性により示されています。

<%@ Page Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>

 つまりSite.masterのコンテンツを変更すれば、どのビュー・テンプレートを描画するときにも、自動的にその変更が適用されて使用されます。

 Site.masterのヘッダ部分を更新して、アプリケーションのヘッダを“My MVC Application”から“NerdDinner”にしてみましょう。また、ナビゲーション・メニューも修正して、最初のタブを“Find Dinner”(HomeControllerのIndexアクション・メソッドにより処理されます)にしましょう。そして、新しい“Host Dinner”というタブを追加してみましょう(DinnersControllerのCreateアクション・メソッドにより処理されます)。

<div id="header">

  <div id="title">
    <h1>NerdDinner</h1>
  </div>

  <div id="logindisplay">
    <% Html.RenderPartial("LoginStatus"); %>
  </div>

  <div id="menucontainer">
    <ul id="menu">
       <li><%=Html.ActionLink("Find Dinner", "Index", "Home")%></li>
       <li><%=Html.ActionLink("Host Dinner", "Create", "Dinners")%></li>
       <li><%=Html.ActionLink("About", "About", "Home")%></li>
    </ul>
  </div>
</div>

 Site.masterファイルを保存してブラウザを更新すると、ヘッダの変更がアプリケーション内のすべてのビューにわたって表示されます。以下が例です。


図6

/Dinners/Edit/[id] URLの場合:


図7

次のステップ

 パーシャルとマスター・ページは非常に柔軟性のあるオプションを提供するので、ビューをクリーンにまとめることができます。ビューのコンテンツ/コードの重複を避けることができ、ビュー・テンプレートがより読みやすく保守しやすくなるのが分かるはずです。

 では、以前に構築した一覧のシナリオに戻って、スケーラブルなページングのサポートを可能にしましょう。

[注:NerdDinnerアプリケーションの完成版はhttp://nerddinner.codeplex.com/からダウンロードできます。] End of Article

 
インデックス・ページヘ  「NerdDinnerチュートリアル」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間