Mark Seemann
本文將介紹以下內容:
• |
編寫針對 Enterprise Library 的新應用程序塊 |
• |
構建一個插件加載器應用程序塊 |
• |
瞭解和實現工廠 |
• |
實現自定義應用程序塊的配置 |
本文涉及如下技術:
• |
Enterprise Library、Visual Studio、C# |
可以從此處下載代碼:
AppBlocks2006_07.exe(315KB)
本頁內容
創建應用程序塊 | |
對象的工廠 | |
創建工廠 | |
定義插件配置 | |
實現 PlugInProvider | |
設計時行爲 | |
提供程序節點 | |
序列化和反序列化 XML | |
小結 |
Enterprise Library for Microsoft .NET Framework 2.0 是一個應用程序塊庫,而應用程序塊則是一些模塊化組件,設計用來幫助開發人員應對常見的開發挑戰。它爲構建健壯的可伸縮應用程序提供了一個可擴展框架。儘管 Enterprise Library 包含若干有用的應用程序塊,但仍可以集成您自己的可重用組件。在本文中,我將演示如何構建一個與 Enterprise Library 集成的示例應用程序塊。
Microsoft 的“模式和實施方案”團隊是 Enterprise Library 的創建者,並且創造了“應用程序塊”這個術語來描述超出 DLL 文件範疇的庫。雖然應用程序塊實際上是一個 DLL,但它具有更深一層的獨特特徵。
應用程序塊是配置驅動式的,這意味着可使用配置來定義其行爲。該配置可在標準的 .NET 配置文件中定義,也可以在運行時創建配置並將其注入到應用程序塊中。某些配置將是典型的配置值,例如用於定義某些行爲的條件的編號。但是在應用程序塊方面,配置還可用來定義對基本塊的擴展。
應用程序塊還是模塊化和可擴展的。可擴展性通常藉助提供程序模型來實現;塊將某些工作委託給一個提供程序,而由該提供程序實現塊所使用的某個基類或接口。雖然塊通常帶有幾個預先打包的提供程序(例如,允許您在 Windows® 事件日誌中寫入日誌記錄的提供程序,或允許您讀寫 SQL Server™ 數據的提供程序),但是也可通過在塊的配置中定義提供程序類型來開發和替代其他提供程序。
何時您需要創建應用程序塊而不是標準的庫呢?如果您不需要配置庫並且不想讓其他人擴展它,則無需創建應用程序塊。但是,如果這些功能可能會很有用,則應該考慮創建應用程序塊。此外,由於 Enterprise Library 已經提供了大量可以利用的配置管理基礎結構,您可以通過創建應用程序塊而從中受益,創建獨立的可配置庫則無法享受這些好處。
應用程序塊增加了庫的複雜性,並且增加了對 Enterprise Library 中的其他塊的依賴。但是反過來,您獲得了一個公共的配置管理系統,並且能夠使用 Enterprise Library 中的其他部分。例如,數據訪問便是一種非常常見的需要。如果將應用程序塊與 Enterprise Library 進行集成,便可以爲塊編寫一個使用“數據訪問應用程序塊”的可選提供程序。在一切正確完成後,這會創建一種對“數據訪問應用程序塊”的可選依賴;如果不需要數據訪問,則可以避免這種依賴。
創建應用程序塊
在本文中,我將爲您展示如何構建一個可用來管理應用程序插件的應用程序塊。請注意,雖然我講述的內容會涉及應用程序塊開發的許多方面,但我在實現部分中將省略一些不適用於 Enterprise Library 的內容。因此,我在本文中創建的解決方案簡單且很不安全。如果希望全面瞭解如何以安全和健壯的方式加載插件,請參見 Shawn Farkas 的 MSDN®Magazine 文章“您信任它嗎?探尋使用 .NET Framework 2.0 安全承載不受信任的外接程序的技術”。由於我在這裏要創建的 Plug-In Loader 應用程序塊是可擴展的,因此可以開發一個利用 Shawn Farkas 文章中所介紹方法的新提供程序並將其添加到此應用程序塊中。
可能上述免責聲明有些跑題了,下面讓我們開始。首先,我將定義應用程序塊的最終目標。因爲它的目的是加載插件,因此我認爲一個合理的目標應該是使開發人員能夠編寫下面這樣的代碼:
PlugInManager mgr = PlugInFactory.GetManager(); IList plugIns = mgr.GetPlugIns(); // 在此處利用插件執行某些工作...
靜態的 PlugInFactory 類新建一個可用來獲取插件的 PlugInManager。在本文中,GetPlugIns 是 PlugInManager 的唯一成員。在更全面的實現中,可添加其他功能(例如,在運行時啓用或禁用每個插件的能力)。PlugInManager 將實際工作委託給應用程序塊的配置中指定的提供程序。然後,該提供程序根據其實現中的定義,加載並返回插件。
應用程序塊包括運行時組件和可選的設計時組件。我將爲您演示如何開發這兩個組件。庫的邏輯是在運行時組件中實現的。如果您想要利用應用程序塊,則需要在項目中引用該文件。開發運行時組件就其本身來說涉及幾個步驟。
設計時組件是可創建和包含的另一個庫程序集。正如運行時組件與 Enterprise Library 運行時框架集成在一起,設計時組件也與 Enterprise Library 的設計時工具相集成。該設計時功能的主要存在目的便是:讓開發人員使用應用程序塊的過程更加容易。但是,它也可由系統管理員或其他用戶使用,以提供一個圖形化工具,對應用程序的配置進行編輯。
應用程序塊是一個複雜的代碼集合,包含大量的交互類。在您閱讀本分步指南時,您可能奇怪爲什麼必須擁有那麼多的不固定部分。其部分原因是:Enterprise Library 在如何實現應用程序塊方面提供了極大的靈活性。在很多步驟裏,Enterprise Library 使用抽象(例如接口和抽象類)來完成某些任務。在大多數此類方案中都包含了一個可擴展的默認實現,但是很少強迫您使用該默認實現。如果願意,您可以創建自己的實現。
在 Enterprise Library 每次完成某個任務(例如從持久性存儲中讀取配置數據或在請求抽象類型時實例化具體類型)時,可以對默認行爲進行擴展或從默認行爲進行派生。對於 Enterprise Library 中的每個擴展點,通常都會涉及幾個交互類。因爲 Enterprise Library 包含大量的擴展點,因此也存在衆多的交互類 —— 您很快就會自己發現這一點。
對象的工廠
Enterprise Library 使用工廠創建對象。它有自己的基於屬性的方法,通過使用這種方法,您可利用類的工廠的類型來劃分類的屬性,然後通過從 Enterprise Library 提供的基類進行派生來實現該工廠。圖 1 簡要描述了典型的對象創建過程。正如您看到的,這個過程十分複雜,但必須記住的是:它完成的工作並不僅僅是創建了類型的任意實例。您請求一個抽象類型,然後根據配置數據獲得該類型的正確實現,並且它已進行了適當的配置和檢測。
此對象創建框架的可擴展性極好,您應將其作爲一種對象創建機制用於自己的應用程序塊,但是您很快就會發現需要創建大量的工廠類。圖 2 簡單描述了這些類之間的交互。在使用 Plug-In Loader 應用程序塊時,靜態的 PlugInFactory 類通常是入口點。該類僅僅是一個便利類 (Convenience Class),它使用 PlugInManagerFactory 創建 PlugInManager 的實例。大多數 Enterprise Library 應用程序塊都提供這樣的一個靜態工廠類,將其作爲通往應用程序塊的入口點。在創建 Enterprise Library 應用程序塊時,可以選擇是否實現靜態工廠類,但是我決定爲 Plug-In Loader 包含一個這樣的類,因爲它在這裏十分有用。
圖 2 對象工廠類的交互
Enterprise Library 採用一種基於工廠的做法來創建可配置對象,雖然該框架十分靈活,但是它並不支持泛型,因爲類型在編譯時是未知的。出於這個原因,Plug-In Loader 的泛型類將它們的大部分工作委託給可由 Enterprise Library 創建和管理的非泛型類。PlugInManager 包裝抽象的 PlugInProvider 類,PlugInManagerFactory 則包裝 PlugInProviderFactory。如果您的應用程序塊不需要支持泛型,則沒有必要實現 PlugInManager 和 PlugInManagerFactory 這樣的類。
PlugInProviderFactory 創建 PlugInProvider 的實例,它派生自 Enterprise Library 提供的一個類。通過使用在圖 1 的步驟 1 中描述的 CustomFactory 屬性,PlugInProvider 可標識 PlugInProviderCustomFactory,Enterprise Library 將 PlugInProviderCustomFactory 與配置類結合使用來創建 PlugInProvider 的實例。
這似乎比較複雜,但其中的大部分工作均由 Enterprise Library 負責執行。您需要實現相當多的類,但所有的類均十分簡單。您可能會問自己爲什麼需要如此複雜。正如我前面提到的,您所做的並不僅僅是新建 PlugInProvider 的一個實例。對於初學者,您無法實例化 PlugInProvider,因爲它是一個抽象類。當您請求 PlugInProvider 的實例的時候,應用程序塊實際上必須根據配置數據來提供該抽象類的實現。Enterprise Library 使用此機制來識別要創建的內容和創建方式。
創建工廠
如果您看了我前面展示的預期目標代碼,可以發現:我首先需要實現的類是 PlugInManager 和 PlugInFactory。這兩個類將它們的大多數工作委託給其他類。PlugInManager 使用 PlugInProvider 執行實際工作,如圖 3 所示。由於 Enterprise Library 對象創建機制不支持泛型,PlugInProvider 並不是一個泛型類,因此,爲了返回 IList,PlugInProvider 返回的 IList 必須被轉換爲相應的類型安全集合。
PlugInProvider 本身是如圖 4 所示的簡單類。由於它是抽象的,因此它爲 Plug-In Loader 提供了擴展點。如果希望擴展 Plug-In Loader,可從 PlugInProvider 派生一個新類並在該類中實現自定義邏輯。最值得注意的 PlugInProvider 功能是 CustomFactory 屬性,它可指示 Enterprise Library 如何創建從 PlugInProvider 派生的類的新實例。此外還應注意抽象的 GetPlugIns 方法,該方法是繼承者必須實現的方法。
PlugInProviderCustomFactory 派生自 Enterprise Library 提供的抽象類 AssemblerBased-CustomFactory。在從該類派生時,您必須做的唯一一件工作是:實現 GetConfiguration 方法。這裏首次出現了一對新類:PlugInLoaderSettings 和 PlugInProviderData,如以下代碼所示:
public class PlugInProviderCustomFactory : AssemblerBasedCustomFactory { protected override PlugInProviderData GetConfiguration( string name, IConfigurationSource configurationSource) { PlugInLoaderSettings settings = (PlugInLoaderSettings)configurationSource. GetSection(PlugInLoaderSettings.SectionName); return settings.PlugInProviders.Get(name); } }
這些類是配置類,我將在後面的部分中更詳細地討論它們。
現在,要注意的最重要事情是:GetConfiguration 方法返回相應的配置數據,使得 Enterprise Library 能夠構造新的 PlugInProvider 對象,如圖 1 所示。在該自定義工廠就位之後,我可以創建一個工廠類,隨後使用該工廠類創建 PlugInProvider 實例,如以下代碼所示:
public class PlugInProviderFactory : NameTypeFactoryBase { public PlugInProviderFactory(IConfigurationSource configSource) : base(configSource) { } }
雖然這又是另一個工廠類,但我需要做的全部工作就是從 NameTypeFactoryBase 類派生並提供一個公共的構造函數。PlugInManagerFactory 只是對 PlugInProviderFactory 進行包裝。PlugInFactory 創建並維護這些工廠的一個字典,並將工作委託給相應的工廠,如圖 5 中的代碼所示。
在這裏,需要注意特定於 Plug-In Loader 的命名約定。Enterprise Library 多態集合使用它們所包含項的名稱作爲鍵,因此每個名稱在集合中必須是唯一的。對於 Plug-In Loader 而言,插件的類型應該是最爲直觀的鍵。然而,這可能要求我創建自己的集合類(使用類型作爲鍵),因此我將無法重用 Enterprise Library 提供的類。
由於設計時組件在任何情況下都需要一個唯一名稱,Plug-In Loader 的約定適用於要使用插件類型的合格程序集名稱進行命名的每個 PlugInProvider,該合格程序集名稱便是您在圖 5 中看到的名稱。這雖然不是一個太好的方法,但是用戶絕對不會注意到,因爲我還將在設計時組件中對此約定進行處理。在另一方面,如果您喜歡編輯原始 XML,那麼所有東西在任何情況下對您而言都只不過是字符串。
這就是創建應用程序塊的第一個步驟。如果您返回上文查閱圖 1,可能會奇怪爲何我沒有定義任何裝配器類。這是因爲裝配器依賴於 PlugInProvider 的實現,而不是依賴於抽象的 PlugInProvider 類本身。現在,我將介紹如何定義配置類,然後我將介紹如何實現 PlugInProvider,其中我還將圍繞創建相應的裝配器展開討論。
定義插件配置
Enterprise Library 的配置框架建立在 System.Configuration 的基礎之上,而且工作方式也與其非常相似。爲了定義 Plug-In Loader 的配置節,我創建了 PlugInLoaderSettings 類,如圖 6 所示。應用程序塊的配置節應該派生自 Microsoft.Practices.EnterpriseLibrary.Common.Configuration.SerializableConfigurationSection,而不是直接從 System.Configuration.ConfigurationSection 派生。這可將 Enterprise Library 功能添加到該類;此外,還允許您在應用程序配置文件之外的其他位置存儲配置節。
PlugInLoaderSettings 類僅包含一個 PlugInProviderData 類的集合。PlugInProviderData 類包含用來配置 PlugInProvider 實例的數據,如下所示:
public class PlugInProviderData :NameTypeConfigurationElement { public PlugInProviderData() :base() { } public PlugInProviderData(string name, Type type) : base(name, type) { } }
該類表示一個配置元素,而且不是直接從 System.Configuration.ConfigurationElement 派生。假如我希望創建一個簡單的配置元素,我可以直接從 ConfigurationElement 派生 PlugInProviderData,但是 Enterprise Library 還爲我提供了另外兩種選擇。一種選擇是 NamedConfigurationElement 類,另一種則是 NameTypeConfigurationElement。前一種選擇將一個名稱添加到配置元素,該配置元素在實現應用程序塊的設計時功能時十分有用。此外,該名稱還可作爲 Enterprise Library 提供的泛型配置集合類中的唯一鍵。
NameTypeConfigurationElement 爲配置元素增加了附加的 Type 屬性,它可用來支持多態集合,而多態集合恰好就是我在本例中所要的東西 —— 以便爲不同的插件類型指定不同的插件提供程序(每個均具有唯一的配置設置)。在這裏,配置元素的名稱作爲元素的鍵,Type 屬性則標識元素所配置的類型。對於 Plug-In Loader 而言,Type 屬性標識實現 PlugInProvider 的類型。讓我們回想一下,按照約定,Plug-In Loader 使用 Name 屬性來存儲插件的類型的合格程序集名稱。很容易弄混這兩種類型,但該名稱標識應當由提供程序爲其提供服務的插件類型,而 Type 屬性則標識提供程序的類型。由於名稱即爲鍵,因此只需將插件類型定義一次,但是同一個提供程序類型可以爲許多不同的插件類型提供服務,事實上,最常見的情況是:它們可能都由同一個提供程序提供服務。
鑑於 Enterprise Library 構造這些配置元素類時使用的方式,我們無法將 PlugInProviderData 類變得抽象,但是您可以將其視爲抽象的。請注意,它實際上沒有執行任何工作,所以在這種特殊的實現方式中,我可以省略它併爲不同的插件提供程序創建我的配置元素,方法是直接從 NameTypeConfigurationElement 派生它們。但是,抽象的 PlugInProvider 類實際也包含某些實現,而且如果在提供程序和它們的配置元素之間存在一對一關係,在理解應用程序塊代碼的結構時會更容易。
實現 PlugInProvider
到目前爲止,運行時組件的抽象框架已經完成了,但是它仍然沒有任何功能。現在,到了實現 PlugInProvider 的時間了。這是一種本機實現,因此並不安全,而且不支持能從內存中卸載的插件。所以,我將它稱作 NaivePlugInProvider。
如圖 7 所示,NaivePlugInProvider 類派生自 PlugInProvider。它的主要功能在 GetPlugIns 方法中實現,該方法簡單地加載和反射位於所配置文件夾中的所有程序集中的所有類型。如果某個類型實現了想要的插件類型,便會創建該類型的新實例並添加到返回的插件列表中。請注意,此實現要求所有插件都具有默認構造函數,更健壯的實現可以使用更完善的方法。
NaivePlugInProvider 具有其他兩個並不是特別顯著的特點,但是在創建 Enterprise Library 應用程序塊的上下文中,這兩個特點顯得十分有趣:使用了 ConfigurationElementType 屬性,以及缺少默認構造函數。
在配置 Plug-In Loader 應用程序塊時,您只需要關心要使用哪一個 PlugInProvider,而不用關心由哪一個類爲該提供程序提供配置數據。ConfigurationElementType 屬性包含該信息,這意味着配置數據僅包含有關要創建哪一個 PlugInProvider 的信息,而 Enterprise Library 基礎結構則指出了哪一個類包含針對該提供程序的配置數據。在本例中,這個類是 NaivePlugInProviderData 類,如圖 8 所示。該類從 PlugInProviderData 派生,並且提供了一個額外的配置屬性,允許您指定將包含插件程序集的文件夾。
有關 NaivePlugInProvider 的另一個有趣的事情是:它缺少默認構造函數。如果沒有默認構造函數,Enterprise Library 如何創建 NaivePlugInProvider 的新實例?NaivePlugInProviderData 具有一個 Assembler 屬性。該屬性標識一個可從 NaivePlugInProviderData 實例創建 NaivePlugInProvider 實例的類型。
NaivePlugInProviderAssembler 類也顯示在圖 8 中。裝配器類必須實現包含單個 Assemble 方法的 IAssembler。它使用提供的配置數據取出相關信息並創建新的 NaivePlugInProvider 實例。
現在,Plug-In Loader 包含了一個完全可工作(儘管有些過於簡單)的實現,並且可投入使用。您現在可以繼續並創建更多提供程序,以便爲該應用程序塊創建不同的插件發現行爲。一種顯而易見的擴展便是:一個遵循有關發現和加載插件的安全實踐的提供程序。另一種可能的擴展是:一個從 SQL Server 表中的 Blob 中獲取插件的提供程序,這可能造成對數據訪問應用程序塊的可選依賴。
如果不介意必須手動在 XML 文件中寫入整個配置,您可以到此爲止併發布您的應用程序塊了。否則,可以創建一個設計時組件,讓它爲您完成這個工作。
設計時行爲
設計時組件插入到 Enterprise Library 配置應用程序之中。這是一個可擴展 Windows 窗體應用程序,允許您使用豐富的用戶界面來編輯應用程序配置文件,而不是必須編寫原始 XML 代碼。該組件有三項職責:它必須爲應用程序 UI 自身提供行爲;它必須允許應用程序序列化用戶的設置;在用戶打開已有的應用程序配置文件時,它必須能夠反序列化配置數據。
由於應用程序配置文件是基於 XML 的,因此它們天生便具有層次結構。配置應用程序將此模型化爲一個由節點組成的樹。運行時組件中的每個配置元素必須由一個設計時節點類來表示,該節點類爲配置類提供了額外的設計時行爲。圖 9 描繪了 Plug-In Loader 應用程序塊的這種關係。
圖 9 運行時類和設計時類之間的映射
爲了與 Enterprise Library 配置應用程序集成,設計時組件必須向其進行註冊。程序集及其依賴項必須與配置應用程序本身位於同一目錄,而且必須利用 ConfigurationDesignManager 屬性對其進行標記,如下所示:
[assembly:ConfigurationDesignManager( typeof(PlugInLoaderConfigurationDesignManager))]
該程序集級別的屬性向配置應用程序註冊 PlugInLoaderConfigurationDesignManager。該類從抽象的 ConfigurationDesignManager 類派生。
定義設計時行爲的工作涉及指定在具體上下文中可能執行的具體操作。可以通過重寫 ConfigurationDesignManager 的 Register 方法來達到這個目的:
public override void Register(IServiceProvider serviceProvider) { PlugInLoaderCommandRegistrar cmdRegistrar = new PlugInLoaderCommandRegistrar(serviceProvider); cmdRegistrar.Register(); // 此處添加節點映射代碼... }
PlugInLoaderCommandRegistrar 從抽象的 CommandRegistrar 類派生;其目的在於向配置應用程序註冊設計時操作。需要實現的第一個操作是將應用程序塊添加到應用程序配置文件的命令。當 Plug-In Loader 應用程序塊被添加到應用程序配置時,必須將 PlugInLoaderSettingsNode 及其子 PlugInProviderCollectionNode 添加到層次結構中。
首先,必須定義這些節點類,例如:
public class PlugInLoaderSettingsNode :ConfigurationNode { public PlugInLoaderSettingsNode() : base("Plug-In Loader Application Block") {} [ReadOnly(true)] public override string Name { get { return base.Name; } set { base.Name = value; } } }
PlugInProviderCollectionNode 幾乎是完全相同的,因爲 PlugInLoaderSettingsNode 不包含除 PlugInProviders 集合之外的其他屬性。儘管您可能認爲我可以爲兩個節點使用一個公共的類,但這並不適用於本例的情況。兩個節點在層次結構中佔據不同的位置,而且我將爲它們附加不同的操作。您可能奇怪我爲何重寫 Name 屬性 (Property),實際上我只不過使用 Read-only 屬性 (Attribute) 對它進行了標記。這將使這些節點在配置應用程序中成爲只讀的。
當用戶調用將 Plug-In Loader 應用程序塊添加到應用程序配置文件的該命令時,必須將這兩個節點添加到層次結構中。爲達到這個目的,我創建了 AddPlugInLoaderSettingsNodeCommand 類,如圖 10 所示。它從 AddChildNodeCommand 派生並重寫 ExecuteCore 方法,以實現想要的邏輯。該命令類必須與節點類進行關聯,以便基類知道它應創建一個 PlugInLoaderSettingsNode 實例並將其添加到層次結構中。在調用 ExecuteCore 的基實現後,已經達到了此目的,所以我所需做的全部工作便是創建一個新的 PlugInProviderCollectionNode 並將其添加到設置節點。
AddPlugInLoaderSettingsNodeCommand 類定義當用戶調用該命令時所發生的操作。但我仍然需要定義該命令的使用時間和使用場合。應僅在用戶選擇應用程序配置根節點時才使用該命令,而且應當只可能調用該命令一次。我通過重寫抽象的 Register 方法,在 PlugInLoaderCommandRegistrar 類中實現這個目的:
public override void Register() { this.AddPlugInLoaderCommand(); // 此處添加其他命令... }
AddPlugInLoaderCommand 方法僅包含三條語句,如下所示:
private void AddPlugInLoaderCommand() { ConfigurationUICommand cmd = ConfigurationUICommand.CreateSingleUICommand( this.ServiceProvider, "Plug-In Loader Application Block", "Add the Plug-In Loader Application Block", new AddPlugInLoaderSettingsNodeCommand(this.ServiceProvider), typeof(PlugInLoaderSettingsNode)); this.AddUICommand(cmd, typeof(ConfigurationApplicationNode)); this.AddDefaultCommands(typeof(PlugInLoaderSettingsNode)); }
通過調用 CreateSingleUICommand,我指定該命令只能被調用一次。在該方法調用中,我還提供了顯示文本和 AddPlugInLoaderSettingsNodeCommand 的一個實例,當用戶選擇執行該操作時調用該實例。通過調用 AddUICommand,我將該命令與 ConfigurationApplicatonNode 類型進行了關聯,該類型是應用程序配置根節點的類型。AddDefaultCommands 方法將默認命令(例如 Add 和 Delete)添加到新創建的 PlugInLoaderSettingsNode。
提供程序節點
PlugInProviderData 必須由 PlugInProviderNode 進行增強,而 NaivePlugInProviderData 則由 NaivePlugInProviderNode 進行增強,如圖 9 所示。圖 11 中抽象的 PlugInProviderNode 爲 PlugInProviderData 提供了設計時功能。來自 System.ComponentModel 命名空間的幾個屬性在此處派上了用場:Category、Editor 和 ReadOnly。它們的功能與在 Visual Studio® 屬性網格中相同。
圖 9 運行時類和設計時類之間的映射
PlugInProviderNode 包裝 PlugInProviderData 實例,該實例提供並存儲除插件類型之外的所有配置數據。Plug-In Loader 使用特殊的命名約定,按照這種約定,所配置的 PlugInProvider 名稱是插件類型的合格程序集名稱。因爲不能保證 Name 屬性會被解析爲類型,PlugInProviderNode 在一個成員變量中單獨存儲插件類型。
PlugInType 屬性 (Property) 還包含 BaseType (Attribute),在 Editor (Attribute) 中標識的 TypeSelectorEditor 使用 BaseType 來篩選可用類型。當該編輯器顯示可用類型時,只會列出那些抽象的基類型(或接口)。在選擇插件類型時,這些是值得列出的僅有類型,因爲您無法將插件建立在密封類型的基礎之上。
另一個值得注意的 PlugInProviderNode 功能是隻讀的 Provider 屬性。我發現,能夠檢查配置了哪個提供程序總是一件不錯的事情,而且這爲向用戶通報信息提供了最直接的方式。否則,真的很難告知使用配置應用程序的時機。
關於 PlugInProviderNode,最後一件值得注意的事情是:我使用 OnRenamed 保存節點的名稱和被同步的基礎數據的名稱。
NaivePlugInProviderNode 類通過提供 PlugInFolder 屬性來擴展 PlugInProviderNode。與添加應用程序塊本身相比,將 NaivePlugInProvider 命令添加到配置應用程序要更爲簡單一些,因爲該命令不需要在 NaivePlugInProviderNode 下創建附加子節點。所以,我不需要爲該操作創建單獨的命令類。相反,我可以讓 PlugInLoaderCommandRegistrar 處理所有的命令註冊:
this.AddMultipleChildNodeCommand( "Naive Plug-In Provider", "Add a new naive Plug-In Provider", typeof(NaivePlugInProviderNode), typeof(PlugInProviderCollectionNode)); this.AddDefaultCommands(typeof(NaivePlugInProviderNode));
通過調用 AddMultipleChildNodeCommand,我指定該命令可以被調用任意多次,以創建新的 NaivePlugInProviderNodes 並作爲 PlugInProviderCollectionNode 的子節點。也可使用類似機制添加其他 PlugInProvider 節點類型。
序列化和反序列化 XML
當使用配置應用程序時,在選擇保存更改之前,設置僅保留在內存中。在保存更改之後,每個節點中配置的數據必須被序列化爲 XML。幸運的是,Enterprise Library 和 System.Configuration 負責在配置元素和 XML 之間執行序列化和反序列化。您需要做的唯一工作是指定如何從節點映射到配置類。
可以通過重寫 PlugInLoaderConfigurationDesignManager 的 GetConfigurationSectionInfo 方法來達到這個目的。此實現從層次結構中獲取 PlugInLoaderSettingsNode,但是將實際工作委託給 PlugInLoaderSettingsBuilder 類,如圖 12 所示。該內部類創建一個新的空 PlugInLoaderSettings 實例,然後使用節點層次結構將配置數據添加到該實例。
由於 Plug-In Loader 的配置層次結構非常淺,因此僅需循環通過所有 PlugInProviders 並從它們的節點添加配置數據。如果層次結構較深,此操作可能需要遍歷節點層次結構並建立配置類的等效層次結構。
就像 Enterprise Library 和 System.Configuration 負責序列化到 XML 一樣,該框架也執行從 XML 到配置類實例的反序列化工作。但是,您必須提供代碼,從配置類到節點層次結構進行映射。第一步是重寫 PlugInLoaderConfigurationDesignManager 的 OpenCore 方法:
protected override void OpenCore(IServiceProvider serviceProvider, ConfigurationApplicationNode rootNode, ConfigurationSection section) { if (section != null) { PlugInLoaderSettingsNodeBuilder builder = new PlugInLoaderSettingsNodeBuilder(serviceProvider, (PlugInLoaderSettings)section); rootNode.AddNode(builder.Build()); } }
與序列化爲 XML 類似,這裏我將實際工作委託給另一個類:PlugInLoaderSettingsNodeBuilder。該類從 NodeBuilder 派生,NodeBuilder 爲配置類和節點間的映射提供了一些實用工具方法。我的想法是:根節點和所有集合節點擁有關聯的節點構建器 (Node Builder) 類,所以節點構建器類僅包含創建它自己的節點類型的代碼,而將創建其餘節點層次結構的工作委託給其他節點構建器類。這也適用於 PlugInLoaderSettingsNodeBuilder,它創建一個新的 PlugInLoaderSettingsNode 並將創建 PlugInProviders 集合的工作委託給另一個類。本文的下載代碼展示了具體的實現方式。
該代碼使用 NodeCreationService 從配置數據創建新的 PlugInProviderNode,並且將該節點添加到集合節點。爲了使 NodeCreationService 能夠執行映射,必須由 PlugInLoaderConfigurationDesignManager 在 Register 方法中註冊節點映射。
爲了創建和註冊節點映射,我創建 PlugInLoaderNodeMapRegistrar 類(派生自 NodeMapRegistrar)並重寫它的抽象 Register 方法。在這裏,我通過調用繼承的 AddMultipleNodeMap 方法,簡單地在配置數據類和對應節點類之間建立了映射:
this.AddMultipleNodeMap("Naive Plug-In Provider", typeof(NaivePlugInProviderNode), typeof(NaivePlugInProviderData));
通過使用節點構建器類的機制,我可以建立一個幾乎具有任意深度和複雜性的節點層次結構,同時讓每個節點構建器類的實現過程仍保持相對簡單。這種方法對於本文中的示例來說似乎太過複雜,但是它具有很好的伸縮性,可在更加複雜的配置方案中大顯身手。
小結
創建 Enterprise Library 應用程序塊的過程並不那麼容易,但是對於正確的項目類型而言,它已經被證明是一種物超所值的方法。創建 Enterprise Library 應用程序塊(或者說將標準的、配置驅動式的庫轉化爲應用程序塊)的核心在於開發運行時組件。但是,可選的額外組件也具有同等的重要性。我介紹了設計時組件,但是這僅僅是若干步驟中的第一步。
將應用程序塊打包在 Microsoft® Installer (MSI) 程序包中並不難實現,這使得應用程序塊更易於下載和安裝。此外,您還應該考慮創建完善的文檔 —— 不僅是從 XML 註釋生成的 API 文檔,還應包括快速介紹、架構概述、入門指南等等。
大多數開發人員通過示例進行學習,所以幾個優秀的 QuickStart(快速入門)示例程序將使應用程序塊更容易得到採納。即便您的受衆僅限於組織內部,也非常值得爲示例投入精力。
Mark Seemann 供職于丹麥哥本哈根的 Microsoft Consulting Services 部門,其工作職責是幫助微軟客戶及合作伙伴構架、設計和開發企業級應用程序。他感謝 Tom Hollander 爲本文提供的寶貴幫助。可通過 Mark 的博客與其聯繫:blogs.msdn.com/ploeh。