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

作者:Scott Mitchell

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

簡介

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

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

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

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

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

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

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

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

注意

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

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

因為從內容頁面中事件處理程式重新整理主版頁面的顯示是內容和主版頁面互動最常見的需求之一,因此讓我們更詳細地探索本主題。 本教學課程的下載包含網站資料夾中名為 NORTHWIND.MDFApp_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。 接下來,從 Designer 展開 GridView 的智慧標記,然後選擇將其系結至新的數據源。 這會啟動 [數據源組態精靈]。 由於資料夾中的 Northwind 資料庫App_Data是 Microsoft SQL Server 資料庫,因此選取 [ (],選擇建立 SqlDataSource,請參閱圖 1) ;將 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 會產生兩個 BoundFields,讓 GridView 顯示 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 ;GridView RecentProducts,具有兩個 BoundFields;以及傳回五個最近新增產品的 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 Property GridMessageText() As String 
 Get
 Return GridMessage.Text 
 End Get 
 Set(ByVal Value As String) 
 GridMessage.Text = Value 
 End Set 
End Property

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

Public Sub RefreshRecentProductsGrid() 
 RecentProducts.DataBind()
End Sub

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 會傳 Nothing回 。

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

' Cast the loosely-typed Page.Master property and then set the GridMessageText property 
Dim myMasterPage As Site = CType(Page.Master, Site)

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

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

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

注意

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

使用@MasterType指示詞建立 Strongly-Typed 參考

如果您仔細查看,您可以看到 ASP.NET 頁面的程式代碼後置類別是部分類別, (請注意 Partial 類別定義) 中的 關鍵詞。 部分類別是在 C# 和 Visual Basic with.NET Framework 2.0 中引進的,而且簡單地說,允許跨多個檔案定義類別的成員。 程式代碼後置類別檔案 - AddProduct.aspx.vb例如, 包含我們頁面開發人員所建立的程式代碼。 除了我們的程式代碼之外,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有了 指示詞,我們可以直接透過 Master 屬性呼叫Site.master主版頁面的公用屬性和方法,而不需要任何轉換。

注意

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

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

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

Protected Sub NewProduct_ItemInserted(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewInsertedEventArgs) Handles NewProduct.ItemInserted
 ' Cast the loosely-typed Page.Master property and then set the GridMessageText property 
 Dim myMasterPage As Site = CType(Page.Master, Site) 
 myMasterPage.GridMessageText = String.Format("{0} added to grid...", e.Values("ProductName"))
 ' Use the strongly-typed Master property 
 Master.RefreshRecentProductsGrid()
End Sub

上述程式代碼會同時使用鬆散類型 Page.Master 屬性和強型別 Master 屬性。 請注意, GridMessageText 屬性設定為 「ProductName 已新增至方格...剛加入的產品值可透過 e.Values 集合存取;如您所見,Just-added ProductName 值是透過 存取 e.Values("ProductName")

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

主版頁面的標籤和 GridView 顯示 Just-Added 產品

圖 08:主版頁面的標籤和 GridView 顯示 Just-Added 產品 (按兩下即可檢視全大小影像)

摘要

在理想情況下,主版頁面及其內容頁面彼此完全分開,而且不需要互動層級。 雖然主版頁面和內容頁面應該考慮到該目標,但有一些常見的案例,內容頁面必須與其主版頁面互動。 最常見的原因之一,是以內容頁面中轉譯的一些動作為基礎,更新主版頁面顯示的特定部分。

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

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

快樂的程序設計!

深入閱讀

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

關於作者

Scott Mitchell 是多個 ASP/ASP.NET 書籍的作者,且 4GuysFromRolla.com 的作者,自 1998 年以來,已與 Microsoft Web 技術合作。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 3.5。 您可以在 或 透過在 的http://ScottOnWriting.NET部落格連線mitchell@4GuysFromRolla.com到 Scott。

特別感謝

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