マスター ページと部分を利用して UI を再使用する
提供元: Microsoft
これは、ASP.NET MVC 1 を使用して小規模で完全な Web アプリケーションを構築する方法を説明する無料の "NerdDinner" アプリケーション チュートリアルの手順 7 です。
手順 7 では、ビュー テンプレート内で "DRY 原則" を適用して、部分ビュー テンプレートとマスター ページを使用してコードの重複をなくす方法に注目します。
ASP.NET MVC 3 を使用している場合は、MVC 3 の概要または MVC Music Store に関するチュートリアルに従うことをお勧めします。
NerdDinner 手順 7: 部分ビューとマスター ページ
ASP.NET MVC で採用されている設計哲学の 1 つに、"Do Not Repeat Yourself" 原則 (通称: "DRY") があります。 DRY 設計は、コードとロジックの重複を排除するのに役立ちます。これにより、最終的にアプリケーションのビルドが高速化され、維持が容易になります。
NerdDinner のいくつかのシナリオで、DRY 原則が既に適用されています。 一部の例: 検証ロジックはモデル レイヤー内に実装されているため、コントローラーでの編集と作成の両方のシナリオに適用できます。編集、詳細、および削除アクションの各メソッドで "NotFound" ビュー テンプレートを再利用しています。ビュー テンプレートで規則の名前付けパターンを使用しているため、View() ヘルパー メソッドを呼び出すときに明示的に名前を指定する必要がなくなります。また、Edit アクションと Create アクションの両方のシナリオで DinnerFormViewModel クラスを再利用しています。
次に、ビュー テンプレート内で "DRY 原則" を適用して、コードの重複をなくす方法を見てみましょう。
ビュー テンプレートの編集と作成を再確認する
現在、Dinner フォーム UI を表示するために、"Edit.aspx" と "Create.aspx" の 2 つの異なるビュー テンプレートを使用しています。 両者を視覚的に比較すると、どれだけ似ているかが分かります。 作成フォームの外観を次に示します。
"編集" フォームは次のようになります。
大きな違いはありませんか? タイトルとヘッダー テキスト以外は、フォーム レイアウト コントロールと入力コントロールは同じです。
"Edit.aspx" ビュー テンプレートと "Create.aspx" ビュー テンプレートを開くと、同じフォーム レイアウトと入力コントロール コードが含まれていることがわかります。 この重複は、新しい Dinner プロパティを導入または変更するたびに、2 回変更する必要が生じることを意味します。これはお勧めできません。
部分ビュー テンプレートの使用
ASP.NET MVC では、ページのサブ部分のビュー レンダリング ロジックをカプセル化するために使用できる "部分ビュー" テンプレートを定義する機能がサポートされています。 "部分ビュー" には、ビュー レンダリング ロジックを一度定義し、アプリケーション全体の複数の場所で再利用する便利な方法が用意されています。
Edit.aspx テンプレートと Create.aspx ビュー テンプレートの重複を "DRY-up" するために、両方に共通するフォーム レイアウトと入力要素をカプセル化する "DinnerForm.ascx" という名前の部分ビュー テンプレートを作成できます。 これを行うには、/Views/Dinners ディレクトリを右クリックし、[Add->View] メニュー コマンドを選択します。
これには、[ビューの追加] ダイアログが表示されます。 作成する新しいビューに "DinnerForm" という名前を付け、ダイアログ内の [部分ビューの作成] チェック ボックスをオンにし、DinnerFormViewModel クラスを渡すように指定します。
[追加] ボタンをクリックすると、Visual Studio によって "\Views\Dinners" ディレクトリ内に新しい "DinnerForm.ascx" ビュー テンプレートが作成されます。
次に、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>
<% } %>
その後、Edit ビュー テンプレートと Create ビュー テンプレートを更新して DinnerForm 部分ビュー テンプレートを呼び出し、フォームの重複をなくすことができます。 これを行うには、ビュー テンプレート内で 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 ディクショナリ オブジェクトが部分ビューに渡されます。 または、Html.RenderPartial() のオーバーロードされたバージョンがあり、部分ビューで使用する代替の Model オブジェクトや ViewData ディクショナリを渡すことができます。 これは、完全な Model/ViewModel のサブセットのみを渡す必要があるシナリオに役立ちます。
サイド トピック: <%= %> ではなく <% %> になる理由 |
---|
上記のコードで気づいた些細な点の 1 つは、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/code マークアップを理解しようとして混乱が生じた場合は、その一部を抽出し、適切な名前の部分ビューにリファクタリングした場合に、それが明確にならないかどうかを検討してください。
マスター ページ
ASP.NET MVC では、部分ビューのサポートに加えて、サイトの一般的なレイアウトと最上位の HTML を定義するために使用できる "マスター ページ" テンプレートを作成する機能もサポートされています。 その後、コンテンツ プレースホルダー コントロールをマスター ページに追加して、ビューによって上書きされたり、"塗りつぶし" できる置き換え可能な領域を特定できます。 これにより、アプリケーション全体に共通のレイアウトを適用する非常に効果的な (かつ DRY である) 方法が提供されます。
既定では、新しい ASP.NET MVC プロジェクトにはマスター ページ テンプレートが自動的に追加されます。 このマスター ページは "Site.master" という名前で、\Views\Shared\ フォルダー内にあります。
既定の 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 テンプレートに基づいています。 これは、[ビューの追加] ダイアログを使用してビューを作成した場合に、既定で top <% @ Page %> ディレクティブに追加された "MasterPageFile" 属性を介して示されます。
<%@ Page Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
つまり、Site.master コンテンツを変更し、ビュー テンプレートをレンダリングする場合に、変更を自動的に適用して使用することができます。
アプリケーションのヘッダーが "マイ MVC アプリケーション" ではなく "NerdDinner" になるように、Site.master のヘッダー セクションを更新しましょう。 また、ナビゲーション メニューを更新して、最初のタブが "Find a Dinner" (HomeController の Index() アクション メソッドによって処理される) になるようにし、"Host a 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 ファイルを保存してブラウザーを最新の情報に更新すると、アプリケーション内のすべてのビューにヘッダーの変更が表示されます。 次に例を示します。
また、/Dinners/Edit/[id] URL を使用した場合は、次のようになります。
次の手順
部分ビューとマスター ページには、ビューをクリーンに整理できる非常に柔軟なオプションが用意されています。 ビューのコンテンツやコードの重複を回避し、ビュー テンプレートを読みやすく維持しやすくするのに役立つことがわかります。
ここで、先ほど作成した一覧のシナリオを見直し、スケーラブルなページングのサポートを有効にしましょう。