(转) ASP.NET 2.0:使用用户控件和定制的Web部件个人化你的门户网站(二)
生活随笔
收集整理的這篇文章主要介紹了
(转) ASP.NET 2.0:使用用户控件和定制的Web部件个人化你的门户网站(二)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Web部件目錄
我們已經見過了如何在 WebPartZones 控件中事先放入Web部件。你還可以用另外一種方法完成這個功能,那就是允許用戶在運行時添加新的Web部件。通過使用 CatalogZone 控件和 CatalogParts 類型的部件,比如 :PageCatalogPart 和 DeclarativeCatalogPart 來達到這個目的。(參見圖8)
當你用這個方法添加某個 CatalogZone 和 CatalogPart 之后,用戶就可以在運行時動態添加Web部件了,界面就像如圖9中所示的那樣。
圖9:CatalogZones允許用戶動態添加Web部件
首先,你需要理解 PageCatalogPart 的用途。在設計顯示模式或者編輯顯示模式下,用戶能調用Web部件的“關閉”命令。當用戶關閉 某個Web部件時,Web部件和相對應的個人化或者自定義配置被保留下來,以便用戶可以在以后再次添加該Web部件。因此,PageCatalogPart 控件顯示所有已經被關閉的Web部件的列表,用戶可以再次加到頁面上去。
“關閉”命令與“刪除”命令是不同的。“刪除”命令在出現在編輯顯示模式。當用戶刪除一個Web部件時,有關這個部件的所有相關信息,包括自定義和個人化數據,全部會被刪除掉。
DeclarativeCatalogPart 部件能夠以聲明的方式添加Web部件。圖8中的代碼說明了怎樣定義這個目錄,其中使用了一個自定義名字的部件 WeatherWebPart。采用這種方法,你就能夠給用戶提供各種各樣的Web部件了。
作為本文的補充,同時為了給你提供更多的有關建立Web部件頁面的詳細信息,我們建議大家去閱讀 Stephen Walther 所寫的“Introducing the ASP.NET 2.0 Web Parts Framework”一文 。文中提供了更詳細的信息,以及使用 EditorZones 和 CatalogZones 建立Web控件頁面的完整例子。Stephen 還講了一些更高級的話題,包括 Verbs、Connections 和 Web 部件的導入導出等內容。
ASP.NET 2.0 門戶應用開發
除了Web部件自身體系結構之外,在ASP.NET 2.0里面還有一些新的特性使得內部門戶網站的開發更加有吸引力。正如文章前面所說,主題和換膚的引入使得風格屬性可以方便直接地獨立于門戶頁面,不用修改每個頁面,就可以做 統一的風格改變。當然,更令人興奮的是母版頁(Master Pages)的引入。使用母版頁,就可以將總體的 WebPartManager 控件和所有 WebPartZones 控件放在一個單獨的模版頁面,其它頁面都可以繼承這些基礎外觀和功能。在示例門戶應用中,我們用到的一個有趣的技術就是在 母版頁每個 WebPartZone 控件的ZoneTemplate 模版中都添加一個 ContentPlaceholder 控件。這樣的話,使用這個 母版頁的內容頁面可以使用這個內容控件來添加其自己的Web部件,并且映射到相應的 ContentPlaceholder 控件中。
為一個門戶網站設計母版頁時,你必須考慮的一件事是如果給用戶提供自定義特性。正如你已經看到的,根據頁面中包含的不同類型的區控件,有幾種不同的修改頁面的自定義模式 供讓用戶選擇。
其中一種給用戶顯示自定義選項的方法是將 WebPartManager 控件和一系列按鈕(典型的是LinkButton)封裝為一個用戶控件,然后將這個控件放在 母版頁中,就可以為網站中所有頁面提供自定義選項。如果你的網站有多于一個的主控頁面,用來給不同的頁面提供不同的布局,封裝為一個用戶控件(我們可以叫 它WebPartManagerPanel)也是很有用的。之前圖1中給出的那個門戶應用的菜單條就給出了一個示例的用戶控件,可以顯示出當前 WebPartManager 的顯示模式和范圍,還提供了一些 LinkButton 將頁面改變 為 WebPartManager 控件支持的其中一種編輯模式。(這就是我們示例門戶應用使用的用戶控件)。
在你的 WebPartManagerPanel 控件中可以提供的另外一個有用的特性是可以根據當前用戶和當前頁面顯示或者隱藏相應的顯示模式菜單項。通過查看 WebPartManager 的 SupportedDisplayModes 這個collection屬性中包含哪些支持的顯示模式,就可以顯示或隱藏相應的顯示模式菜單項。例如,要找出當前頁面是否支持CatalogDisplayMode,你應該寫如下的代碼:
if (WebPartManager1.SupportedDisplayModes.Contains(
WebPartManager.CatalogDisplayMode)) {
//enable catalog display mode LinkButton here...
}
還應該注意,如果當前用戶沒有相應的權限,調用 ToggleScope 將會失敗。所以通過代碼判斷一下是否顯示或隱藏讓用戶進入共享范圍的界面元素是個好主意,查詢 WebPartManager 控件的 Personalization 屬性的 CanEnterSharedScope 屬性可以做到這件事。代碼如下:
if (WebPartManager1.Personalization.CanEnterSharedScope) {
// display UI element that allows user to enter shared scope
}
本文附帶的示例應用中的 WebPartManagerPanel 用戶控件包含了一個完整的實現,它可以根據當前用戶和當前頁面的能力動態地調整 窗格的顯示。
將用戶控件作為Web部件
在使用 Windows SharePoint 服務創建Web部件的時候,最令人沮喪的一件事情就是你必須用代碼去創建控件的整個界面,設計器一點幫不了忙。因為 許多Web部件都是一系列互相交互的服務器端控件組成的,在創建Web部件時,不能使用 Visual Studio 的設計器是一件很不幸的事情。一個顯而易見的解決方法就是允許開發人員創建用戶控件,并且可以作為Web部件使用。(一個叫 SmartPart 的第三方工具提供了在 Windows SharePoint服務中可以將用戶控件作為Web部件使用)。
ASP.NET 2.0 Web部件解決了這個問題,它可以允許任何控件直接作為Web部件使用,不用修改或者包裝這些控件。這不僅可以將用戶控件 結合到Web部件集合中,而且還可以輕易地將現有 asp.net 頁面中使用的那些自定義控件集成起來。
這種方法內部的工作原理是,如果一個標準控件(不是Web部件)被加入到 WebPartZone 控件中,系統會隱含地調用 WebPartManager.CreateWebPart 方法,這個方法會創建一個 GenericWebPart 類的實例,并且用 添加的那個控件去初始化這個實例。GenericWebPart 從基類 WebPart 中繼承,提供了核心Web部件屬性實現。當構建 GenericWebPart 控件的時候,它會將初始化的那個控件作為子控件加入。在頁面呈現過程中,就像大多數復合控件那樣,GenericWebPart自身不會在 響應緩存中輸出任何內容,只是作為輸出子控件內容的一個代理。最終結果是你可以在頁面中的 WebPartZone 控件里面加入任何控件,不用擔心它不會運行。例如,下面的頁面定義了一個 WebPartZone 控件,里面包括一個用戶控件和一個標準日歷控件,在創建的時候,這兩個控件都會被隱含地包裝成為一個 GenericWebPart 類的控件。代碼如下:
<%@ Register Src="webparts/CustomerList.ascx"
TagName="CustomerList" TagPrefix="Wingtip" %>
<asp:WebPartManager ID=" WebPartManager1" runat="server" />
<asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
<ZoneTemplate>
<Wingtip:CustomerList runat="server" id="CustomerList" />
<asp:Calendar runat="server" id="CustomerCalendar" />
</ZoneTemplate>
</asp:WebPartZone>
和標準的Web部件一樣,動態創建被 GenericWebPart 包裝的控件也是可以的。如果是用戶控件,首先,你必須調用 Page.LoadControl 來動態地載入和創建用戶控件實例。其次,還必須顯式地給這個控件設置一個唯一的ID。再者,你還必須調用 WebPartManager 對象的 CreateWebPart 方法去創建一個 GenericWebPart 類的實例來作為用戶控件實例的包裝。最后,將獲得的 GenericWebPart 實例的引用作為參數傳給 AddWebPart 方法,并且指定要加入的WebPartZone。代碼如下:
// create Web Part instance from User Control file
Control uc = this.LoadControl(@"webparts\CompanyNews.ascx");
uc.ID = "wp2";
GenericWebPart wp2 = WebPartManager1.CreateWebPart(uc);
WebPartManager1.AddWebPart(wp2, WebPartZone1, 1);
這種技術的唯一缺點就是你無法控制Web部件的一些專用特性,因為你的控件不是從 WebPart 類繼承的,而只有 GenericWebPart 是從 WebPart 類繼承的。一旦你運行擁有由 GenericWebPart 包裝的控件的頁面,你馬上就會很明顯地發現一個現象,不想大多數Web部件,這些Web部件默認是無標題的,而且也沒有相關的圖標和描述信息。圖10給出了一個 由 GenericeWebPart 控件包裝的帶有默認標題(無標題)和圖標的示例用戶控件。
圖10 GenericWebPart
其中一種解決方法是,在你的用戶控件中,增加一個Init事件處理例程。如果你的控件由 GenericWebPart包裝(通過查詢 Parent 屬性的類型可以判斷),你就應該在程序中設置 GenericWebPart 類的一些屬性,代碼如下:
void Page_Init(object src, EventArgs e) {
GenericWebPart gwp = Parent as GenericWebPart;
if (gwp != null) {
gwp.Title = "My custom user control";
gwp.TitleIconImageUrl = @"~\img\ALLUSR.GIF";
gwp.CatalogIconImageUrl = @"~\img\ALLUSR.GIF";
}
}
當你再次運行此頁面時,一旦用戶控件被 GenericWebPart 包裝,對 GenericeWebPart 父控件的屬性的修改會反映在包含你的控件的Web部件上。圖11給出了新的設置過屬性的用戶控件,請注意標題和圖標。
圖11 標題和圖標
另外一個更有吸引力的解決方案是直接在你的用戶控件類里實現 IWebPart 接口。既然用戶控件從來不直接查詢Web部件的屬性,因為那些信息是由 GenericWebPart 類處理的,這樣做初看起來好像沒什么幫助。幸運的是,GenericWebPart 類的設計者意識到這種需求,如果控件實現了 IWebPart 接口, 那么在 GenericeWebPart 類中實現屬性就會自動委托所包裝的控件。
所以定制某個用戶控件的Web部件特性僅僅是實現 IWebPart 接口,并填充接口中定義的七個屬性就可以了。圖12中的代碼給出了用戶控件的 后臺代碼類的一個例子,實現了和之前我們動態修改 GenericWebPart 屬性一樣的結果。
你可能還會考慮給你的用戶控件建立一個另外的基類,這個基類從 UserControl 繼承,并且實現了IWebPart接口,然后就可以被你的門戶應用中所有的用戶控件所繼承。我們在這篇文章的示例應用中就是這么做的。采用這種方法,你的用戶控件就能在 它們的構造函數中初始化其所需的屬性,其它的就由基類去控制了。圖13給出了一個實現 IWebPart 接口的用戶控件基類以及一個與之相對應的后臺類的代碼, 該用戶控件使用這個基類設置標題和圖標屬性。
現在你擁有了創建用戶控件的這么多的靈活性,你可能會問:當你擁有設計器支持的用戶控件,同時還可以定制Web部件特性,那為什么還要創建自己自定義 的控件呢?實際上,有幾個原因需要你這樣做。其中一個原因是你不能給用戶控件添加定義的動作(verbs)。如果需要那樣做,你必須直接從 WebPart 繼承,然后重寫Verbs屬性。當然,你也可以考慮在你的控件中實現 IWebEditable 接口。
另外一個原因是用戶控件局限于應用程序的目錄,除非你將.ascx文件從一個項目復制到另外一個項目的目錄下,否則你不可能在多個Web應用程序中共享用戶控件。另一方面,自定義Web部件類繼承自 WebPart 類,能夠被編譯到一個可重用的dll里面, 并且部署到全局程序集緩存(GAC)。還有一點,通過自定義Web部件類,你還可以給你的控件寫一個自定義的設計器,以改變在 Visual Studio 中默認的外觀,而且你還可以在這個Web部件類被放在工具箱的時候,創建一個圖標。圖14提供了一個特性對比表,讓你決定是選擇自定義Web部件還是用戶控件。
Web部件和個人化特性提供者程序
提供者程序是ASP.NET 2.0的一個新特性,這也是你能在這個版本中看到如此之多內置的功能完整的控件只需要很少的甚至不需要任何代碼就能運行 的一個主要原因。提供者程序背后的基本思路是為某個特定的特性定義一套公共的與數據相關的任務,將那些任務聚集到一個抽象類聲明中,該抽象類從公共的 ProviderBase 類繼承。在本文探討的個人化特性中,必須明確提供的數據相關任務包括:
為某個特定頁面和用戶保存Web部件的屬性和布局;
為某個特定頁面和用戶裝載Web部件的屬性和布局;
保存常規Web部件屬性和特定的頁面布局( 用于常規定制);
加載常規Web部件屬性和特定的頁面布局(用于常規定制);
將某個特定頁面和用戶的Web部件屬性和布局重置為其默認值 ;
將某個特定頁面的Web部件屬性和布局重置為其默認值(用于常規定制);
還有其它的一些屬于個人化體系結構的附屬特性也需要持久化存儲的能力,但是基本上可以歸結為以上六種需求。如果我們假設有一個類可以完成這六個動作,而且能夠成功 地保存和恢復數據,那么當站點運行的時候,每個頁面上的 WebPartManager 控件就能夠使用那個類保存和恢復所有的個人化和自定義數據。定義這些方法的抽象類的名字叫 PersonalizationProvider 類, 默認情況下使用的一個具體的派生類是 SqlPersonalizationProvider 類。圖15顯示了代表我們定義的 六個功能的那三個方法。請注意,不論輸入的 userName 參數是否為空,每個方法都能夠完成用戶個人化或者共享自定義數據的功能。
所有的個人化數據都保存為普通的二進制數據(byte[]),默認的 SqlPersonalizationProvider 類會將這些數據寫入數據庫中的一個image類型的字段。既然 ASP.NET 2.0 知道有一個類可以提供這些方法,它就能夠在基礎的控件集里面建立比以前更加多的邏輯。在我們的 案例中,每個使用Web部件的頁面上的 WebPartManager類負責正確地調用當前的 PersonalizationProvider 類來序列化和恢復每個頁面的個人化設置。圖16展示了 EditorZone 控件與默認的 SqlPersonalizationProvider 類是如何交互的。
圖16 交互
你使用 ASP.NET 2.0 越多,對該提供者架構的例子了解就會越多。比如其中有成員提供者、角色管理提供者、站點地圖提供者、站點監控提供者等等很多的提供者,所有的提供者程序都定義了一個相似的核心方法集與控件交互。
修改個人化數據存儲
和大多數ASP.NET 2.0中的提供者程序一樣,默認的個人化提供者程序是面向 SQL Server 后臺存儲而實現的。如果不修改配置文件,默認的 SqlPersonalizationProvider 采用 SQL Server 2005 Express Edition 連接字符串,支持基于本地文件的數據庫。這個連接字符串就像下面這樣:
data source=.\SQLEXPRESS; Integrated Security=SSPI;
AttachDBFilename=|DataDirectory|aspnetdb.mdf; User Instance=true
使用 SQL Server 2005 Express Edition 基于文件的數據庫的一個優勢就是它可以被動態創建,不需要用戶任何附加的設置。這意味著你可以建立一個全新的站點,不用設置數據庫就能啟用個人化特性,也能夠運行!當你最初與網站交互時,系統會在站點的 App_Data 目錄中生成一個新的 aspnetdb.mdf 文件,并且用支持所有默認提供者程序所需的表和存儲過程來初始化該數據庫。
對于不需要擴展規模或者支持很多并發用戶的小站點來說,這簡直太好了。但是對于企業系統來說,需要將數據存儲到某個被全面管理的、專用的數據庫服務器上。幸運的是,修改 SqlPersonalizationProvider 使用的數據庫是非常簡單直接的。SqlPersonalizationProvider 的配置將連接字符串初始化為 LocalSqlServer,這意味著它會在配置文件的<connectionStrings>節中尋找名字為 LocalSqlServer 的配置項,使用相關的連接字符串去打開到數據庫的連接。默認情況下,這個字符串就是你在前文所看到的,意味著它會寫入一個本地的 SQL Server 2005 Express Edition .mdf 文件。要修改它,你必須首先清除掉 LocalSqlServer 連接字符串集合,在你的 Web.config 文件中重新設置一個新的連接字符串值。(或者你也可以修改機器范圍的 Machine.config文件,去影響這臺機器上的所有站點)以下是一個 Web.config 文件的例子,它將提供者數據庫的值修改為指向一個本地的 SQL Server 2000 的實例:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/
V.2.0">
<connectionStrings>
<clear />
<add name="LocalSqlServer" connectionString=
"server=.;integrated security=sspi;database=aspnetdb"/>
</connectionStrings>
...
</configuration>
在修改生效之前,本地 SQL 服務器上必須有一個名為 aspnetdb 的數據庫,里面有SqlPersonalizationProvider 所需的表和存儲過程。隨 ASP.NET 2.0 發行了一個名為 aspnet_regsql.exe 的工具,用它可以建立這個數據庫。當以默認設置運行該程序時,它將創建一個名為 aspnetdb 的本地數據庫,其中有所有提供者程序所必需的表和存儲過程,或者你可以選擇將這些表和存儲過程安裝到一個已有的數據庫中。所有的表和存儲過程的名字都會以“aspnet”開頭,所以不太可能會與任何現有的表重復。
和所有 ASP.NET 2.0 的提供者程序一樣,這種間接的模式提供了一種非常靈活的架構,使得不用修改任何頁面或者Web部件,就可以將后端的數據存儲完全替換掉。
創建自己的個人化提供者程序
為個人化提供者程序修改連接字符串的能力賦予了你一定程度的靈活性,但是在 SqlPersonalizationProvider 內部還是使用命名空間 System.Data.Sql.Client 的功能來存取數據。這意味著你必須使用 SqlServer 數據庫。如果你需要將個人化數據保存到另外一種數據庫中,或者可能是另外一種完全不同的數據存儲中,你將不得不更進一步,創建你自己定制的個人化數據提供 者。幸運的是,大多數困難的工作已經為你做好了,而且也易于使用。作為個人化數據存儲到另外一種數據存儲的例子,本文的示例門戶網站有一個自定義提供者程 序的完整實現,名為 FileBasedPersonalizationProvider 類,它將所有的個人化和自定義數據保存到應用程序 App_Data 目錄下的一個本地二進制文件中。這個二進制文件的名稱為每個用戶和路徑唯一生成,每個路徑下還有一個唯一的通用的用戶配置文件。
創建一個自定義的個人化數據提供者,你必須首先建立一個從 PersonalizationProvider 基類繼承的新的類,然后重寫所有從基類繼承的抽象方法。圖17中給出的類定義演示了如何做到這一點。
為了使你的提供者程序能夠運作,其實只有兩個重要的方法必須要實現:LoadPersonalizationBlobs 和 SavePersonalizationBlob。這兩個方法完成了個人化數據的二進制序列化功能,當加載頁面時,個人化架構會調用它們。當Web部件頁 面處于編輯、目錄或者設計模式時,如果數據被修改了,個人化架構還會調用它們將數據寫回。(典型情況下是基于一個特定的用戶)。
在下載的示例代碼中,SavePersonalizationBlob 的實現代碼將 dataBlob 參數寫入一個基于傳入的用戶名稱和路徑唯一命名的文件。相似地,LoadPersonalizationBlobs 的實現代碼會查找這個文件(使用相同的命名方法),并返回一個用戶個人化的或者共享的blob數據。如果傳入的userName參數為空,這兩個方法默認都會保存或者裝載共享數據,如果不為空,就會保存或者裝載用戶個人化數據。圖18給出了示例 FileBasedPersonalizationProvider 中這兩個方法的實現代碼,以及一對用來根據用戶名和路徑信息生成唯一文件名的 helper 方法。
一旦提供者程序完全實現了,通過個人化配置節中的提供者節,你就可以將這個程序注冊為一個提供者程序。要想實際使用它,你必須在 Web.config 文件中定義它為默認的個人化提供者。下面是一個將我們自定義的基于文件的提供者程序作為默認提供者的例子:
<webParts>
<personalization defaultProvider="FileBasedPersonalizationProvider">
<providers>
<add name="FileBasedPersonalizationProvider"
type="Wingtip.Providers.FileBasedPersonalizationProvider" />
</providers>
</personalization>
</webParts>
如果我們重新運行我們的網站,所有的個人化數據現在都會被存儲到一個本地的二進制文件。顯然這不是最好的解決方法,但是示例代碼提供了一些思路,讓你 了解如何實現你自己的個人化提供者,不論你想基于什么樣的后端存儲都可以。圖19給出了我們新的提供者是如何插入到整個的Web部件體系結構中去的。
圖19 使用 FileBasedPersonalizationProvider
更進一步
到現在為止,你已經看到了如果使用 ASP.NET 2.0 和其新的Web部件控件來創建具有豐富特性的支持自定義和個人化的門戶應用程序,而 ASP.NET 2.0 使得這一工作變得相當簡單。也許這個架構的最重要的特性是可插件的能力。提供者架構使得將符合你的站點特性的個人化數據寫入后端數據存儲變得相對簡單,而且也不會被綁定到一個特定的序列化實現和數據存儲上。所以,現在就前進,大膽地使用ASP.NET 2.0 Web部件去創建可以自定義的站點吧!
如果你喜歡這篇文章,在ASP.NET 2.0門戶應用中創建Web部件還有大量的東西需要學習。記得從MSDN雜志網站下載本文的示例代碼。在 ASP.NET 2.0 QuickStart tutorials 中也有一些例子可以拿來研究。我們還推薦你去看看 Fredrik Normén的網志,里面有一些有關 ASP.NET 2.0 Web 部件的有趣的例子。
我們已經見過了如何在 WebPartZones 控件中事先放入Web部件。你還可以用另外一種方法完成這個功能,那就是允許用戶在運行時添加新的Web部件。通過使用 CatalogZone 控件和 CatalogParts 類型的部件,比如 :PageCatalogPart 和 DeclarativeCatalogPart 來達到這個目的。(參見圖8)
當你用這個方法添加某個 CatalogZone 和 CatalogPart 之后,用戶就可以在運行時動態添加Web部件了,界面就像如圖9中所示的那樣。
圖9:CatalogZones允許用戶動態添加Web部件
首先,你需要理解 PageCatalogPart 的用途。在設計顯示模式或者編輯顯示模式下,用戶能調用Web部件的“關閉”命令。當用戶關閉 某個Web部件時,Web部件和相對應的個人化或者自定義配置被保留下來,以便用戶可以在以后再次添加該Web部件。因此,PageCatalogPart 控件顯示所有已經被關閉的Web部件的列表,用戶可以再次加到頁面上去。
“關閉”命令與“刪除”命令是不同的。“刪除”命令在出現在編輯顯示模式。當用戶刪除一個Web部件時,有關這個部件的所有相關信息,包括自定義和個人化數據,全部會被刪除掉。
DeclarativeCatalogPart 部件能夠以聲明的方式添加Web部件。圖8中的代碼說明了怎樣定義這個目錄,其中使用了一個自定義名字的部件 WeatherWebPart。采用這種方法,你就能夠給用戶提供各種各樣的Web部件了。
作為本文的補充,同時為了給你提供更多的有關建立Web部件頁面的詳細信息,我們建議大家去閱讀 Stephen Walther 所寫的“Introducing the ASP.NET 2.0 Web Parts Framework”一文 。文中提供了更詳細的信息,以及使用 EditorZones 和 CatalogZones 建立Web控件頁面的完整例子。Stephen 還講了一些更高級的話題,包括 Verbs、Connections 和 Web 部件的導入導出等內容。
ASP.NET 2.0 門戶應用開發
除了Web部件自身體系結構之外,在ASP.NET 2.0里面還有一些新的特性使得內部門戶網站的開發更加有吸引力。正如文章前面所說,主題和換膚的引入使得風格屬性可以方便直接地獨立于門戶頁面,不用修改每個頁面,就可以做 統一的風格改變。當然,更令人興奮的是母版頁(Master Pages)的引入。使用母版頁,就可以將總體的 WebPartManager 控件和所有 WebPartZones 控件放在一個單獨的模版頁面,其它頁面都可以繼承這些基礎外觀和功能。在示例門戶應用中,我們用到的一個有趣的技術就是在 母版頁每個 WebPartZone 控件的ZoneTemplate 模版中都添加一個 ContentPlaceholder 控件。這樣的話,使用這個 母版頁的內容頁面可以使用這個內容控件來添加其自己的Web部件,并且映射到相應的 ContentPlaceholder 控件中。
為一個門戶網站設計母版頁時,你必須考慮的一件事是如果給用戶提供自定義特性。正如你已經看到的,根據頁面中包含的不同類型的區控件,有幾種不同的修改頁面的自定義模式 供讓用戶選擇。
其中一種給用戶顯示自定義選項的方法是將 WebPartManager 控件和一系列按鈕(典型的是LinkButton)封裝為一個用戶控件,然后將這個控件放在 母版頁中,就可以為網站中所有頁面提供自定義選項。如果你的網站有多于一個的主控頁面,用來給不同的頁面提供不同的布局,封裝為一個用戶控件(我們可以叫 它WebPartManagerPanel)也是很有用的。之前圖1中給出的那個門戶應用的菜單條就給出了一個示例的用戶控件,可以顯示出當前 WebPartManager 的顯示模式和范圍,還提供了一些 LinkButton 將頁面改變 為 WebPartManager 控件支持的其中一種編輯模式。(這就是我們示例門戶應用使用的用戶控件)。
在你的 WebPartManagerPanel 控件中可以提供的另外一個有用的特性是可以根據當前用戶和當前頁面顯示或者隱藏相應的顯示模式菜單項。通過查看 WebPartManager 的 SupportedDisplayModes 這個collection屬性中包含哪些支持的顯示模式,就可以顯示或隱藏相應的顯示模式菜單項。例如,要找出當前頁面是否支持CatalogDisplayMode,你應該寫如下的代碼:
if (WebPartManager1.SupportedDisplayModes.Contains(
WebPartManager.CatalogDisplayMode)) {
//enable catalog display mode LinkButton here...
}
還應該注意,如果當前用戶沒有相應的權限,調用 ToggleScope 將會失敗。所以通過代碼判斷一下是否顯示或隱藏讓用戶進入共享范圍的界面元素是個好主意,查詢 WebPartManager 控件的 Personalization 屬性的 CanEnterSharedScope 屬性可以做到這件事。代碼如下:
if (WebPartManager1.Personalization.CanEnterSharedScope) {
// display UI element that allows user to enter shared scope
}
本文附帶的示例應用中的 WebPartManagerPanel 用戶控件包含了一個完整的實現,它可以根據當前用戶和當前頁面的能力動態地調整 窗格的顯示。
將用戶控件作為Web部件
在使用 Windows SharePoint 服務創建Web部件的時候,最令人沮喪的一件事情就是你必須用代碼去創建控件的整個界面,設計器一點幫不了忙。因為 許多Web部件都是一系列互相交互的服務器端控件組成的,在創建Web部件時,不能使用 Visual Studio 的設計器是一件很不幸的事情。一個顯而易見的解決方法就是允許開發人員創建用戶控件,并且可以作為Web部件使用。(一個叫 SmartPart 的第三方工具提供了在 Windows SharePoint服務中可以將用戶控件作為Web部件使用)。
ASP.NET 2.0 Web部件解決了這個問題,它可以允許任何控件直接作為Web部件使用,不用修改或者包裝這些控件。這不僅可以將用戶控件 結合到Web部件集合中,而且還可以輕易地將現有 asp.net 頁面中使用的那些自定義控件集成起來。
這種方法內部的工作原理是,如果一個標準控件(不是Web部件)被加入到 WebPartZone 控件中,系統會隱含地調用 WebPartManager.CreateWebPart 方法,這個方法會創建一個 GenericWebPart 類的實例,并且用 添加的那個控件去初始化這個實例。GenericWebPart 從基類 WebPart 中繼承,提供了核心Web部件屬性實現。當構建 GenericWebPart 控件的時候,它會將初始化的那個控件作為子控件加入。在頁面呈現過程中,就像大多數復合控件那樣,GenericWebPart自身不會在 響應緩存中輸出任何內容,只是作為輸出子控件內容的一個代理。最終結果是你可以在頁面中的 WebPartZone 控件里面加入任何控件,不用擔心它不會運行。例如,下面的頁面定義了一個 WebPartZone 控件,里面包括一個用戶控件和一個標準日歷控件,在創建的時候,這兩個控件都會被隱含地包裝成為一個 GenericWebPart 類的控件。代碼如下:
<%@ Register Src="webparts/CustomerList.ascx"
TagName="CustomerList" TagPrefix="Wingtip" %>
<asp:WebPartManager ID=" WebPartManager1" runat="server" />
<asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
<ZoneTemplate>
<Wingtip:CustomerList runat="server" id="CustomerList" />
<asp:Calendar runat="server" id="CustomerCalendar" />
</ZoneTemplate>
</asp:WebPartZone>
和標準的Web部件一樣,動態創建被 GenericWebPart 包裝的控件也是可以的。如果是用戶控件,首先,你必須調用 Page.LoadControl 來動態地載入和創建用戶控件實例。其次,還必須顯式地給這個控件設置一個唯一的ID。再者,你還必須調用 WebPartManager 對象的 CreateWebPart 方法去創建一個 GenericWebPart 類的實例來作為用戶控件實例的包裝。最后,將獲得的 GenericWebPart 實例的引用作為參數傳給 AddWebPart 方法,并且指定要加入的WebPartZone。代碼如下:
// create Web Part instance from User Control file
Control uc = this.LoadControl(@"webparts\CompanyNews.ascx");
uc.ID = "wp2";
GenericWebPart wp2 = WebPartManager1.CreateWebPart(uc);
WebPartManager1.AddWebPart(wp2, WebPartZone1, 1);
這種技術的唯一缺點就是你無法控制Web部件的一些專用特性,因為你的控件不是從 WebPart 類繼承的,而只有 GenericWebPart 是從 WebPart 類繼承的。一旦你運行擁有由 GenericWebPart 包裝的控件的頁面,你馬上就會很明顯地發現一個現象,不想大多數Web部件,這些Web部件默認是無標題的,而且也沒有相關的圖標和描述信息。圖10給出了一個 由 GenericeWebPart 控件包裝的帶有默認標題(無標題)和圖標的示例用戶控件。
圖10 GenericWebPart
其中一種解決方法是,在你的用戶控件中,增加一個Init事件處理例程。如果你的控件由 GenericWebPart包裝(通過查詢 Parent 屬性的類型可以判斷),你就應該在程序中設置 GenericWebPart 類的一些屬性,代碼如下:
void Page_Init(object src, EventArgs e) {
GenericWebPart gwp = Parent as GenericWebPart;
if (gwp != null) {
gwp.Title = "My custom user control";
gwp.TitleIconImageUrl = @"~\img\ALLUSR.GIF";
gwp.CatalogIconImageUrl = @"~\img\ALLUSR.GIF";
}
}
當你再次運行此頁面時,一旦用戶控件被 GenericWebPart 包裝,對 GenericeWebPart 父控件的屬性的修改會反映在包含你的控件的Web部件上。圖11給出了新的設置過屬性的用戶控件,請注意標題和圖標。
圖11 標題和圖標
另外一個更有吸引力的解決方案是直接在你的用戶控件類里實現 IWebPart 接口。既然用戶控件從來不直接查詢Web部件的屬性,因為那些信息是由 GenericWebPart 類處理的,這樣做初看起來好像沒什么幫助。幸運的是,GenericWebPart 類的設計者意識到這種需求,如果控件實現了 IWebPart 接口, 那么在 GenericeWebPart 類中實現屬性就會自動委托所包裝的控件。
所以定制某個用戶控件的Web部件特性僅僅是實現 IWebPart 接口,并填充接口中定義的七個屬性就可以了。圖12中的代碼給出了用戶控件的 后臺代碼類的一個例子,實現了和之前我們動態修改 GenericWebPart 屬性一樣的結果。
你可能還會考慮給你的用戶控件建立一個另外的基類,這個基類從 UserControl 繼承,并且實現了IWebPart接口,然后就可以被你的門戶應用中所有的用戶控件所繼承。我們在這篇文章的示例應用中就是這么做的。采用這種方法,你的用戶控件就能在 它們的構造函數中初始化其所需的屬性,其它的就由基類去控制了。圖13給出了一個實現 IWebPart 接口的用戶控件基類以及一個與之相對應的后臺類的代碼, 該用戶控件使用這個基類設置標題和圖標屬性。
現在你擁有了創建用戶控件的這么多的靈活性,你可能會問:當你擁有設計器支持的用戶控件,同時還可以定制Web部件特性,那為什么還要創建自己自定義 的控件呢?實際上,有幾個原因需要你這樣做。其中一個原因是你不能給用戶控件添加定義的動作(verbs)。如果需要那樣做,你必須直接從 WebPart 繼承,然后重寫Verbs屬性。當然,你也可以考慮在你的控件中實現 IWebEditable 接口。
另外一個原因是用戶控件局限于應用程序的目錄,除非你將.ascx文件從一個項目復制到另外一個項目的目錄下,否則你不可能在多個Web應用程序中共享用戶控件。另一方面,自定義Web部件類繼承自 WebPart 類,能夠被編譯到一個可重用的dll里面, 并且部署到全局程序集緩存(GAC)。還有一點,通過自定義Web部件類,你還可以給你的控件寫一個自定義的設計器,以改變在 Visual Studio 中默認的外觀,而且你還可以在這個Web部件類被放在工具箱的時候,創建一個圖標。圖14提供了一個特性對比表,讓你決定是選擇自定義Web部件還是用戶控件。
Web部件和個人化特性提供者程序
提供者程序是ASP.NET 2.0的一個新特性,這也是你能在這個版本中看到如此之多內置的功能完整的控件只需要很少的甚至不需要任何代碼就能運行 的一個主要原因。提供者程序背后的基本思路是為某個特定的特性定義一套公共的與數據相關的任務,將那些任務聚集到一個抽象類聲明中,該抽象類從公共的 ProviderBase 類繼承。在本文探討的個人化特性中,必須明確提供的數據相關任務包括:
為某個特定頁面和用戶保存Web部件的屬性和布局;
為某個特定頁面和用戶裝載Web部件的屬性和布局;
保存常規Web部件屬性和特定的頁面布局( 用于常規定制);
加載常規Web部件屬性和特定的頁面布局(用于常規定制);
將某個特定頁面和用戶的Web部件屬性和布局重置為其默認值 ;
將某個特定頁面的Web部件屬性和布局重置為其默認值(用于常規定制);
還有其它的一些屬于個人化體系結構的附屬特性也需要持久化存儲的能力,但是基本上可以歸結為以上六種需求。如果我們假設有一個類可以完成這六個動作,而且能夠成功 地保存和恢復數據,那么當站點運行的時候,每個頁面上的 WebPartManager 控件就能夠使用那個類保存和恢復所有的個人化和自定義數據。定義這些方法的抽象類的名字叫 PersonalizationProvider 類, 默認情況下使用的一個具體的派生類是 SqlPersonalizationProvider 類。圖15顯示了代表我們定義的 六個功能的那三個方法。請注意,不論輸入的 userName 參數是否為空,每個方法都能夠完成用戶個人化或者共享自定義數據的功能。
所有的個人化數據都保存為普通的二進制數據(byte[]),默認的 SqlPersonalizationProvider 類會將這些數據寫入數據庫中的一個image類型的字段。既然 ASP.NET 2.0 知道有一個類可以提供這些方法,它就能夠在基礎的控件集里面建立比以前更加多的邏輯。在我們的 案例中,每個使用Web部件的頁面上的 WebPartManager類負責正確地調用當前的 PersonalizationProvider 類來序列化和恢復每個頁面的個人化設置。圖16展示了 EditorZone 控件與默認的 SqlPersonalizationProvider 類是如何交互的。
圖16 交互
你使用 ASP.NET 2.0 越多,對該提供者架構的例子了解就會越多。比如其中有成員提供者、角色管理提供者、站點地圖提供者、站點監控提供者等等很多的提供者,所有的提供者程序都定義了一個相似的核心方法集與控件交互。
修改個人化數據存儲
和大多數ASP.NET 2.0中的提供者程序一樣,默認的個人化提供者程序是面向 SQL Server 后臺存儲而實現的。如果不修改配置文件,默認的 SqlPersonalizationProvider 采用 SQL Server 2005 Express Edition 連接字符串,支持基于本地文件的數據庫。這個連接字符串就像下面這樣:
data source=.\SQLEXPRESS; Integrated Security=SSPI;
AttachDBFilename=|DataDirectory|aspnetdb.mdf; User Instance=true
使用 SQL Server 2005 Express Edition 基于文件的數據庫的一個優勢就是它可以被動態創建,不需要用戶任何附加的設置。這意味著你可以建立一個全新的站點,不用設置數據庫就能啟用個人化特性,也能夠運行!當你最初與網站交互時,系統會在站點的 App_Data 目錄中生成一個新的 aspnetdb.mdf 文件,并且用支持所有默認提供者程序所需的表和存儲過程來初始化該數據庫。
對于不需要擴展規模或者支持很多并發用戶的小站點來說,這簡直太好了。但是對于企業系統來說,需要將數據存儲到某個被全面管理的、專用的數據庫服務器上。幸運的是,修改 SqlPersonalizationProvider 使用的數據庫是非常簡單直接的。SqlPersonalizationProvider 的配置將連接字符串初始化為 LocalSqlServer,這意味著它會在配置文件的<connectionStrings>節中尋找名字為 LocalSqlServer 的配置項,使用相關的連接字符串去打開到數據庫的連接。默認情況下,這個字符串就是你在前文所看到的,意味著它會寫入一個本地的 SQL Server 2005 Express Edition .mdf 文件。要修改它,你必須首先清除掉 LocalSqlServer 連接字符串集合,在你的 Web.config 文件中重新設置一個新的連接字符串值。(或者你也可以修改機器范圍的 Machine.config文件,去影響這臺機器上的所有站點)以下是一個 Web.config 文件的例子,它將提供者數據庫的值修改為指向一個本地的 SQL Server 2000 的實例:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/
V.2.0">
<connectionStrings>
<clear />
<add name="LocalSqlServer" connectionString=
"server=.;integrated security=sspi;database=aspnetdb"/>
</connectionStrings>
...
</configuration>
在修改生效之前,本地 SQL 服務器上必須有一個名為 aspnetdb 的數據庫,里面有SqlPersonalizationProvider 所需的表和存儲過程。隨 ASP.NET 2.0 發行了一個名為 aspnet_regsql.exe 的工具,用它可以建立這個數據庫。當以默認設置運行該程序時,它將創建一個名為 aspnetdb 的本地數據庫,其中有所有提供者程序所必需的表和存儲過程,或者你可以選擇將這些表和存儲過程安裝到一個已有的數據庫中。所有的表和存儲過程的名字都會以“aspnet”開頭,所以不太可能會與任何現有的表重復。
和所有 ASP.NET 2.0 的提供者程序一樣,這種間接的模式提供了一種非常靈活的架構,使得不用修改任何頁面或者Web部件,就可以將后端的數據存儲完全替換掉。
創建自己的個人化提供者程序
為個人化提供者程序修改連接字符串的能力賦予了你一定程度的靈活性,但是在 SqlPersonalizationProvider 內部還是使用命名空間 System.Data.Sql.Client 的功能來存取數據。這意味著你必須使用 SqlServer 數據庫。如果你需要將個人化數據保存到另外一種數據庫中,或者可能是另外一種完全不同的數據存儲中,你將不得不更進一步,創建你自己定制的個人化數據提供 者。幸運的是,大多數困難的工作已經為你做好了,而且也易于使用。作為個人化數據存儲到另外一種數據存儲的例子,本文的示例門戶網站有一個自定義提供者程 序的完整實現,名為 FileBasedPersonalizationProvider 類,它將所有的個人化和自定義數據保存到應用程序 App_Data 目錄下的一個本地二進制文件中。這個二進制文件的名稱為每個用戶和路徑唯一生成,每個路徑下還有一個唯一的通用的用戶配置文件。
創建一個自定義的個人化數據提供者,你必須首先建立一個從 PersonalizationProvider 基類繼承的新的類,然后重寫所有從基類繼承的抽象方法。圖17中給出的類定義演示了如何做到這一點。
為了使你的提供者程序能夠運作,其實只有兩個重要的方法必須要實現:LoadPersonalizationBlobs 和 SavePersonalizationBlob。這兩個方法完成了個人化數據的二進制序列化功能,當加載頁面時,個人化架構會調用它們。當Web部件頁 面處于編輯、目錄或者設計模式時,如果數據被修改了,個人化架構還會調用它們將數據寫回。(典型情況下是基于一個特定的用戶)。
在下載的示例代碼中,SavePersonalizationBlob 的實現代碼將 dataBlob 參數寫入一個基于傳入的用戶名稱和路徑唯一命名的文件。相似地,LoadPersonalizationBlobs 的實現代碼會查找這個文件(使用相同的命名方法),并返回一個用戶個人化的或者共享的blob數據。如果傳入的userName參數為空,這兩個方法默認都會保存或者裝載共享數據,如果不為空,就會保存或者裝載用戶個人化數據。圖18給出了示例 FileBasedPersonalizationProvider 中這兩個方法的實現代碼,以及一對用來根據用戶名和路徑信息生成唯一文件名的 helper 方法。
一旦提供者程序完全實現了,通過個人化配置節中的提供者節,你就可以將這個程序注冊為一個提供者程序。要想實際使用它,你必須在 Web.config 文件中定義它為默認的個人化提供者。下面是一個將我們自定義的基于文件的提供者程序作為默認提供者的例子:
<webParts>
<personalization defaultProvider="FileBasedPersonalizationProvider">
<providers>
<add name="FileBasedPersonalizationProvider"
type="Wingtip.Providers.FileBasedPersonalizationProvider" />
</providers>
</personalization>
</webParts>
如果我們重新運行我們的網站,所有的個人化數據現在都會被存儲到一個本地的二進制文件。顯然這不是最好的解決方法,但是示例代碼提供了一些思路,讓你 了解如何實現你自己的個人化提供者,不論你想基于什么樣的后端存儲都可以。圖19給出了我們新的提供者是如何插入到整個的Web部件體系結構中去的。
圖19 使用 FileBasedPersonalizationProvider
更進一步
到現在為止,你已經看到了如果使用 ASP.NET 2.0 和其新的Web部件控件來創建具有豐富特性的支持自定義和個人化的門戶應用程序,而 ASP.NET 2.0 使得這一工作變得相當簡單。也許這個架構的最重要的特性是可插件的能力。提供者架構使得將符合你的站點特性的個人化數據寫入后端數據存儲變得相對簡單,而且也不會被綁定到一個特定的序列化實現和數據存儲上。所以,現在就前進,大膽地使用ASP.NET 2.0 Web部件去創建可以自定義的站點吧!
如果你喜歡這篇文章,在ASP.NET 2.0門戶應用中創建Web部件還有大量的東西需要學習。記得從MSDN雜志網站下載本文的示例代碼。在 ASP.NET 2.0 QuickStart tutorials 中也有一些例子可以拿來研究。我們還推薦你去看看 Fredrik Normén的網志,里面有一些有關 ASP.NET 2.0 Web 部件的有趣的例子。
總結
以上是生活随笔為你收集整理的(转) ASP.NET 2.0:使用用户控件和定制的Web部件个人化你的门户网站(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: M0721&nbsp;木色铝合金
- 下一篇: Validation of ViewSt