入れ子にされたマスター ページ (C#)

作成者: Scott Mitchell

1 つのマスター ページを別のマスター ページに入れ子にする方法について説明します。

はじめに

これまでの 9 つのチュートリアルでは、マスター ページを使用してサイト全体のレイアウトを実装する方法を確認しました。 簡単に言えば、マスター ページを使用すると、ページ開発者は、マスター ページで共通のマークアップと、コンテンツ ページごとにカスタマイズできる特定の領域を定義できます。 マスター ページの ContentPlaceHolder コントロールはカスタマイズ可能な領域を示します。ContentPlaceHolder コントロールのカスタマイズされたマークアップは、コンテンツ コントロールを介してコンテンツ ページで定義されます。

これまでに調べてきたマスター ページの手法は、サイト全体で 1 つのレイアウトだけを使用する場合に最適な手法です。 しかし、多くの Web サイトでは、サイト レイアウトがさまざまなセクションでカスタマイズされています。 たとえば、病院のスタッフが患者情報、アクティビティ、請求を管理するために使用する医療アプリケーションについて考えてみましょう。 このアプリケーションには次の 3 種類の Web ページがあるとします。

  • スタッフ メンバー固有のページ。ここでは、スタッフ メンバーが、空き時間の更新、スケジュールの表示、または休暇の申請を行うことができます。
  • 患者固有のページ。ここでは、スタッフ メンバーが、特定の患者に関する情報を表示または編集できます。
  • 請求固有のページ。ここでは、会計士が、現在の請求の状態と財務レポートを確認します。

各ページでは、上部のメニューや下部にあるよく使用される一連のリンクなどに共通のレイアウトが使用されます。 ただし、スタッフ、患者、請求固有の各ページでは、この汎用レイアウトをカスタマイズする必要があります。 たとえば、すべてのスタッフ固有のページには、カレンダーと、現在ログオンしているユーザーの空き時間と毎日のスケジュールを示すタスク リストを含める必要があるでしょう。 また、患者固有のページには、情報を編集している患者の名前、住所、保険情報を表示する必要があるでしょう。

"入れ子になったマスター ページ" を使用すると、このようなカスタマイズされたレイアウトを作成できます。 上記のシナリオを実装するには、まず、サイト全体のレイアウト、メニューとフッターのコンテンツを定義するマスター ページを作成し、ContentPlaceHolder を使用してカスタマイズ可能な領域を定義します。 次に、Web ページの種類ごとに 1 つずつ、合計 3 つの入れ子になったマスター ページを作成します。 入れ子になった各マスター ページでは、マスター ページを使用するコンテンツ ページの種類の間でコンテンツを定義します。 つまり、患者固有のコンテンツ ページの入れ子になったマスター ページには、編集中の患者に関する情報をっ表示するためのマークアップとプログラム ロジックが含まれます。 新しい患者固有のページを作成する場合、それをこの入れ子になったマスター ページにバインドします。

このチュートリアルでは、まず、入れ子になったマスター ページの利点に焦点を当てます。 その後、入れ子になったマスター ページを作成して使用する方法を示します。

Note

マスター ページは、.NET Framework のバージョン 2.0 から入れ子にすることができるようになりました。 ただし、Visual Studio 2005 には、入れ子になったマスター ページのデザイン時サポートは含まれていませんでした。 幸いなことに、Visual Studio 2008 では、入れ子になったマスター ページのデザイン時エクスペリエンスが提供されています。 入れ子になったマスター ページの使用に関心はあるが、まだ Visual Studio 2005 を使用している場合は、Scott Guthrie のブログ記事「VS 2005 でのデザイン時の入れ子になったマスター ページに関するヒント」を参照してください。

入れ子になったマスター ページの利点

多くの Web サイトには、サイト全体にわたるデザインと、特定の種類のページに固有のよりカスタマイズされたデザインがあります。 たとえば、デモ Web アプリケーションでは、基本的な管理セクション (~/Admin フォルダー内のページ) を作成しました。 現在、~/Admin フォルダー内の Web ページでは同じマスター ページ (つまり、ユーザーの選択に応じて Site.master または Alternate.master) が使用されていますが、このマスター ページは管理セクションに含まれていません。

Note

今のところは、サイトに 1 つのマスター ページ Site.master しかないことにします。 2 つ (以上) のマスター ページでの入れ子になったマスター ページの使用については、このチュートリアルで後ほど説明する「入れ子になったマスター ページを管理セクションに使用する」以降で取り上げます。

サイト内の他のページに存在しない追加情報やリンクを含むように管理ページのレイアウトをカスタマイズするよう求められたとします。 この要件を実装するには、次の 4 つの手法があります。

  1. ~/Admin フォルダー内のすべてのコンテンツ ページに、管理固有の情報とリンクを手動で追加します。
  2. 管理セクション固有の情報とリンクを含むように Site.master マスター ページを更新し、いずれかの管理ページにアクセスしているかどうかに基づいて、これらのセクションを表示するか、非表示にするコードをマスター ページに追加します。
  3. 管理セクション専用の新しいマスター ページを作成し、Site.master からマークアップをコピーし、管理セクション固有の情報とリンクを追加した後、この新しいマスター ページを使用するように、~/Admin フォルダー内のコンテンツ ページを更新します。
  4. 入れ子になったマスター ページを作成して Site.master にバインドし、この新しい入れ子になったマスター ページを ~/Admin フォルダー内のコンテンツ ページで使用するようにします。 この入れ子になったマスター ページには、管理ページ固有の追加情報とリンクだけが含まれるため、Site.master で既に定義されているマークアップを繰り返す必要はありません。

最初のオプションは、最も受け入れにくい方法です。 マスター ページを使用する最も重要な利点は、共通のマークアップを手動でコピーして新しい ASP.NET ページに貼り付ける必要がなくなることです。 2 番目のオプションは受け入れられますが、たまにしか表示されないマークアップでマスター ページが増大し、マスター ページを編集する開発者がこのマークアップを回避し、特定のマークアップがいつ表示され、いつ非表示になるかを正確に覚えておく必要があるため、アプリケーションの保守性が低下します。 このアプローチは、この単一のマスター ページで対応する必要がある Web ページの種類が増えるにつれて、Web ページのカスタマイズに対応できなくなります。

3 番目のオプションは、2 番目のオプションで表面化した煩雑さと複雑さの問題を排除します。 ただし、オプション 3 の欠点は、共通のレイアウトを Site.master からコピーして新しい管理セクション固有のマスター ページに貼り付ける必要があるということです。 後でサイト全体のレイアウトの変更を決定した場合、忘れずに 2 つの場所で変更する必要があります。

4 番目のオプションである入れ子になったマスター ページは、2 番目と 3 番目のオプションの長所を兼ね備えています。 サイト全体のレイアウト情報は 1 つのファイル (最上位のマスター ページ) に保持され、特定の領域に固有のコンテンツは、別々のファイルに分離されます。

このチュートリアルでは、まず、単純な入れ子になったマスター ページの作成と使用について説明します。 まったく新しい最上位マスター ページ、2 つの入れ子になったマスター ページ、2 つのコンテンツ ページを作成します。 「入れ子になったマスター ページを管理セクションに使用する」以降では、入れ子になったマスター ページの使用を含むように既存のマスター ページ アーキテクチャを更新する方法について説明します。 具体的には、入れ子になったマスター ページを作成し、それを使用して、~/Admin フォルダー内のコンテンツ ページ用の追加のカスタム コンテンツを含めます。

手順 1: 単純な最上位マスター ページを作成する

既存のマスター ページのいずれかに基づいて入れ子になったマスターを作成し、最上位マスター ページの代わりにこの新しい入れ子になったマスター ページを使用するように既存のコンテンツ ページを更新すると、既存のコンテンツ ページでは最上位マスター ページで定義されている特定の ContentPlaceHolder コントロールが既に想定されているため、多少の複雑さが伴います。 このため、入れ子になったマスター ページには、同じ名前を持つ同じ ContentPlaceHolder コントロールも含まれている必要があります。 さらに、ここで使用しているデモ アプリケーションには 2 つのマスター ページ (Site.masterAlternate.master) があり、これらは、ユーザーの設定に基づいてコンテンツ ページに動的に割り当てられるため、この複雑さがさらに増します。 このチュートリアルでは後ほど、入れ子になったマスター ページを使用するように既存のアプリケーションを更新する方法について説明しますが、まず、単純な入れ子になったマスター ページの例に焦点を当てることにしましょう。

NestedMasterPages という名前の新しいフォルダーを作成し、そのフォルダーに Simple.master という名前の新しいマスター ページ ファイルを追加します (このフォルダーとファイルを追加した後のソリューション エクスプローラーのスクリーンショットについては、図 1 を参照してください)。AlternateStyles.css スタイル シート ファイルをソリューション エクスプローラーからデザイナーにドラッグします。 これにより、<head> 要素のスタイル シート ファイルに <link> 要素が追加されます。追加後のマスター ページの <head> 要素のマークアップは、次のようになります。

<head runat="server">
 <title>Untitled Page</title> 
 <asp:ContentPlaceHolder id="head" runat="server"> 
 </asp:ContentPlaceHolder>
 <link href="../AlternateStyles.css" rel="stylesheet" type="text/css" /> 
</head>

次に、Simple.master の Web フォーム内に次のマークアップを追加します。

<div id="topContent"> 
 <asp:HyperLink ID="lnkHome" runat="server" 
 NavigateUrl="~/NestedMasterPages/Default.aspx"
 Text="Nested Master Pages Tutorial (Simple)" /> 
</div> 
<div id="mainContent"> 
 <asp:ContentPlaceHolder id="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
</div>

このマークアップにより、ページの上部に "入れ子になったマスター ページ (単純)" というタイトルのリンクが、濃紺の背景に白色のフォントで表示されます。 その下には、MainContent ContentPlaceHolder があります。 図 1 は、Visual Studio デザイナーに読み込まれた Simple.master マスター ページを示しています。

Visual Studio デザイナーに読み込まれた場合の単純なドット マスター マスター ページ。

図 01: 管理セクションのページに固有のコンテンツを定義する入れ子になったマスター ページ (クリックするとフルサイズの画像が表示されます)

手順 2: 単純な入れ子になったマスター ページを作成する

Simple.master には、2 つの ContentPlaceHolder コントロールが含まれています。Web フォーム内に追加した MainContent ContentPlaceHolder と <head> 要素内の head ContentPlaceHolder です。 コンテンツ ページを作成し、それを Simple.master にバインドすると、そのコンテンツ ページには、この 2 つの ContentPlaceHolder を参照する 2 つのコンテンツ コントロールが含まれます。 同様に、入れ子になったマスター ページを作成し、Simple.master にバインドすると、その入れ子になったマスター ページには、2 つのコンテンツ コントロールが含まれます。

では、SimpleNested.master という名前の新しい入れ子になったマスター ページを NestedMasterPages フォルダーに追加してみましょう。 NestedMasterPages フォルダーを右クリックし、[新しい項目の追加] を選択します。 これにより、図 2 に示す [新しい項目の追加] ダイアログ ボックスが表示されます。 テンプレートの種類として [マスター ページ] を選択し、新しいマスター ページの名前を入力します。 新しいマスター ページが入れ子になったマスター ページである必要があることを示すには、[マスター ページを選択する] チェックボックスをオンにします。

次に、[追加] ボタンをクリックします。 これにより、コンテンツ ページをマスター ページにバインドするときに表示されるものと同じ [マスター ページの選択] ダイアログ ボックスが表示されます (図 3 を参照)。 NestedMasterPages フォルダー内の Simple.master マスター ページを選択し、[OK] をクリックします。

Note

Web サイト プロジェクト モデルではなく Web アプリケーション プロジェクト モデルを使用して ASP.NET Web サイトを作成すると、図 2 に示す [新しい項目の追加] ダイアログ ボックスに [マスター ページを選択する] チェックボックスは表示されません。 Web アプリケーション プロジェクト モデルを使用する場合に入れ子になったマスター ページを作成するには、([マスター ページ] テンプレートではなく) [入れ子になったマスター ページ] テンプレートを選択する必要があります。 [入れ子になったマスター ページ] テンプレートを選択し、[追加] をクリックすると、図 3 に示すものと同じ [マスター ページの選択] ダイアログ ボックスが表示されます。

[マスター ページの選択] チェック ボックスをオンにして、入れ子になったマスター ページを追加します

図 02: [マスター ページを選択する] チェックボックスをオンにして、入れ子になったマスター ページを追加する (クリックするとフルサイズの画像が表示されます)

入れ子になったマスター ページを Simple.master マスター ページにバインドする

図 03: 入れ子になったマスター ページを Simple.master マスター ページにバインドする (クリックするとフルサイズの画像が表示されます)

次に示す入れ子になったマスター ページの宣言型マークアップには、最上位マスター ページの 2 つの ContentPlaceHolder コントロールを参照する 2 つのコンテンツ コントロールが含まれています。

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNested.master.cs" Inherits="NestedMasterPages_SimpleNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 </asp:Content>

入れ子になったマスター ページの最初の宣言型マークアップは、<%@ Master %> ディレクティブを除き、コンテンツ ページを同じ最上位マスター ページにバインドするときに最初に生成されるマークアップと同じです。 コンテンツ ページの <%@ Page %> ディレクティブと同様、この <%@ Master %> ディレクティブには、入れ子になったマスター ページの親マスター ページを指定する MasterPageFile 属性が含まれています。 同じ最上位マスター ページにバインドされる入れ子になったマスター ページとコンテンツ ページの主な違いは、入れ子になったマスター ページには、ContentPlaceHolder コントロールを含めることができる点です。 入れ子になったマスター ページの ContentPlaceHolder コントロールは、コンテンツ ページでマークアップをカスタマイズできる領域を定義します。

この入れ子になったマスター ページを更新して、MainContent ContentPlaceHolder コントロールに対応するコンテンツ コントロールにテキスト "Hello, from SimpleNested!" が表示されるようにします。

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <p>Hello, from SimpleNested!</p>
</asp:Content>

この追加を行った後、入れ子になったマスター ページを保存し、Default.aspx という名前の新しいコンテンツ ページを NestedMasterPages フォルダーに追加し、SimpleNested.master マスター ページにバインドします。 このページを追加すると、コンテンツ コントロールが含まれていないことに驚くかもしれません (図 4 を参照)。 コンテンツ ページは、"親" マスター ページの ContentPlaceHolder にのみアクセスできます。 SimpleNested.master には、ContentPlaceHolder コントロールが含まれていません。そのため、このマスター ページにバインドされたコンテンツ ページにはコンテンツ コントロールを含めることはできません。

[新しいコンテンツ] ページにコンテンツ コントロールがありません

図 04: コンテンツ コントロールが含まれていない新しいコンテンツ ページ (クリックするとフルサイズの画像が表示されます)

必要なのは、ContentPlaceHolder コントロールを含むように入れ子になったマスター ページ (SimpleNested.master) を更新することです。 通常、入れ子になったマスター ページに、親マスター ページによって定義された各 ContentPlaceHolder の ContentPlaceHolder を含めて、子マスター ページまたはコンテンツ ページで、最上位マスター ページの ContentPlaceHolder コントロールを操作できるようにします。

SimpleNested.master マスター ページを更新して、2 つのコンテンツ コントロールに ContentPlaceHolder を含めます。 ContentPlaceHolder コントロールに、そのコンテンツ コントロールが参照する ContentPlaceHolder コントロールと同じ名前を付けます。 つまり、MainContent という名前の ContentPlaceHolder コントロールを、Simple.master 内の MainContent ContentPlaceHolder を参照する SimpleNested.master 内のコンテンツ コントロールに追加します。 head ContentPlaceHolder を参照するコンテンツ コントロールでも同じ操作を行います。

Note

入れ子になったマスター ページの ContentPlaceHolder コントロールに、最上位マスター ページの ContentPlaceHolder と同じ名前を付けることをお勧めしますが、この命名の対称性は必要ありません。 入れ子になったマスター ページの ContentPlaceHolder コントロールには、任意の名前を付けることができます。 ただし、最上位マスター ページと入れ子になったマスター ページで同じ名前を使用すると、どの ContentPlaceHolder がページのどの領域に対応しているかを覚えやすくなります。

これらの追加を行った後、SimpleNested.master マスター ページの宣言型マークアップは次のようになります。

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master"AutoEventWireup="false" CodeFile="SimpleNested.master.cs" Inherits="NestedMasterPages_SimpleNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 <asp:ContentPlaceHolder ID="head" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <p>Hello, from SimpleNested!</p>
 <asp:ContentPlaceHolder ID="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content>

先ほど作成した Default.aspx コンテンツ ページを削除し、もう一度追加して、SimpleNested.master マスター ページにバインドします。 今度は、Visual Studio で、2 つのコンテンツ コントロールが Default.aspx に追加され、SimpleNested.master で定義されている ContentPlaceHolder が参照されるようになりました (図 6 を参照)。 MainContent を参照するコンテンツ コントロールにテキスト "Hello, from Default.aspx!" を追加します。

図 5 は、ここに関係する 3 つのエンティティ (Simple.masterSimpleNested.masterDefault.aspx) と、それらの相互関係を示しています。 図に示すように、入れ子になったマスター ページは、親の ContentPlaceHolder のコンテンツ コントロールを実装します。 これらの領域にコンテンツ ページからアクセスする必要がある場合、入れ子になったマスター ページで、独自の ContentPlaceHolder をコンテンツ コントロールに追加する必要があります。

最上位および入れ子になったマスター ページによって、コンテンツ ページのレイアウトが決まります

図 05: 最上位マスター ページと入れ子になったマスター ページによってコンテンツ ページのレイアウトが決定される (クリックするとフルサイズの画像が表示されます)

この動作は、コンテンツ ページまたはマスター ページがその親マスター ページのみを認識する方法を示しています。 この動作は、Visual Studio デザイナーでも示されます。 図 6 は、Default.aspx のデザイナーを示しています。 デザイナーには、コンテンツ ページから編集可能な領域と編集できない部分は明確に示されますが、入れ子になったマスター ページから編集できない領域と最上位マスター ページの領域は明確に区別されません。

コンテンツ ページに、入れ子になったマスター ページの ContentPlaceHolders のコンテンツ コントロールが含まれるようになりました

図 06: 入れ子になったマスター ページの ContentPlaceHolders が含まれるようになったコンテンツ ページ (クリックするとフルサイズの画像が表示されます)

手順 3: 2 つ目の単純な入れ子になったマスター ページを追加する

入れ子になったマスター ページの利点は、複数の入れ子になったマスター ページが存在する場合により明確になります。 この利点を示すために、NestedMasterPages フォルダーに別の入れ子になったマスター ページを作成します。この新しいマスター ページに SimpleNestedAlternate.master という名前を付け、Simple.master マスター ページにバインドします。 手順 2 で行ったように、入れ子になったマスター ページの 2 つのコンテンツ コントロールに ContentPlaceHolder コントロールを追加します。 また、最上位マスター ページの MainContent ContentPlaceHolder に対応するコンテンツ コントロールにテキスト "Hello, from SimpleNestedAlternate!" を追加します。 これらの変更を行うと、新しい入れ子になったマスター ページの宣言型マークアップは次のようになります。

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNestedAlternate.master.cs" Inherits="NestedMasterPages_SimpleNestedAlternate" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
 <asp:ContentPlaceHolder ID="head" runat="server">
 </asp:ContentPlaceHolder> 
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
 <p>Hello, from SimpleNestedAlternate!</p> 
 <asp:ContentPlaceHolder ID="MainContent" runat="server">
 </asp:ContentPlaceHolder> 
 </asp:Content>

NestedMasterPages フォルダーに Alternate.aspx という名前のコンテンツ ページを作成し、入れ子になったマスター ページ SimpleNestedAlternate.master にバインドします。 MainContent に対応するコンテンツ コントロールにテキスト "Hello, from Alternate!" を追加します。 図 7 は、Visual Studio デザイナーに表示された場合の Alternate.aspx を示しています。

Alternate.aspxは SimpleNestedAlternate.master マスター ページにバインドされています

図 07: Alternate.aspxSimpleNestedAlternate.master マスター ページにバインドする (クリックするとフルサイズの画像が表示されます)

図 7 のデザイナーと図 6 のデザイナーを比較してください。 どちらのコンテンツ ページも、最上位マスター ページ (Simple.master) で定義されている同じレイアウト (つまり、"入れ子になったマスター ページのチュートリアル (シンプル)" というタイトル) を共有します。 ただし、どちらも親マスター ページで個別のコンテンツが定義されています。つまり、図 6 は "Hello, from SimpleNested!" というテキストで、図 7 は"Hello, from SimpleNestedAlternate!" というテキストです。 確かに、この場合はささいな違いですが、この例を拡張して、より意味のある違いを含めることもできます。 たとえば、SimpleNested.master ページには、そのコンテンツ ページに固有のオプションを含むメニューが含まれ、SimpleNestedAlternate.master には、それにバインドされているコンテンツ ページに関連する情報が含まれる場合があります。

ここで、サイト全体のレイアウトを変更する必要があるとしましょう。 たとえば、共通のリンクの一覧をすべてのコンテンツ ページに追加するとします。 これを実現するには、最上位マスター ページ Simple.master を更新します。 行った変更はすべて、その入れ子になったマスター ページと、広い意味では、それらのコンテンツ ページに即座に反映されます。

包括的なサイト レイアウトを簡単に変更できるようにするには、 Simple.master マスター ページを開き、 topContent 要素と mainContent <div> 要素の間に次のマークアップを追加します。

<div id="navContent"> 
 <asp:HyperLink ID="lnkDefault" runat="server" 
 NavigateUrl="~/NestedMasterPages/Default.aspx" 
 Text="Nested Master Page Example 1" /> 
 | 
 <asp:HyperLink ID="lnkAlternate" runat="server" 
 NavigateUrl="~/NestedMasterPages/Alternate.aspx" 
 Text="Nested Master Page Example 2" /> 
</div>

これにより、Simple.masterSimpleNested.master、または SimpleNestedAlternate.master にバインドされるすべてのページの上部に 2 つのリンクが追加されます。これらの変更は入れ子になったすべてのマスター ページとそのコンテンツ ページに即座に適用されます。 図 8 は、ブラウザーに表示された場合の Alternate.aspx を示しています。 ページの上部にリンクが追加されていることに注意してください (図 7 と比較した場合)。

最上位マスター ページに変更されると、入れ子になったマスター ページとそのコンテンツ ページにすぐに反映されます

図 08: 最上位マスター ページの変更は、入れ子になったマスター ページとそのコンテンツ ページに即座に反映される (クリックするとフルサイズの画像が表示されます)

入れ子になったマスター ページを管理セクションに使用する

ここまで、入れ子になったマスター ページの利点について説明し、入れ子になったマスター ページを ASP.NET アプリケーションで作成して使用する方法を確認しました。 ただし、手順 1、2、3 の例は、新しい最上位マスター ページ、新しい入れ子になったマスター ページ、新しいコンテンツ ページの作成に関するものでした。 既存の最上位マスター ページとコンテンツ ページを含む Web サイトに、新しい入れ子になったマスター ページを追加する場合はどうでしょうか?

入れ子になったマスター ページを既存の Web サイトに統合し、それを既存のコンテンツ ページに関連付けるには、最初から始めるよりも多少の手間が必要です。 手順 4、5、6、および 7 では、管理者向けの説明が含まれており、~/Admin フォルダー内の ASP.NET ページによって使用される AdminNested.master という名前の新しい入れ子になったマスター ページを含むようにデモ アプリケーションを拡張しながら、これらの課題を探求します。

入れ子になったマスター ページをデモ アプリケーションに統合する場合、次の問題が発生します。

  • ~/Admin フォルダー内の既存のコンテンツ ページには、マスター ページによる特定の想定が含まれています。 まず、特定の ContentPlaceHolder コントロールが存在していることが想定されています。 さらに、~/Admin/AddProduct.aspx および ~/Admin/Products.aspx ページは、マスター ページの RefreshRecentProductsGrid パブリック メソッドを呼び出すか、その GridMessageText プロパティを設定するか、または PricesDoubled イベントのイベント ハンドラーがあります。 このため、入れ子になったマスター ページは、同じ ContentPlaceHolders とパブリック メンバーを提供する必要があります。
  • 前のチュートリアルでは、セッション変数に基づいて Page オブジェクトの MasterPageFile プロパティを動的に設定するように BasePage クラスを拡張しました。 入れ子になったマスター ページを使用する場合、動的なマスター ページをサポートするにはどうすればよいでしょうか?

これら 2 つの課題は、入れ子になったマスター ページを構築し、それを既存のコンテンツ ページから使用するときに表面化します。 これらの問題が発生したら、調査して克服します。

手順 4: 入れ子になったマスター ページを作成する

最初の作業は、管理セクションのページで使用される入れ子になったマスター ページの作成です。 手順 2 で説明したように、新しい入れ子になったマスター ページを追加する場合、入れ子になったマスター ページの親マスター ページを指定する必要があります。 しかし、最上位マスター ページには、Site.masterAlternate.master の 2 つがあります。 前のチュートリアルで Alternate.master を作成したときのことを思い出してください。このとき、実行時に Page オブジェクトの MasterPageFile プロパティを、MyMasterPage セッション変数の値に応じて Site.master または Alternate.master のいずれかに設定するコードを BasePage クラスに記述しました。

適切な最上位マスター ページを使用するように、入れ子になったマスター ページを構成するにはどうすればよいでしょうか? 2 つのオプションがあります。

  • 2 つの入れ子になったマスター ページ (AdminNestedSite.masterAdminNestedAlternate.master) を作成し、それぞれ最上位マスター ページ Site.masterAlternate.master にバインドします。 次に、BasePage で、Page オブジェクトの MasterPageFile を適切な入れ子になったマスター ページに設定します。
  • 1 つの入れ子になったマスター ページを作成し、コンテンツ ページでこの特定のマスター ページを使用します。 その後、実行時に、入れ子になったマスター ページの MasterPageFile プロパティを、実行時の適切な最上位マスター ページに設定する必要があります (既におわかりかと思いますが、マスター ページにも MasterPageFile プロパティがあります)。

2 番目のオプションを使用することにしましょう。 ~/Admin フォルダーに、AdminNested.master という名前の 1 つの入れ子になったマスター ページ ファイルを作成します。 Site.masterAlternate.master のどちらにも、同じ一連の ContentPlaceHolder コントロールがあるため、バインド先のマスター ページは重要ではありませんが、一貫性を保つために Site.master にバインドすることをお勧めします。

入れ子になったマスター ページを ~/Admin フォルダーに追加します。

図 09: 入れ子になったマスター ページを ~/Admin フォルダーに追加する。 (クリックするとフルサイズの画像が表示されます)

入れ子になったマスター ページは、4 つの ContentPlaceHolder コントロールを含むマスター ページにバインドされているため、Visual Studio では、新しい入れ子になったマスター ページ ファイルの最初のマークアップに 4 つのコンテンツ コントロールが追加されます。 手順 2 と 3 で行ったように、各コンテンツ コントロールに ContentPlaceHolder コントロールを追加し、最上位マスター ページの ContentPlaceHolder コントロールと同じ名前を付けます。 また、MainContent ContentPlaceHolder に対応するコンテンツ コントロールに次のマークアップを追加します。

<div class="instructions"> 
 <b>Administration Instructions:</b>
 <br /> 
 The pages in the Administration section allow you, the Administrator, to 
 add new products and view existing products. 
</div>

次に、CSS ファイル Styles.css および AlternateStyles.css で CSS クラス instructions を定義します。 次の CSS ルールにより、instructions クラスでスタイル設定された HTML 要素が、明るい黄色の背景色と黒色の実線の境界線で表示されます。

.instructions 
{ 
 padding: 6px; 
 border: dashed 1px black; 
 background-color: #ffb; 
 margin-bottom: 10px; 
}

このマークアップは、入れ子になったマスター ページに追加されているため、この入れ子になったマスター ページを使用するページ (つまり、管理セクションのページ) にのみ表示されます。

入れ子になったマスター ページにこれらの追加を行った後、その宣言型マークアップは次のようになります。

<%@ Master Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="false" CodeFile="AdminNested.master.cs" Inherits="Admin_AdminNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 <asp:ContentPlaceHolder ID="head" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <div class="instructions">
 <b>Administration Instructions:</b>
 <br /> 
 The pages in the Administration section allow you, the Administrator, to 
 add new products and view existing products. 
 </div> 
 <asp:ContentPlaceHolder ID="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content3" ContentPlaceHolderID="QuickLoginUI" Runat="Server"> 
 <asp:ContentPlaceHolder ID="QuickLoginUI" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content4" ContentPlaceHolderID="LeftColumnContent" Runat="Server"> 
 <asp:ContentPlaceHolder ID="LeftColumnContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content>

各コンテンツ コントロールには ContentPlaceHolder コントロールがあり、ContentPlaceHolder コントロールの ID プロパティには、最上位マスター ページの対応する ContentPlaceHolder コントロールと同じ値が割り当てられていることに注意してください。 さらに、管理セクション固有のマークアップが MainContent ContentPlaceHolder に表示されます。

図 10 は、Visual Studio のデザイナーに表示された場合の入れ子になったマスター ページ AdminNested.master を示しています。 MainContent コンテンツ コントロールの上にある黄色のボックスに説明が表示されます。

入れ子になったマスター ページは、管理者の指示を含むように最上位のマスター ページを拡張します。

図 10: 入れ子になったマスター ページによって、管理者向けの説明を含むように最上位マスター ページが拡張される。 (クリックするとフルサイズの画像が表示されます)

手順 5: 新しい入れ子になったマスター ページを使用するように既存のコンテンツ ページを更新する

新しいコンテンツ ページの場合、管理者セクションに追加したら、先ほど作成した AdminNested.master マスター ページにバインドする必要があります。 では、既存のコンテンツ ページの場合はどうでしょうか? 現在、サイト内のすべてのコンテンツ ページは、BasePage クラスから派生しています。このクラスは、実行時にコンテンツ ページのマスター ページをプログラムで設定します。 これは、管理セクションのコンテンツ ページに対して求められる動作ではありません。 代わりに、これらのコンテンツ ページで常に AdminNested.master ページを使用するようにします。 実行時に適切な最上位コンテンツ ページを選択するのは、入れ子になったマスター ページが担当します。

この目的の動作を実現する最善の方法は、BasePage クラスを拡張する AdminBasePage という名前の新しいカスタム基本ページを作成することです。 その後、AdminBasePageSetMasterPageFile をオーバーライドし、Page オブジェクトの MasterPageFile を、ハードコーディングされた値 "~/Admin/AdminNested.master" に設定できます。 この方法では、AdminBasePage から派生したすべてのページで AdminNested.master が使用され、BasePage から派生したすべてのページでは、MyMasterPage セッション変数の値に基づいて、MasterPageFile プロパティが "~/Site.master" または "~/Alternate.master" に動的に設定されます。

まず、AdminBasePage.cs という名前の新しいクラス ファイルを App_Code フォルダーに追加します。 AdminBasePageBasePage を拡張し、SetMasterPageFile メソッドをオーバーライドします。 このメソッドでは、MasterPageFile に値 "~/Admin/AdminNested.master" を割り当てます。 これらの変更を行った後、クラス ファイルは次のようになります。

public class AdminBasePage : BasePage 
{ 
    protected override void SetMasterPageFile() 
    { 
        this.MasterPageFile = "~/Admin/AdminNested.master"; 
    } 
}

次に、管理セクション内の既存のコンテンツ ページが BasePage ではなく AdminBasePage から派生するようにする必要があります。 ~/Admin フォルダー内の各コンテンツ ページの分離コード クラス ファイルに移動し、この変更を行います。 たとえば、~/Admin/Default.aspx で、次の分離コード クラス宣言を変更します。

public partial class Admin_Default : BasePage

移動先:

public partial class Admin_Default : AdminBasePage

図 11 は、最上位マスター ページ (Site.master または Alternate.master)、入れ子になったマスター ページ (AdminNested.master)、管理セクションのコンテンツ ページの相互関係を示しています。

入れ子になったマスター ページは、[管理] セクションのページに固有のコンテンツを定義します

図 11: 管理セクション内のページに固有のコンテンツを定義する入れ子になったマスター ページ (クリックするとフルサイズの画像が表示されます)

手順 6: マスター ページのパブリック メソッドとプロパティをミラーリングする

~/Admin/AddProduct.aspx~/Admin/Products.aspx は、プログラムによってマスター ページとやり取りすることを思い出してください。~/Admin/AddProduct.aspx はマスター ページの RefreshRecentProductsGrid パブリック メソッドを呼び出し、その GridMessageText プロパティを設定します。~/Admin/Products.aspx には、PricesDoubled イベントのイベント ハンドラーがあります。 前のチュートリアルでは、これらのパブリック メンバーを定義する BaseMasterPage 抽象クラスを作成しました。

~/Admin/AddProduct.aspx および ~/Admin/Products.aspx ページでは、そのマスター ページが BaseMasterPage クラスから派生することを前提としています。 ただし、現在、AdminNested.master ページは、System.Web.UI.MasterPage クラスを拡張しています。 その結果、~/Admin/Products.aspx にアクセスすると、"'ASP.admin_adminnested_master' 型のオブジェクトを 'BaseMasterPage' 型にキャストできません" というメッセージで InvalidCastException がスローされます。

これを修正するには、AdminNested.master 分離コード クラスで BaseMasterPage を拡張する必要があります。 入れ子になったマスター ページの次の分離コード クラス宣言を更新します。

public partial class Admin_AdminNested : System.Web.UI.MasterPage

移動先:

public partial class Admin_AdminNested : BaseMasterPage

まだ終わりではありません。 BaseMasterPage は抽象クラスであるため、abstract メンバー、RefreshRecentProductsGridGridMessageText をオーバーライドする必要があります。 これらのメンバーは、そのユーザー インターフェイスを更新するために最上位マスター ページによって使用されます (どちらの最上位マスター ページも BaseMasterPage を拡張するため、これらのメソッドは両方に実装されますが、実際に使用するのは Site.master マスター ページだけです)。

これらのメンバーを AdminNested.master に実装する必要がありますが、これらのすべての実装では、入れ子になったマスター ページで使用される最上位レベルのマスター ページで同じメンバーを呼び出すだけで済みます。 たとえば、管理セクションのコンテンツ ページで入れ子になったマスター ページの RefreshRecentProductsGrid メソッドが呼び出された場合、どの入れ子になったマスター ページでも、Site.master または Alternate.masterRefreshRecentProductsGrid メソッドを呼び出すだけで済みます。

これを実現するには、まず、次の @MasterType ディレクティブを AdminNested.master の先頭に追加します。

<%@ MasterType TypeName="BaseMasterPage" %>

@MasterType ディレクティブは、厳密に型指定されたプロパティを、Master という名前の分離コード クラスに追加することを思い出してください。 この後は、RefreshRecentProductsGrid および GridMessageText メンバーをオーバーライドし、呼び出しを Master の対応するメソッドに委任するだけです。

public partial class Admin_AdminNested : BaseMasterPage 
{ 
    public override void RefreshRecentProductsGrid() 
    { 
        Master.RefreshRecentProductsGrid();
    } 
    public override string GridMessageText
    { 
        get 
        {
            return Master.GridMessageText;
        } 
        set
        { 
            Master.GridMessageText = value; 
        } 
    }
}

このコードを配置すると、管理セクション内のコンテンツ ページにアクセスして使用できるようになります。 図 12 は、ブラウザーに表示された場合の ~/Admin/Products.aspx ページを示しています。 ご覧のように、このページには、入れ子になったマスター ページで定義されている "管理の説明" ボックスが含まれています。

[管理] セクションのコンテンツ ページには、各ページの上部にある手順が含まれます

図 12: 管理セクションのコンテンツ ページには、各ページの上部に説明が含まれる (クリックするとフルサイズの画像が表示されます)

手順 7: 実行時に適切な最上位マスター ページを使用する

管理セクション内のすべてのコンテンツ ページは完全に機能しますが、すべてのページで同じ最上位マスター ページが使用され、ChooseMasterPage.aspx でユーザーが選択したマスター ページは無視されます。 この動作は、入れ子になったマスター ページの MasterPageFile プロパティは、その <%@ Master %> ディレクティブで Site.master に静的に設定されているためです。

エンド ユーザーが選択した最上位マスター ページを使用するには、AdminNested.masterMasterPageFile プロパティを、MyMasterPage セッション変数の値に設定する必要があります。 コンテンツ ページの MasterPageFile プロパティを BasePage で設定するため、入れ子になったマスター ページの MasterPageFile プロパティは、BaseMasterPage または AdminNested.master の分離コード クラスで設定するものだと思うかもしれません。 しかし、それでは機能しません。PreInit ステージが終わるまでに MasterPageFile プロパティを設定しておく必要があるためです。 マスター ページからページ ライフサイクルにプログラムでアクセスできる最も早いステージは、Init ステージです (PreInit ステージの後に発生します)。

そのため、入れ子になったマスター ページの MasterPageFile プロパティはコンテンツ ページから設定する必要があります。 AdminNested.master マスター ページを使用するコンテンツ ページのみが AdminBasePage から派生します。 そのため、このロジックをそこに配置できます。 手順 5 では、SetMasterPageFile メソッドをオーバーライドし、Page オブジェクトの MasterPageFile プロパティを "~/Admin/AdminNested.master" に設定しました。 さらにマスター ページの MasterPageFile プロパティを、セッションに格納された結果に設定するように SetMasterPageFile を更新します。

public class AdminBasePage : BasePage 
{ 
    protected override void SetMasterPageFile() 
    { 
        this.MasterPageFile = "~/Admin/AdminNested.master"; 
        Page.Master.MasterPageFile = base.GetMasterPageFileFromSession(); 
    } 
}

前のチュートリアルで BasePage クラスに追加した GetMasterPageFileFromSession メソッドは、セッション変数の値に基づいて適切なマスター ページ ファイルのパスを返します。

この変更を行うと、ユーザーのマスター ページの選択は、管理セクションに引き継がれます。 図 13 は図 12 と同じページですが、ユーザーがマスター ページの選択を Alternate.master に変更した後のものです。

入れ子になった管理ページでは、ユーザーによって選択された最上位のマスター ページが使用されます

図 13: ユーザーが選択した最上位マスター ページを使用する入れ子になったマスター ページ (クリックするとフルサイズの画像が表示されます)

まとめ

コンテンツ ページをマスター ページにバインドする方法と同様に、子マスター ページを親マスター ページにバインドして、入れ子になったマスター ページを作成できます。 子マスター ページでは、親の ContentPlaceHolder ごとにコンテンツ コントロールを定義できます。その後、これらのコンテンツ コントロールに独自の ContentPlaceHolder コントロール (およびその他のマークアップ) を追加できます。 入れ子になったマスター ページは、すべてのページで全体的な外観と操作性を共有するが、サイトの特定のセクションでは独自のカスタマイズが必要となる大規模な Web アプリケーションで非常に役立ちます。

プログラミングに満足!

もっと読む

この記事で説明したトピックの詳細については、次のリソースを参照してください。

作成者について

複数の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の著書は、「Sams Teach Yourself ASP.NET 3.5 in 24 Hours」です。 Mitchell 氏には、mitchell@4GuysFromRolla.com から、または http://ScottOnWriting.NET で彼のブログを介して連絡できます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 今後の MSDN の記事を確認することに関心がありますか? ご希望なら、mitchell@4GuysFromRolla.com でメッセージをお送りください