プログラムでマスター ページを指定する (C#)

作成者: Scott Mitchell

PreInit イベント ハンドラー経由でプログラムでコンテンツ ページのマスター ページを設定する方法を見ていきます。

はじめに

マスター ページを利用してサイト全体レイアウトを作成する」の最初の例以降、すべてのコンテンツ ページは @Page ディレクティブの MasterPageFile 属性経由で宣言によってマスター ページを参照しています。 たとえば、次の @Page ディレクティブは、コンテンツ ページをマスター ページ Site.master にリンクします。

<%@ Page Language="C#" MasterPageFile="~/Site.master" ... %>

System.Web.UI 名前空間の Page クラスには、コンテンツ ページのマスター ページへのパスを返す MasterPageFile プロパティが含まれています。@Page ディレクティブによって設定されるのはこのプロパティです。 このプロパティは、プログラムでコンテンツ ページのマスター ページを指定するためにも使用できます。 この方法は、ページにアクセスするユーザーなどの外部要因に基づいてマスター ページを動的に割り当てる場合に便利です。

このチュートリアルでは、Web サイトに 2 番めのマスター ページを追加し、実行時に使用するマスター ページを動的に決定します。

手順 1: ページのライフサイクルを確認する

コンテンツ ページである ASP.NET ページの要求が Web サーバーに到着するたびに、ASP.NET エンジンはページのコンテンツ コントロールをマスター ページの対応する ContentPlaceHolder コントロールに融合する必要があります。 この融合により、一般的なページ ライフサイクルを進めることができる単一のコントロール階層が作成されます。

図 1 に、この融合を示します。 図 1 の手順 1 は、最初のコンテンツとマスター ページのコントロール階層を示しています。 PreInit ステージの終わりに、ページ内のコンテンツ コントロールがマスター ページの対応する ContentPlaceHolders に追加されます (手順 2)。 この融合の後、マスター ページは融合されたコントロール階層のルートとして機能します。 この融合されたコントロール階層がページに追加されて、最終的なコントロール階層が生成されます (手順 3)。 最終結果として、ページのコントロール階層に融合されたコントロール階層が含まれます。

The Master Page and Content Page's Control Hierarchies are Fused Together during the PreInit Stage

図 01: マスター ページとコンテンツ ページのコントロール階層が、PreInit ステージ中に融合されます (クリックするとフルサイズの画像が表示されます)

手順 2: コードから MasterPageFile プロパティを設定する

マスター ページがこの融合に参加する部分は、Page オブジェクトの MasterPageFile プロパティの値によって異なります。 @Page ディレクティブに MasterPageFile 属性を設定すると、ページのライフサイクルの最初のステージである初期化ステージ中に PageMasterPageFile プロパティを割り当てるという実質的な効果があります。 別の方法として、プログラムでこのプロパティを設定できます。 ただし、図 1 の融合が行われる前に、このプロパティを設定することが不可欠です。

PreInit ステージの開始時に、Page オブジェクトは PreInit イベントを発生させて、その OnPreInit メソッドを呼び出します。 次に、プログラムでマスター ページを設定するために、PreInit イベントのイベント ハンドラーを作成するか、OnPreInit メソッドをオーバーライドできます。 両方のアプローチを見てみましょう。

まず、サイトのホームページの分離コード クラス ファイルである Default.aspx.cs を開きます。 次のコードを入力して、ページの PreInit イベントのイベント ハンドラーを追加します。

protected void Page_PreInit(object sender, EventArgs e) 
{ 
}

ここから、MasterPageFile プロパティを設定できます。 MasterPageFile プロパティに値 "~/Site.master" を割り当てるようにコードを更新します。

protected void Page_PreInit(object sender, EventArgs e) 
{
    this.MasterPageFile = "~/Site.master"; 
}

ブレークポイントを設定してデバッグを開始すると、Default.aspx ページへのアクセスや、このページへのポストバックが発生するたびに、Page_PreInit イベント ハンドラーが実行され、MasterPageFile プロパティが "~/Site.master" に割り当てられることがわかります。

別の方法として、Page クラスの OnPreInit メソッドをオーバーライドし、そこで MasterPageFile プロパティを設定できます。 この例では、特定のページではなく、BasePage からマスター ページを設定しましょう。 「マスター ページでタイトル、メタ タグ、その他の HTML ヘッダーを指定する」チュートリアルでカスタムの基本ページ クラス (BasePage) を作成したことを思い出してください。 現在、BasePagePage クラスの OnLoadComplete メソッドをオーバーライドし、このメソッドがサイト マップのデータに基づいてページの Title プロパティを設定します。 OnPreInit メソッドもオーバーライドするように BasePage を更新して、マスター ページをプログラムで指定するようにしましょう。

protected override void OnPreInit(EventArgs e) 
{ 
    this.MasterPageFile = "~/Site.master"; 
    base.OnPreInit(e); 
}

すべてのコンテンツ ページが BasePage から派生しているため、すべてのコンテンツ ページにそのマスター ページがプログラムで割り当てられるようになりました。 この時点で、Default.aspx.csPreInit イベント ハンドラーは余分です。自由に削除してください。

@Page ディレクティブについて

少し混乱する可能性があるのは、コンテンツ ページの MasterPageFile プロパティが 2 つの場所で指定されるということです。1 つはプログラムで BasePage クラスの OnPreInit メソッドから、もう 1 つは各コンテンツ ページの @Page ディレクティブ内の MasterPageFile 属性経由で指定されます。

ページ ライフサイクルの最初のステージは初期化ステージです。 このステージ中に、Page オブジェクトの MasterPageFile プロパティに @Page ディレクティブ内の MasterPageFile 属性の値が割り当てられます (指定されている場合)。 PreInit ステージは初期化ステージの後に続きます。プログラムで Page オブジェクトの MasterPageFile プロパティを設定し、それによって @Page ディレクティブから割り当てられた値を上書きするのはここです。 Page オブジェクトの MasterPageFile プロパティはプログラムで設定するため、エンド ユーザーのエクスペリエンスに影響を与えずに @Page ディレクティブから MasterPageFile 属性を削除できます。 これを確かめるために、思い切って Default.aspx@Page ディレクティブから MasterPageFile 属性を削除して、ブラウザーからページにアクセスしてみましょう。 予想どおり、出力は属性が削除される前と同じです。

MasterPageFile プロパティが @Page ディレクティブ経由と、プログラムのどちらで設定されるかは、エンド ユーザーのエクスペリエンスに関係なく行われます。 ただし、@Page ディレクティブの MasterPageFile 属性は、デザイン時にデザイナーで WYSIWYG ビューを生成するために Visual Studio によって使用されます。 Visual Studio で Default.aspx に戻り、デザイナーに移動すると、「Master Page error: The page has controls that require a Master Page reference, but none is specified (マスター ページ エラー: ページにマスター ページ参照が必要なコントロールがありますが、何も指定されていません)」というメッセージが表示されます (図 2 を参照)。

つまり、Visual Studio で豊富なデザイン時エクスペリエンスを利用するには、@Page ディレクティブに MasterPageFile 属性を残す必要があります。

Visual Studio Uses the <span class=デザイン ビューをレンダリングするための @Page ディレクティブの MasterPageFile 属性" />

図 02: Visual Studio は、@Page ディレクティブの MasterPageFile 属性を使用してデザイン ビューをレンダリングします (クリックするとフルサイズの画像が表示されます)

手順 3: 代替マスター ページを作成する

コンテンツ ページのマスター ページは実行時にプログラムで設定できるため、外部条件に基づいて特定のマスター ページを動的に読み込むことができます。 この機能は、サイトのレイアウトをユーザーに基づいて変化させる必要がある状況で便利です。 たとえば、ブログ エンジン Web アプリケーションで、ユーザーが自分のブログのレイアウトを選択でき、それぞれのレイアウトは異なるマスター ページに関連付けられている場合があります。 実行時に、訪問者がユーザーのブログを表示したとき、Web アプリケーションはブログのレイアウトを決定し、対応するマスター ページをコンテンツ ページに動的に関連付ける必要があります。

いくつかの外部条件に基づいて、実行時にマスター ページを動的に読み込む方法を見てみましょう。 現在、この Web サイトには 1 つのマスター ページ (Site.master) だけが含まれています。 実行時にマスター ページを選択する方法を示すために、別のマスター ページが必要です。 この手順では、新しいマスター ページの作成と構成に焦点をあてます。 実行時に使用するマスター ページの決定方法は、手順 4 で示します。

Alternate.master という名前のルート フォルダーに新しいマスター ページを作成します。 Web サイトに AlternateStyles.css という名前の新しいスタイル シートも追加します。

Add Another Master Page and CSS File to the Website

図 03: 別のマスター ページと CSS ファイルを Web サイトに追加します (クリックするとフルサイズの画像が表示されます)

タイトルをページの上部に、中央揃えで、濃紺の背景に表示するように Alternate.master マスター ページをデザインしました。 左側の列を調べ、その内容を MainContent の ContentPlaceHolder コントロールの下に移動しました。このコントロールの対象が、ページ幅全体になりました。 さらに、順序付けられていないレッスン リストをやめて、MainContent の上の横方向のリストに置き換えました。 マスター ページ (および、その延長線上で、そのコンテンツ ページ) で使用されるフォントと色も更新しました。 Alternate.master マスター ページを使用したときの Default.aspx を図 4 に示します。

Note

ASP.NET には、"テーマ" を定義する機能が含まれています。 テーマとは、実行時にページに適用できるイメージ、CSS ファイル、およびスタイル関連の Web コントロール プロパティ設定のコレクションです。 テーマは、表示される画像と CSS ルールのみに、サイトのレイアウトの違いがある場合に使用する方法です。 異なる Web コントロールを使用するか、根本的に異なるレイアウトがあるなど、レイアウトが本質的に異なる場合は、別個のマスター ページを使用する必要があります。 テーマの詳細については、このチュートリアルの最後にある「関連項目」セクションを参照してください。

Our Content Pages Can Now Use a New Look and Feel

図 04: コンテンツ ページで新しい外観を使用できるようになりました (クリックするとフルサイズの画像が表示されます)

マスター ページとコンテンツ ページのマークアップが融合されるときに、MasterPage クラスは、コンテンツ ページ内のすべてのコンテンツ コントロールがマスター ページの ContentPlaceHolder を参照していることを確認します。 存在しない ContentPlaceHolder を参照するコンテンツ コントロールが見つかった場合、例外がスローされます。 つまり、コンテンツ ページに割り当てられるマスター ページは、コンテンツ ページ内の各コンテンツ コントロールに対する ContentPlaceHolder を持っていることが必要です。

Site.master マスター ページには、次の 4 つの ContentPlaceHolder コントロールが含まれています。

  • head
  • MainContent
  • QuickLoginUI
  • LeftColumnContent

この Web サイトのコンテンツ ページには、1 つか 2 つのみのコンテンツコントロールが含まれているものと、使用可能な ContentPlaceHolder それぞれに対するコンテンツ コントロールが含まれているものがあります。 新しいマスター ページ (Alternate.master) が、Site.master のすべての ContentPlaceHolders に対するコンテンツ コントロールを持つコンテンツ ページに割り当てられる可能性がある場合は、Alternate.master にも Site.master と同じ ContentPlaceHolder コントロールを含める必要があります。

Alternate.master マスター ページを自分のマスター ページ (図 4 を参照) と似た外観にするには、まず AlternateStyles.css スタイル シートにマスター ページのスタイルを定義します。 AlternateStyles.css に次のルールを追加します。

body 
{ 
 font-family: Comic Sans MS, Arial; 
 font-size: medium; 
 margin: 0px; 
} 
#topContent 
{ 
 text-align: center; 
 background-color: Navy; 
 color: White; 
 font-size: x-large;
 text-decoration: none; 
 font-weight: bold; 
 padding: 10px; 
 height: 50px;
} 
#topContent a 
{ 
 text-decoration: none; 
 color: White; 
} 
#navContent 
{ 
 font-size: small; 
 text-align: center; 
} 
#footerContent 
{ 
 padding: 10px; 
 font-size: 90%; 
 text-align: center; 
 border-top: solid 1px black; 
} 
#mainContent 
{ 
 text-align: left; 
 padding: 10px; 
}

次に、Alternate.master に次の宣言型マークアップを追加します。 ご覧のように、Alternate.master には、Site.master のContentPlaceHolder コントロールと同じ ID 値を持つ 4 つの ContentPlaceHolder コントロールが含まれます。 さらに、ASP.NET AJAX フレームワークを使用する Web サイトのページに必要な ScriptManager コントロールも含まれます。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head id="Head1" runat="server"> 
 <title>Untitled Page</title>
 <asp:ContentPlaceHolder id="head" runat="server">
 </asp:ContentPlaceHolder> 
 <link href="AlternateStyles.css" rel="stylesheet" type="text/css" /> 
</head> 
<body> 
 <form id="form1" runat="server"> 
 <asp:ScriptManager ID="MyManager" runat="server"> 
 </asp:ScriptManager>
 <div id="topContent">
 <asp:HyperLink ID="lnkHome" runat="server" NavigateUrl="~/Default.aspx" 
 Text="Master Pages Tutorials" /> 
 </div>
 <div id="navContent">
 <asp:ListView ID="LessonsList" runat="server" 
 DataSourceID="LessonsDataSource">
 <LayoutTemplate>
 <asp:PlaceHolder runat="server" ID="itemPlaceholder" /> 
 </LayoutTemplate>
 <ItemTemplate>
 <asp:HyperLink runat="server" ID="lnkLesson" 
 NavigateUrl='<%# Eval("Url") %>' 
 Text='<%# Eval("Title") %>' /> 
 </ItemTemplate>
 <ItemSeparatorTemplate> | </ItemSeparatorTemplate> 
 </asp:ListView>
 <asp:SiteMapDataSource ID="LessonsDataSource" runat="server" 
 ShowStartingNode="false" /> 
 </div>
 <div id="mainContent">
 <asp:ContentPlaceHolder id="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </div> 
 <div id="footerContent">
 <p> 
 <asp:Label ID="DateDisplay" runat="server"></asp:Label> 
 </p>
 <asp:ContentPlaceHolder ID="QuickLoginUI" runat="server"> 
 </asp:ContentPlaceHolder>
 <asp:ContentPlaceHolder ID="LeftColumnContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </div> 
 </form>
</body> 
</html>

新しいマスター ページをテストする

この新しいマスター ページをテストするには、MasterPageFile プロパティに値 "~/Alternate.master" が割り当てられるように BasePage クラスの OnPreInit メソッドを更新してから、Web サイトにアクセスします。 ~/Admin/AddProduct.aspx~/Admin/Products.aspx の 2 つを除くすべてのページが、エラーなしで機能するはずです。 ~/Admin/AddProduct.aspx で DetailsView に製品を追加すると、マスター ページの GridMessageText プロパティを設定しようとするコード行で NullReferenceException が発生します。 ~/Admin/Products.aspx にアクセスすると、ページの読み込み時に InvalidCastException がスローされ、「'ASP.alternate_master' 型のオブジェクトを 'ASP.site_master' 型にキャストすることはできません」というメッセージが表示されます。

これらのエラーは、Alternate.master に定義されていないパブリック イベント、プロパティ、およびメソッドが Site.master 分離コード クラスに含まれているために発生します。 これら 2 つのページのマークアップ部分には、Site.master マスター ページを参照する @MasterType ディレクティブがあります。

<%@ MasterType VirtualPath="~/Site.master" %>

また、~/Admin/AddProduct.aspx の DetailsView の ItemInserted イベント ハンドラーには、緩やかに型指定された Page.Master プロパティを型 Site のオブジェクトにキャストするコードが含まれています。 @MasterType ディレクティブ (このように使用) と ItemInserted イベント ハンドラーのキャストは、Site.master マスター ページに ~/Admin/AddProduct.aspx ページと ~/Admin/Products.aspx ページを密に結合します。

この密結合を解除するために、Site.masterAlternate.master を、パブリック メンバーの定義を含む共通の基底クラスから派生させることができます。 その後、この共通の基本データ型を参照するように @MasterType ディレクティブを更新できます。

カスタムの基本マスター ページ クラスを作成する

BaseMasterPage.cs という名前の新しいクラス ファイルを App_Code フォルダーに追加し、System.Web.UI.MasterPage から派生させます。 BaseMasterPageRefreshRecentProductsGrid メソッドと GridMessageText プロパティを定義する必要がありますが、これらを Site.master から単純に移動することはできません。これらのメンバーは Site.master マスター ページに固有の Web コントロール (RecentProducts GridView と GridMessage Label) で動作するためです。

必要なことは、これらのメンバーの定義は BaseMasterPage で行われるが、実際の実装はその派生クラス (Site.master および Alternate.master) によって行われるように BaseMasterPage を構成することです。 この種類の継承は、クラスとそのメンバーに abstract のマークを付けることで可能になります。 つまり、これら 2 つのメンバーに abstract キーワードを追加すると、BaseMasterPageRefreshRecentProductsGridGridMessageText を実装していないが、その派生クラスが実装することが告知されます。

BaseMasterPagePricesDoubled イベントを定義し、派生クラスによってイベントを発生させる手段を提供する必要もあります。 この動作を支援するために .NET Framework で使用されるパターンは、基底クラスにパブリック イベントを作成し、OnEventName という名前の保護された virtual メソッドを追加することです。 これで、派生クラスはこのメソッドを呼び出してイベントを発生させたり、イベントが発生する直前または発生後にコードを実行するようにメソッドをオーバーライドしたりできます。

次のコードが含まれるように BaseMasterPage クラスを更新します。

using System; public abstract class BaseMasterPage : System.Web.UI.MasterPage
{ 
    public event EventHandler PricesDoubled; 
    protected virtual void OnPricesDoubled(EventArgs e) 
    { 
        if (PricesDoubled != null) 
        PricesDoubled(this, e); 
    } 
    public abstract void RefreshRecentProductsGrid();
    public abstract string GridMessageText 
    { 
        get; 
        set; 
    } 
}

次に、Site.master 分離コード クラスに移動し、クラスが BaseMasterPage から派生するようにします。 BaseMasterPageabstract であるため、この Site.master でこれらの abstract メンバーをオーバーライドする必要があります。 メソッドとプロパティ定義に override キーワードを追加します。 さらに、DoublePrice ボタンの Click イベント ハンドラーで PricesDoubled イベントを発生させるコードを、基底クラスの OnPricesDoubled メソッドの呼び出しで更新します。

これらの変更を行うと、Site.master 分離コード クラスには次のコードが含まれているはずです。

public partial class Site : BaseMasterPage { 
    protected void Page_Load(object sender, EventArgs e) 
    { 
        DateDisplay.Text = DateTime.Now.ToString("dddd, MMMM dd"); 
    } 
    public override void RefreshRecentProductsGrid()
    { 
        RecentProducts.DataBind();
    } 
    public override string GridMessageText
    { 
        get 
        {
            return GridMessage.Text;
        } 
        set
        {
            GridMessage.Text = value; 
        } 
    }
    protected void DoublePrice_Click(object sender, EventArgs e) 
    { 
        // Double the prices 
        DoublePricesDataSource.Update();
        // Refresh RecentProducts 
        RecentProducts.DataBind();
        // Raise the PricesDoubled event
        base.OnPricesDoubled(EventArgs.Empty);
    } 
}

Alternate.master の分離コード クラスも、BaseMasterPage から派生し、2 つの abstract メンバーをオーバーライドするように更新する必要があります。 ただし、Alternate.master には最新の製品を一覧表示する GridView も、新しい製品がデータベースに追加された後にメッセージを表示する Label も含まれていないため、これらのメソッドは何もする必要はありません。

public partial class Alternate : BaseMasterPage 
{ 
    public override void RefreshRecentProductsGrid() 
    { 
        // Do nothing 
    } 
    public override string GridMessageText 
    { 
        get
        { 
            return string.Empty;
        } 
        set
        {
            // Do nothing 
        } 
    }
}

基本マスター ページ クラスを参照する

BaseMasterPage クラスを完成させ、2 つのマスター ページでそれを拡張したので、最後の手順では、この共通の型を参照するように ~/Admin/AddProduct.aspx ページと ~/Admin/Products.aspx ページを更新します。 まず、両方のページの @MasterType ディレクティブを変更します。変更前:

<%@ MasterType VirtualPath="~/Site.master" %>

移動先:

<%@ MasterType TypeName="BaseMasterPage" %>

@MasterType プロパティは、ファイル パスを参照するのではなく、基本型 (BaseMasterPage) を参照するようになりました。 その結果、両方のページの分離コード クラスで使用される厳密に型指定された Master プロパティの型が (型 Site ではなく) BaseMasterPage になりました。 このように変更したら、~/Admin/Products.aspx に再度アクセスします。 前は、ページが Alternate.master マスター ページを使用するように構成されているのに、@MasterType ディレクティブが Site.master ファイルを参照したため、キャスト エラーが発生しました。 しかし今度は、ページはエラーなしでレンダリングされます。 これは、Alternate.master マスター ページを型 BaseMasterPage のオブジェクトにキャストできるためです (拡張されるため)。

~/Admin/AddProduct.aspx で行う必要がある小さな変更が 1 つあります。 DetailsView コントロールの ItemInserted イベント ハンドラーは、厳密に型指定された Master プロパティと緩やかに型指定された Page.Master プロパティの両方を使用します。 @MasterType ディレクティブを更新したときに厳密に型指定された参照を修正しましたが、緩やかに型指定された参照を更新する必要がまだあります。 次のコード行を置き換えます。

Site myMasterPage = Page.Master as Site;

置き換える内容は次のとおりです。これは Page.Master を基本型にキャストします。

BaseMasterPage myMasterPage = Page.Master as BaseMasterPage;

手順 4: コンテンツ ページにバインドするマスター ページを決定する

BasePage クラスは現在、すべてのコンテンツ ページの MasterPageFile プロパティを、ページ ライフサイクルの PreInit ステージでハードコーディングされた値に設定しています。 このコードを更新して、何らかの外部要因をマスター ページの基礎として使用できます。 読み込むマスター ページは、現在ログオンしているユーザーの設定によって変わると考えられます。 その場合、現在アクセスしているユーザーのマスター ページの基本設定を調べるコードを BasePageOnPreInit メソッドに記述する必要があります。

使用するマスター ページ (Site.master または Alternate.master) をユーザーが選択できる Web ページを作成し、その選択肢をセッション変数に保存しましょう。 まず、ChooseMasterPage.aspx という名前のルート ディレクトリに新しい Web ページを作成します。 このページ (またはそれ以降の任意のコンテンツ ページ) を作成するとき、それをマスター ページにバインドする必要はありません。マスター ページはプログラムで BasePage に設定されるためです。 ただし、新しいページをマスター ページにバインドしない場合、新しいページの既定の宣言型マークアップには、Web フォームと、マスター ページによって提供されるその他のコンテンツが含まれます。 このマークアップを適切なコンテンツ コントロールに手動で置き換える必要があります。 そのため、新しい ASP.NET ページをマスター ページにバインドする方が簡単であると思われます。

Note

Site.masterAlternate.master の ContentPlaceHolder コントロールのセットは同じであるため、新しいコンテンツ ページを作成するときにどちらのマスター ページを選択しても問題ありません。 一貫性のために、Site.master の使用をお勧めします。

Add a New Content Page to the Website

図 05: Web サイトに新しいコンテンツ ページを追加します (クリックするとフルサイズの画像が表示されます)

このレッスンのエントリを含むように Web.sitemap ファイルを更新します。 マスター ページと ASP.NET AJAX レッスンの <siteMapNode> の下に次のマークアップを追加します。

<siteMapNode url="~/ChooseMasterPage.aspx" title="Choose a Master Page" />

ChooseMasterPage.aspx ページにコンテンツを追加する前に、ページの分離コード クラスを更新して (System.Web.UI.Page ではなく) BasePage から派生するようにします。 次に、ページに DropDownList コントロールを追加し、その ID プロパティを MasterPageChoice に設定し、Text 値が "~/Site.master" と "~/Alternate.master" である 2 つの ListItem を追加します。

ボタン Web コントロールをページに追加し、その ID プロパティと Text プロパティをそれぞれ SaveLayout と [Save Layout Choice] (レイアウトの選択を保存) に設定します。 この時点で、ページの宣言型マークアップは次のようになります。

<p> 
 Your layout choice: 
 <asp:DropDownList ID="MasterPageChoice" runat="server"> 
 <asp:ListItem>~/Site.master</asp:ListItem>
 <asp:ListItem>~/Alternate.master</asp:ListItem>
 </asp:DropDownList> 
</p> 
<p> 
 <asp:Button ID="SaveLayout" runat="server" Text="Save Layout Choice" /> 
</p>

ページが最初に表示されるときに、ユーザーが現在選択しているマスター ページの選択肢を表示する必要があります。 Page_Load イベント ハンドラーを作成し、次のコードを追加します。

protected void Page_Load(object sender, EventArgs e) 
{ 
    if (!Page.IsPostBack) 
    { 
        if (Session["MyMasterPage"] != null)
        {
            ListItem li = MasterPageChoice.Items.FindByText(Session["MyMasterPage"].ToString());
            if (li != null) 
                li.Selected = true; 
        } 
    }
}

上記のコードは、初回のページ アクセスでのみ実行されます (後続のポストバックでは実行されません)。 まずセッション変数 MyMasterPage が存在するかどうかを確認します。 存在する場合は、MasterPageChoice DropDownList に一致する ListItem を見つけようとします。 一致する ListItem が見つかった場合、その Selected プロパティは true に設定されます。

ユーザーの選択肢を MyMasterPage セッション変数に保存するコードも必要です。 SaveLayout ボタンの Click イベントのイベント ハンドラーを作成し、次のコードを追加します。

protected void SaveLayout_Click(object sender, EventArgs e)
{
    Session["MyMasterPage"] = MasterPageChoice.SelectedValue;
    Response.Redirect("ChooseMasterPage.aspx"); 
}

Note

Click イベント ハンドラーがポストバックで実行される時点までに、マスター ページは既に選択されています。 そのため、ユーザーのドロップダウン リストの選択肢は、次回のページ アクセスまで反映されません。 Response.Redirect は、ChooseMasterPage.aspx を再度要求することをブラウザーに強制します。

ChooseMasterPage.aspx ページが完成したら、最後のタスクは、BasePageMyMasterPage セッション変数の値に基づいて MasterPageFile プロパティを割り当てるようにすることです。 セッション変数が設定されていない場合は、BasePage の既定値を Site.master にします。

protected override void OnPreInit(EventArgs e) 
{ 
    SetMasterPageFile();
    base.OnPreInit(e); 
} 
protected virtual void SetMasterPageFile()
{ 
    this.MasterPageFile = GetMasterPageFileFromSession();
} 
protected string GetMasterPageFileFromSession() 
{ 
    if (Session["MyMasterPage"] == null) 
        return "~/Site.master";
    else
        return Session["MyMasterPage"].ToString(); 
}

Note

Page オブジェクトの MasterPageFile プロパティを割り当てるコードを OnPreInit イベント ハンドラーから 2 つの別個のメソッドに移動しました。 この最初のメソッド SetMasterPageFile は、MasterPageFile プロパティを、2 番めのメソッド GetMasterPageFileFromSession によって返される値に割り当てます。 BasePage を拡張する将来のクラスが必要に応じてそれをオーバーライドしてカスタム ロジックを実装できるように、SetMasterPageFile メソッドを virtual にしました。 BasePageSetMasterPageFile プロパティをオーバーライドする例は、次のチュートリアルで示します。

このコードが配置された状態で、ChooseMasterPage.aspx ページにアクセスします。 最初は Site.master マスター ページが選択されていますが (図 6 を参照)、ユーザーはドロップダウン リストから別のマスター ページを選択できます。

Content Pages are Displayed Using the Site.master Master Page

図 06: Site.master マスター ページを使用してコンテンツ ページが表示されています (クリックするとフルサイズの画像が表示されます)

Content Pages are Now Displayed Using the Alternate.master Master Page

図 07: 今度は Alternate.master マスター ページを使用してコンテンツ ページが表示されています (クリックするとフルサイズの画像が表示されます)

まとめ

コンテンツ ページにアクセスすると、そのコンテンツ コントロールはそのマスター ページの ContentPlaceHolder コントロールと融合されます。 コンテンツ ページのマスター ページは、初期化ステージ中に @Page ディレクティブの MasterPageFile 属性に割り当てられる Page クラスの MasterPageFile プロパティによって示されます。 このチュートリアルで示したように、PreInit ステージが終了する前に行う限り、MasterPageFile プロパティに値を割り当てることができます。 プログラムでマスター ページを指定できることで、外部要因に基づいてコンテンツ ページをマスター ページに動的にバインドするなどの、より高度なシナリオの扉が開きます。

プログラミングに満足!

もっと読む

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

作成者について

複数の 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) 経由で連絡できます。

特別な感謝

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