共用方式為


從內容頁與主版頁面互動 (C#)

由斯科特· 米切爾

檢查如何從內容頁面中的程式代碼呼叫方法、設定屬性等主版頁面。

簡介

在過去五個教學課程中,我們已探討如何建立主版頁面、定義內容區域、將 ASP.NET 頁面系結至主版頁面,以及定義頁面特定內容。 當訪客要求特定內容頁面時,內容和主版頁面的標記會在運行時間融合,以產生統一控件階層的轉譯。 因此,我們已經看到主版頁面及其其中一個內容頁面可以互動的方式:內容頁面會拼出標記,以轉譯至主版頁面的 ContentPlaceHolder 控件。

我們尚未檢查的是主版頁面和內容頁面如何以程式設計方式互動。 除了定義主版頁面 ContentPlaceHolder 控件的標記之外,內容頁面也可以將值指派給主版頁面的公用屬性,並叫用其公用方法。 同樣地,主版頁面可能會與其內容頁面互動。 雖然主版與內容頁面之間的程式設計互動與宣告式標記之間的互動較不常見,但有許多案例需要這類程式設計互動。

在本教學課程中,我們會檢查內容頁面如何以程序設計方式與其主版頁面互動;在下一個教學課程中,我們將探討主版頁面如何與內容頁面類似互動。

內容頁面與其主版頁面之間的程式設計互動範例

當頁面的特定區域需要逐頁設定時,我們會使用 ContentPlaceHolder 控件。 但是,大部分頁面需要發出特定輸出的情況如何,但少數頁面需要自定義它以顯示其他內容? 我們在多個 ContentPlaceHolders 和預設內容教學課程中檢查的其中一個範例,涉及在每個頁面上顯示登入介面。 雖然大部分頁面都應該包含登入介面,但應該隱藏少數頁面,例如:主要登入頁面(Login.aspx;建立帳戶頁面;以及其他只能供已驗證的使用者存取的頁面。 Multiple ContentPlaceHolders 和預設內容 教學課程示範如何在主版頁面中定義 ContentPlaceHolder 的預設內容,以及如何在不想要默認內容的頁面中覆寫它。

另一個選項是在主版頁面中建立公用屬性或方法,指出是要顯示或隱藏登入介面。 例如,主版頁面可能包含名為 ShowLoginUI 的公用屬性,其值是用來在主版頁面中設定 Visible Login控件的屬性。 應該隱藏登入使用者介面的內容頁面,然後可以程式設計方式將 ShowLoginUI 屬性設定為 false

在內容頁面中出現某些動作之後,需要重新整理主版頁面中顯示的數據時,可能會發生內容和主版頁面互動最常見的範例。 請考慮包含 GridView 的主版頁面,其中顯示特定資料庫數據表中最近新增的五筆記錄,且其中一個內容頁面包含將新記錄新增至該相同數據表的介面。

當使用者瀏覽頁面以新增記錄時,她會看到主版頁面中顯示最近新增的五筆記錄。 填入新記錄數據行的值之後,她會提交窗體。 假設主版頁面中的 GridView 其 屬性設定為 true(預設值),則會從檢視狀態重載其 EnableViewState 內容,因此即使剛將較新的記錄新增至資料庫,也會顯示五個相同的記錄。 這可能會混淆使用者。

注意

即使您停用 GridView 的檢視狀態,讓它在每次回傳時重新系結至其基礎數據源,它仍然不會顯示剛新增的記錄,因為數據會系結至頁面生命週期中稍早的 GridView,而不是將新記錄新增至資料庫時。

若要解決此問題,如此一來,剛新增的記錄就會顯示在主版頁面的 GridView 中,我們需要指示 GridView 在新的記錄新增至資料庫之後,重新系結至其數據源。 這需要內容和主版頁面之間的互動,因為新增記錄的介面(及其事件處理程式)位於內容頁面中,但需要重新整理的 GridView 位於主版頁面。

因為從內容頁面中事件處理程式重新整理主版頁面的顯示是內容和主版頁面互動最常見的需求之一,讓我們更詳細地探索本主題。 本教學課程的下載包含網站資料夾中名為 NORTHWIND.MDF App_Data 的 Microsoft SQL Server 2005 Express Edition 資料庫。 Northwind 資料庫會儲存虛構公司 Northwind Traders 的產品、員工和銷售資訊。

步驟 1 會逐步解說在主版頁面中顯示 GridView 中最近新增的五個產品。 步驟 2 會建立內容頁面來新增產品。 步驟 3 會探討如何在主版頁面中建立公用屬性和方法,步驟 4 說明如何以程式設計方式從內容頁面使用這些屬性和方法進行介面。

注意

本教學課程不會深入探討在 ASP.NET 中使用數據的詳細數據。 設定主版頁面以顯示數據和插入數據的內容頁面的步驟已完成,但很簡潔。 如需深入瞭解如何顯示和插入數據,以及使用 SqlDataSource 和 GridView 控件,請參閱本教學課程結尾一節中的資源。

步驟 1:在主版頁面中顯示五個最近新增的產品

開啟主版頁面, Site.master 並將 Label 和 GridView 控制項新增至 leftContent <div>。 清除 Label 的 Text 屬性、將其 EnableViewState 屬性設定為 false,並將其 ID 屬性設定為 GridMessage;將 GridView 的 ID 屬性設定為 RecentProducts。 接下來,從設計師展開 GridView 的智慧標記,然後選擇將其系結至新的數據源。 這會啟動 [數據源組態精靈]。 因為資料夾中的 App_Data Northwind 資料庫是Microsoft SQL Server 資料庫,因此選取 [請參閱圖 1],選擇建立 SqlDataSource;將 SqlDataSource RecentProductsDataSource命名為 。

將 GridView 系結至名為 RecentProductsDataSource 的 SqlDataSource 控制件

圖 01:將 GridView 系結至名為 RecentProductsDataSource 的 SqlDataSource 控件(按兩下以檢視完整大小的影像

下一個步驟會要求我們指定要連線的資料庫。 NORTHWIND.MDF從下拉式清單中選擇資料庫檔案,然後按 [下一步]。 因為這是我們第一次使用此資料庫,精靈會提供將 連接字串 儲存在 中Web.config。 讓它使用 名稱NorthwindConnectionString來儲存 連接字串。

線上到 Northwind 資料庫

圖 02:連線到 Northwind 資料庫 (按兩下以檢視完整大小的影像

[設定數據源精靈] 提供兩種方法,我們可以指定用來擷取數據的查詢:

  • 藉由指定自定義 SQL 語句或預存程式,或
  • 藉由挑選數據表或檢視表,然後指定要傳回的數據行

因為我們想要只傳回最近新增的五個產品,所以我們需要指定自定義 SQL 語句。 使用下列 SELECT 查詢:

SELECT TOP 5 ProductName, UnitPrice FROM Products ORDER BY ProductID DESC

關鍵詞 TOP 5 只會從查詢傳回前五筆記錄。 Products數據表的主鍵 ProductID是一個IDENTITY數據行,可保證新增至數據表的每個新產品都會有比前一個專案更大的值。 因此,依遞減順序排序結果 ProductID 會傳回以最近建立的產品開始的產品。

傳回五個最近新增的產品

圖 03:傳回五個最近新增的產品(按兩下以檢視完整大小的影像

完成精靈之後,Visual Studio 會為 GridView 產生兩個 BoundFields,以顯示 ProductName 從資料庫傳回的 和 UnitPrice 字段。 此時,主版頁面的宣告式標記應該包含類似下列的標記:

<asp:Label ID="GridMessage" runat="server" EnableViewState="false"></asp:Label>
<asp:GridView ID="RecentProducts" runat="server" AutoGenerateColumns="False"
 DataSourceID="RecentProductsDataSource">
 <Columns> 
 <asp:BoundField DataField="ProductName" HeaderText="ProductName" 
 SortExpression="ProductName"/> 
 <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
 SortExpression="UnitPrice"/> 
 </Columns> 
</asp:GridView> 

<asp:SqlDataSource ID="RecentProductsDataSource" runat="server" 
 ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" 
 SelectCommand="SELECT TOP 5 ProductName, UnitPrice FROM Products ORDER BY ProductID DESC"> 
</asp:SqlDataSource>

如您所見,標記包含:卷標 Web 控件(GridMessage)、具有兩個 BoundFields 的 GridView RecentProducts,以及傳回五個最近新增產品的 SqlDataSource 控件。

建立此 GridView 並設定其 SqlDataSource 控件之後,請透過瀏覽器瀏覽網站。 如圖 4 所示,您會在左下角看到一個網格線,其中列出五個最近新增的產品。

GridView 會顯示五個最近新增的產品

圖 04:GridView 顯示五個最近新增的產品(按兩下以檢視完整大小的影像

注意

您可以隨意清除 GridView 的外觀。 某些建議包括將顯示 UnitPrice 的值格式化為貨幣,以及使用背景色彩和字型來改善網格線的外觀。

步驟 2:建立內容頁面以新增產品

下一個工作是建立內容頁面,讓使用者可以從中將新產品新增至 Products 數據表。 將新的內容頁面新增至 Admin 名為 AddProduct.aspx的資料夾,請務必將它系結至 Site.master 主版頁面。 圖 5 顯示此頁面新增至網站之後的 方案總管。

將新的 ASP.NET 頁面新增至管理資料夾

圖 05:將新的 ASP.NET 頁面新增至 Admin 資料夾(按一下以檢視完整大小的影像

回想一下,在主版頁面教學課程中指定標題、中繼標記和其他 HTML 標頭中,我們建立了名為 BasePage 的自定義基頁類別,如果未明確設定,就會產生頁面標題。 移至 AddProduct.aspx 頁面的程式代碼後置類別,並讓它衍生自 BasePage (而不是 衍生自 System.Web.UI.Page)。

最後,更新 Web.sitemap 檔案以包含本課程的專案。 針對控件識別碼命名問題課程,在下方 <siteMapNode> 新增下列標記:

<siteMapNode url="~/Admin/AddProduct.aspx" title="Content to Master Page Interaction" />

如圖 6 所示,此元素的 <siteMapNode> 新增會反映在 Lessons 清單中。

AddProduct.aspx傳回 。 在 ContentPlaceHolder 的內容控制項中 MainContent ,新增 DetailsView 控制項並將其命名為 NewProduct。 將 DetailsView 系結至名為 NewProductDataSource的新 SqlDataSource 控件。 如同步驟 1 中的 SqlDataSource,請設定精靈,讓它使用 Northwind 資料庫並選擇指定自定義 SQL 語句。 由於 DetailsView 將用來將專案新增至資料庫,因此我們必須同時指定 SELECT 語句和 INSERT 語句。 使用下列 SELECT 查詢:

SELECT ProductName, UnitPrice FROM Products

然後,從 [INSERT] 索引標籤新增下列 INSERT 語句:

INSERT INTO Products(ProductName, UnitPrice) VALUES(@ProductName, @UnitPrice)

完成精靈之後,請移至 DetailsView 的智慧標記,然後核取 [啟用插入] 複選框。 這會將 CommandField 新增至 DetailsView,其 ShowInsertButton 屬性設定為 true。 由於此 DetailsView 將單獨用於插入資料,請將 DetailsView 的 DefaultMode 屬性設定為 Insert

這樣就全部完成了! 讓我們測試此頁面。 瀏覽 AddProduct.aspx 瀏覽器,輸入名稱和價格(請參閱圖 6)。

將新產品新增至資料庫

圖 06:將新產品新增至資料庫 (按一下以檢視完整大小的影像

輸入新產品的名稱和價格之後,按下 [插入] 按鈕。 這會導致窗體回傳。 在回傳時,會執行 SqlDataSource 控件的 INSERT 語句;其兩個參數會在 DetailsView 的兩個 TextBox 控件中填入使用者輸入的值。 不幸的是,沒有發生插入的視覺回饋。 最好顯示訊息,確認已新增記錄。 我把這作為讀者的練習。 此外,在從 DetailsView 新增新記錄之後,主版頁面中的 GridView 仍會顯示與之前相同的五筆記錄:它不包含剛加入的記錄。 我們將在後續步驟中檢查如何解決這個問題。

注意

除了新增插入成功的某種形式的視覺回饋之外,我也會鼓勵您更新 DetailsView 的插入介面以包含驗證。 目前沒有驗證。 如果使用者為欄位輸入無效的值 UnitPrice ,例如「太昂貴」,當系統嘗試將該字串轉換成十進位時,會在回傳時擲回例外狀況。 如需自定義插入介面的詳細資訊,請參閱使用數據教學課程系列中的自定義數據修改介面教學課程。

步驟 3:在主版頁面中建立公用屬性和方法

在步驟 1 中,我們在主版頁面的 GridView 上方新增了名為 GridMessage 的標籤 Web 控制件。 此標籤是選擇性地顯示訊息。 例如,在將新記錄新增至 Products 數據表之後,我們可能會想要顯示一則訊息,指出「ProductName 已新增至資料庫」。我們可能希望郵件可由內容頁面自定義,而不是將主版頁面中此標籤的文字硬式編碼。

因為 Label 控制件會實作為主版頁面中受保護的成員變數,所以無法直接從內容頁面存取該控制件。 若要在主版頁面中使用主版頁面內的標籤(或者,就此事而言,主版頁面中的任何 Web 控件),我們需要在主版頁面中建立公開 Web 控件的公用屬性,或做為其其中一個屬性可以存取的 Proxy。 將下列語法新增至主版頁面的程式代碼後置類別,以公開 Label 的 Text 屬性:

public string GridMessageText 
{ 
    get
    { 
        return GridMessage.Text; 
    } 
    set 
    {
        GridMessage.Text = value; 
    }
}

從內容頁面將新記錄新增至 Products 數據表時, RecentProducts 主版頁面中的 GridView 必須重新繫結至其基礎數據源。 若要重新系結 GridView,請呼叫其 DataBind 方法。 由於主版頁面中的 GridView 無法以程式設計方式存取內容頁面,因此我們需要在主版頁面中建立公用方法,呼叫時將數據重新繫結至 GridView。 將下列方法新增至主版頁面的程式代碼後置類別:

public void RefreshRecentProductsGrid() 
{ 
    RecentProducts.DataBind();
}

GridMessageText有了 屬性和 RefreshRecentProductsGrid 方法,任何內容頁面都可以以程式設計方式設定或讀取 Label Text 屬性的值GridMessage,或將數據重新系結至 RecentProducts GridView。 步驟 4 會檢查如何從內容頁面存取主版頁面的公用屬性和方法。

注意

別忘了將主版頁面的屬性和方法標示為 public。 如果您未明確表示這些屬性和方法為 public,則無法從內容頁面存取這些屬性和方法。

步驟 4:從內容頁面呼叫主版頁面的公用成員

現在主版頁面具有必要的公用屬性和方法,我們已經準備好從 AddProduct.aspx 內容頁面叫用這些屬性和方法。 具體而言,我們需要設定主版頁面的 GridMessageText 屬性,並在新產品新增至資料庫之後呼叫其 RefreshRecentProductsGrid 方法。 所有 ASP.NET 數據 Web 控件都會在完成各種工作前後立即引發事件,讓頁面開發人員在工作前後輕鬆採取一些程式設計動作。 例如,當使用者按兩下DetailsView的 [插入] 按鈕時,在回傳時,DetailsView 會在開始插入工作流程之前引發其 ItemInserting 事件。 然後,它會將記錄插入資料庫中。 之後,DetailsView 會引發其 ItemInserted 事件。 因此,若要在新增產品之後使用主版頁面,請建立DetailsView事件的 ItemInserted 事件處理程式。

內容頁面可以透過程式設計方式與其主版頁面進行介面的方式有兩種:

  • Page.Master使用 屬性,傳回主版頁面的鬆散型別參考,或
  • 透過 @MasterType 指示詞指定頁面的主版頁面類型或檔案路徑;這會自動將強型別屬性新增至名為 Master的頁面。

讓我們檢查這兩種方法。

使用鬆散類型Page.Master屬性

所有 ASP.NET 網頁都必須衍生自 Page 位於 命名空間的 System.Web.UI 類別。 類別 Page 包含 Master 屬性,該屬性 會傳回頁面主版頁面的參考。 如果頁面沒有主版頁面 Master 會傳 null回 。

屬性 Master 會傳回型別 MasterPage 的物件(也位於 System.Web.UI 命名空間中),這是所有主版頁面衍生來源的基底類型。 因此,若要使用網站主版頁面中定義的公用屬性或方法,我們必須將從 Master 屬性傳回的對象轉換成MasterPage適當的類型。 因為我們將主版頁面檔案 Site.master命名為 ,因此程式代碼後置類別的名稱為 Site。 因此,下列程式代碼會將 Page.Master 屬性轉換成 Site 類別的實例。

// Cast the loosely-typed Page.Master property and then set the GridMessageText property 
Site myMasterPage = Page.Master as Site;

既然我們已經將鬆散類型 Page.Master 屬性轉換成類型, Site 我們可以參考 Site 特有的屬性和方法。 如圖 7 所示,公用屬性 GridMessageText 會出現在 IntelliSense 下拉式清單中。

IntelliSense 顯示主版頁面的公用屬性和方法

圖 07:IntelliSense 顯示主版頁面的公用屬性和方法(按兩下以檢視完整大小的影像

注意

如果您將主版頁面檔案 MasterPage.master 命名為 ,則主版頁面的程式代碼後置類別名稱為 MasterPage。 從類型 System.Web.UI.MasterPage 轉換成類別時,這可能會導致模棱兩可的程序 MasterPage 代碼。 簡言之,您需要完整限定您要轉型的類型,這在使用網站專案模型時可能有點棘手。 我的建議是確定當您建立主版頁面時,將它命名為主版頁面以外的 MasterPage.master 專案,甚至更好,請建立主版頁面的強型別參考。

使用@MasterType指示詞建立強型別參考

如果您仔細查看,您可以看到 ASP.NET 頁面的程式代碼後置類別是部分類別(請注意 partial 類別定義中的 關鍵詞)。 部分類別是在 C# 和 Visual Basic with.NET Framework 2.0 中引進的,而且簡言之,允許跨多個檔案定義類別的成員。 程式代碼後置類別檔案 - AddProduct.aspx.cs例如 - 包含我們頁面開發人員所建立的程式代碼。 除了我們的程式代碼之外,ASP.NET 引擎會自動建立具有屬性和事件處理程式的個別類別檔案,以將宣告式標記轉譯成頁面的類別階層。

每當流覽 ASP.NET 頁面時,就會發生的自動程式代碼產生,為一些相當有趣且實用的可能性鋪平了道路。 在主版頁面的情況下,如果我們告訴 ASP.NET 引擎內容頁面正在使用哪個主版頁面,它就會為我們產生強型別 Master 屬性。

@MasterType使用指示詞來通知 ASP.NET 引擎內容頁面的主版頁面類型。 指示 @MasterType 詞可以接受主版頁面的類型名稱或其檔案路徑。 若要指定 AddProduct.aspx 頁面作為 Site.master 主版頁面,請將下列指示詞新增至 頂端 AddProduct.aspx

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

此指示詞會指示 ASP.NET 引擎透過名為 Master的屬性,將強型別參考新增至主版頁面。 @MasterType有了 指示詞,我們可以直接透過 屬性呼叫Site.master主版頁面的公用屬性和方法,Master而不需要任何轉換。

注意

如果您省略 @MasterType 指示詞,則語法 Page.MasterMaster 傳回相同的內容:將鬆散類型的對象傳回至頁面的主版頁面。 如果您包含 @MasterType 指示詞,則會 Master 傳回指定主版頁面的強型別參考。 Page.Master不過,仍然會傳回鬆散類型的參考。 如需更徹底的了解為什麼這是案例,以及如何Master在包含 指示詞時@MasterType建構 屬性,請參閱 K. Scott Allen 的部落格文章@MasterType,ASP.NET 2.0。

新增產品之後更新主版頁面

既然我們已瞭解如何從內容頁面叫用主版頁面的公用屬性和方法,我們即可更新 AddProduct.aspx 頁面,以便在新增產品之後重新整理主版頁面。 在步驟 4 開始時,我們建立了 DetailsView 控件事件的 ItemInserting 事件處理程式,該事件處理程式會在新產品新增至資料庫之後立即執行。 將下列程式代碼新增至該事件處理程式:

protected void NewProduct_ItemInserted(object sender, DetailsViewInsertedEventArgs e) 
{ 
    // Cast the loosely-typed Page.Master property and then set the GridMessageText property 
    Site myMasterPage = Page.Master as Site; 
    myMasterPage.GridMessageText = string.Format("{0} added to grid...", e.Values["ProductName"]); 
    // Use the strongly-typed Master property 
    Master.RefreshRecentProductsGrid();
}

上述程式代碼會同時使用鬆散型 Page.Master 別屬性和強型別 Master 屬性。 請注意,屬性 GridMessageText 設定為 “ProductName added to grid...”剛加入的產品值可透過 e.Values 集合存取;如您所見,Just-added ProductName 值是透過 e.Values["ProductName"]存取。

圖 8 顯示 AddProduct.aspx 新產品 - 斯科特的 Soda - 已新增至資料庫之後的頁面。 請注意,主版頁面的 Label 中會註明剛新增的產品名稱,而且 GridView 已重新整理以包含產品及其價格。

主版頁面的標籤和 GridView 顯示剛新增的產品

圖 08:主版頁面的標籤和 GridView 顯示 Just-Added 產品(單擊以檢視完整大小的影像

摘要

在理想情況下,主版頁面及其內容頁面彼此完全分開,不需要任何層級的互動。 雖然主版頁面和內容頁面的設計應考慮到該目標,但有一些常見的案例,其中內容頁面必須與其主版頁面介面。 其中一個最常見的原因是,根據內容頁面中發生的一些動作,更新主版頁面顯示的特定部分。

好消息是,讓內容頁面以程序設計方式與其主版頁面互動相當簡單。 首先,在主版頁面中建立公用屬性或方法,以封裝內容頁面需要叫用的功能。 然後,在內容頁面中,透過鬆散類型 Page.Master 屬性存取主版頁面的屬性和方法,或使用 @MasterType 指示詞來建立主版頁面的強型別參考。

在下一個教學課程中,我們會檢查如何讓主版頁面以程序設計方式與其其中一個內容頁面互動。

快樂的程序設計!

深入閱讀

如需本教學課程中所討論主題的詳細資訊,請參閱下列資源:

關於作者

斯科特·米切爾,多個 ASP/ASP.NET 書籍的作者,4GuysFromRolla.com 的創始人,自1998年以來一直與Microsoft Web 技術合作。 斯科特擔任獨立顧問、教練和作家。 他的最新書是 山姆斯在24小時內 ASP.NET 3.5。 斯科特可以透過 mitchell@4GuysFromRolla.com 他在的部落格聯繫到或通過他的博客 http://ScottOnWriting.NET

特別感謝

本教學課程系列已由許多實用的檢閱者檢閱。 本教學課程的主要檢閱者是 Zack Jones。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果是,請將一行放在 mitchell@4GuysFromRolla.com