#眉標=SD #副標=Microsoft.NET Design Patterns專欄 #大標=Enterprise Library管理介面的架構與設計 #作者=文/林耀珍 #引言=之前我們曾經介紹過以log4net來幫助軟體開發人員追蹤系統出錯的原因,這次將深入剖析.NET Enterprise Library設計技術,協助讀者開發新的Application block,而且可以從中熟悉Plug-in的設計技巧。 我在前次專欄介紹.Net Enterprise Lib的設計架構,Plug-in是它的重要設計概念,其中的Configuration Application Block(CAB)是整個Enterprise Lib架構的核心,CAB把每個Application block都視為一個plug-in,並且提供一個圖形化管理介面,稱為Configuration Console。如圖1所示,開發人員在設計階段使用Console設定Application block的Configuration,於佈署後,營運管理人員使用Console改變設定時,CAB會通知運行中的Application Block,由Application block自行決定是否要即時採用新的設定值。如果要使自己設計的Application Block被Configuration console管理,則必須了解Configuration Application Block對設計階段的支援與要求,在自訂的Application block中提供必要的資料結構,與實現Console所需要的程式介面。本文將說明Enterprise Library管理介面的架構與設計模型,不但有助於讀者開發新的Application block,而且可以從中熟悉Plug-in的設計技巧。 -------圖-------- 圖1 -------圖-------- #中標=Enterprise Library管理介面 Configuration Console分為二部分,一部分是內部的資料處理,稍後說明,另一部分是UI的展現,我們先從UI開始研究。UI包含上下左右四個部分,左邊的Tree View展示Configurations data結樹狀構,根節點是Solution,每一個solution可以包含多個Application,每一個Application的設定資料放在一個UIHierarchy中。每個Configurations data以一個Tree node顯示,內部則稱為Configuration Node,右邊是VS.NET內建的Property Grid,用來顯示及編輯節點資料。下方則是Validation Error List,用來顯示Configuration Error,自動檢查的功能是GUI介面比text editing好用的地方 ! Console的上方部分是功能表,它和Tree view上的Context menu合起來是Configuration Console的Menu service內容,它會隨著使用者點選不同種類的節點,動態調整正確的功能表清單,防止使用者不正當的操作。(圖2) -------圖-------- 圖2 -------圖-------- #中標=應用Design Time Support的步驟 我們用HelloWorld Applcation block為例,圖3是Enterprise Library與Design Time Support的架構圖,HelloWorld Application block包含兩個部分,基層元件是實現HelloWorld的資料與功能,上層元件實現Design Time Configuration。我們分三個部步驟了解其設計的模式與技術。 -------圖-------- 圖3 -------圖-------- #中標=UI的設計 Configuration Console使用在UI方面主要用到二個技術,一是使用TreeView顯示階層式的Configuration Nodes,一是使用PropertyGrid顯示與編輯每個Configuration node的資料。從圖4的左邊開始,Configuration console使用繼承了.NET WindowsUI TreeView的CustomTreeView。內含顯示用的TreeNode,稱為ConfigurationTreeNode,Console用ConfigurationTreeNode顯示內含的Configuration Node資料,整個Tree代表一個Solution,由Solution Configuration Node代表,Solution可以包含多個Applications的配置設定,由Application Configuration Node代表,Application所擁有的節點形成一個UIHierarchy,Console的右邊視框的PropertyGrid顯示UIHierarchy內含的Selected Node資料。 CAB Design time support以ConfigurationNode為核心,ConfigurationNode繼承SitedComponent類別,實現ISite介面,所以當Configuration node被放入或移出TreeView時,可以透過OnSited()與OnUnSited()事件通知TreeView做畫面的處理。 但是ConfigurationNode是抽象類別,每個Application Block必須提供個別的具體類別,在HelloWorld的Configuration Design套件裡,具體實現了HelloWorldSettingsNode-代表Application block的配置,TextGreetingProviderNode-代表提供Greeting功能的配置,及TextGreetingNode-代表對特定人的問候語設定。 其實顯示在TreeView與PropertyGrid的Data來自於HelloWorld Configuration基礎套件裡的HelloWorldSettings、TextGreetingProviderData、及TextGreetingData。HelloWorldSettingsNode、TextGreetingProviderNode與TextGreetingNode是應用Wrapper design pattern的Wrapper類別,分別內含所需要的配置物件,所有個別的Concrete ConfigurationNode必須提供PropertyGrid所需要的顯示控制與編輯功能。 -------圖-------- 圖4 -------圖-------- #中標=提供PropertyGrid的顯示控制與編輯功能 當TreeView上的節點被選取時,Configuration Console的MainForm會設定PropertyGrid的操作物件為被選取的ConfigurationNode,如程式1的程式列表。在預設的情況下, SelectedObject全部的public variables與properties都會顯示在Grid上,所以標準的設計原則是宣告所有的member variables為private,並在每個public property上設定PropertyGrid相關的Attributes,以控制顯示與編輯行為。 -----程式----- #程式1 // In Console MainForm.cs private void OnTreeViewAfterSelect(object sender, TreeViewEventArgs e) { ConfigurationTreeNode node = e.Node as ConfigurationTreeNode; UpdateSelection(node); … } private void UpdateSelection (ConfigurationTreeNode treeNode) { object[] objects = new object[] {treeNode.ConfigurationNode}; propertyGrid.SelectedObjects = objects; } -----程式----- 我們以程式2的程式列表為例,說明如何運用PropertyGrid Control。使用[Browsable(false)] Attribute指定這個public property不顯示Grid中,使用[ReadOnly] Attribute設定Property在Grid中是否可以修改。[DefaultGreetingProvider] Attribute是讓使用者在HelloWorld Application Block中選擇預設的GreetingProvider,使用[Required] Attribute規定此欄位不能為null,[SRCategory]繼承.NET內建的[Category]Attribute,讓PropertyGird把property顯示在指定的分類。[SRDescription]繼承.NET內建的[Description]Attribute,設定PropertyGrid顯示在下方的欄位說明,[ReferenceType]規定此Property的資料型態。 -----程式----- #程式2 // in HelloWorldSettingsNode.cs [ReadOnly(true)] public override string Name { get { return base.Name; } set { base.Name = value; } } [Required] [SRCategory(“…”)] [SRDescription(“…”)] [Editor(typeof(ReferenceEditor), …] [ReferenceType(typeof(GreetingProviderNode))] public GreetingProviderNode DefaultGreetingProvider { } -----程式----- PropertyGrid編輯欄位資料時,會根據欄位的資料型態選擇適當的UITypeEditor,例如bool值使用true/false listbox,字串或數值則使用textbox。遇到客戶自行定義的資料型態,則可由.NET Design Time Support自行判斷,或如程式1使用[Editor] Attribute指定編輯此欄位所用的Editor。在CAB的Configuration Design套件中支援ReferenceEditor,它的功能讓使用者從目前Application的Reference Type instances中選擇所需要的Object。其運作的規則是當使用者從PropertyGrid點選修改欄位內容時,PropertyGrid發出Edit Request到.NET Design Time Support套件,.NET Design time support從被編輯的物件取出指定的Editor,?用ReferenceEditor,ReferenceEditor找出符合ReferenceType的全部Instances,再開?ReferenceEditorUI的Listbox列出清單,讓使用者選取,最後做一些 Validation完成編輯工作。 -------圖-------- 圖5 -------圖-------- #中標=提供動態的功能表與動作服務 了解如何顯示與編輯欄位資料後,我們接下來探討Configuration Console與CAB的Design套件如何支援功能表與動作(Actions)。Configuration Console有預設的公用功能表,例如Save File,Help,使用者點選TreeNode時,則按Node種類加入對應的Context Menu Items,例如SolutionNode可以有New Application功能表項目,ApplicationNode可以有new Application Block功能表項目,Application Block的節點與內部節點,則要按Application Block的設計決定,我們由上而下深入了解內部的設計技術。 使用者在TreeView上點選點節時,進入程式3的OnTreeViewAfterSelect()方法,接著是引用LoadMenuItemsFromNode(),再從Node的AddMenuItems()方法取出可以使用的menu items放入全域的MenuService,它是一個menu items的Container,最後CreateContextMenu()從Menu service取出Menu items,並複製一份臨時的Context Menu,由Windows UI的標準Right mouse click事件顯示出來。 -----程式----- #程式3 // In Console MainForm.cs private void OnTreeViewAfterSelect(…) { LoadMenuItemsFromNode(…) CreateContextMenu(); } -----程式----- ConfigurationNode的AddMenuItems()交給OnAddMenuItems()取得Menu items,所以每個Concrete ConfigurationNode必須override OnAddMenuItems (),OnAddMenuItems()負責建立新的MenuItems,給予Item名稱及設定Menu Item的Action Command。例如程式4,新Solution只有預設的new application功能,所以必須override OnAddMenuItems(),其內容是空的表示沒有自定的功能表項目。Application節點可以驗證配置設定是否正確,Close Application,及建立新的Application blocks等功能。HierachyLoad()負責從目錄中載入支援Design Time Support的Application blocks。HelloWorldSettingsNode代表HelloWorld Application block,從這個節點可以建立GreetingProvider,所以使用ConfigurationNode.CreateDynamicMenuItems()建立new provider的menu items。在TextGreetingProviderNode的OnAddMenuItems()則是提供建立TextGreetingNode的menu item。TextGreetingNode是TreeView樹狀結構的Leaf節點,目前沒有提供額外的功能,所以不需要override OnAddMenuItems()了。 -----程式----- #程式4 // In SolutionConfigurationNode.cs protected override void OnAddMenuItems() { } // In aApplicationConfigurationNode.cs protected override void OnAddMenuItems() { AddMenuItem(….CreateValidateNodeCommand(..)); AddMenuItem(…CloseApplicationConfigurationCo mmand); Hierarchy.Load(); } // In HelloWorldSettingsNode.cs protected override void OnAddMenuItems() { base.OnAddMenuItems (); CreateDynamicMenuItems(typeof(GreetingProvider Node)); } // In TextGreetingProviderNode.cs protected override void OnAddMenuItems() { base.OnAddMenuItems (); AddMenuItem(… AddChildNodeCommand(typeof(TextGreetingNode)) …); } -----程式----- #中標=Console、CAB Design套件與Application Block Design套件的架構設計 每個Application block的DLL放在Application的執行目錄中,ApplicationConfigurationNode的OnAddMenuItems()使用Hierarchy.Load()從目錄中載入支援Design Time Support的DLL,但Application如何得知DLL是否支援CAB的Design time support ? 關鍵在於每個Application Block的AssemblyInfo.cs,裡面必須宣告[ConfigurationDesignManager] Attributes,如程式5的程式列表,方能被Hierarchy.Load()載入。 -----程式----- #程式5 [assembly: ConfigurationDesignManager(typeof(HelloWorldConf igurationDesignManager))] -----程式----- 我們已經了解如何載入Application Block,加入Application Block的Configurations,在TreeView顯示Configuration Node,及在PropertyGrid中編輯Configuration Node。現在我們從圖6來看Console、CAB Design套件、與Application block的架構設計。Configuration Console的MainForm?動時,會先產生一個ConfigurationDesignHost物件,該物件是一個全域的ServiceContainer,內含MeunService、UIService、NodeCreationService等等,它是Console與UIHierarchy的溝通橋樑。接下來,使用者執行new application功能表,Console為Application建立一個UIHierarchy儲存全部的配置資料,並使用UIHierarchy.Load()方法載入Application目錄下的Application blocks,這時候Console經由DesignManagerDomain載入Assembly與configuration File,DesignManagerDomain主要的任務還有Save 更新的Configurations。最後一個設計是採用proxy design pattern,因為CAB的Design套件無法事先取得Application Block的DesignManager(Manager的Class Name是自定的),為了讓CAB Design套件具有獨立完整的特性,所以CAB Design套件提供DesignManagerProxy,讓Console與CAB Design套件透過Proxy引用Application Block的DesignManager功能。 -------圖-------- 圖6 -------圖-------- #中標=結論 Enterprise Library的Design Time Support與Configuration Console同時支援Runtime與Design的設定,包含完整的UI設計,運用了plugin與多種design patterns,是提昇.NET技術與軟體設計功力的最佳範本,值得讀者深入研究。 責任編輯/黃雅惠 ----------box-------- 作者簡介 林耀珍 台灣地區Microsoft.NET技術代言人,現任寰震科技副總經理,歷任第三波資訊技術總監及育�祤あ鴐鴔瓾`經理。專長於資訊系統規劃、管理軟體開發團隊與軟體技術,擁有Rational OOAD,Microsoft MCSD/MCSE/MCDBA等認證資格,為Microsoft.NET/Java/軟體工程的講師與顧問。 ---------end--------------