在進行架構開發的過程中,我們往往需要對設定檔的結構進行設計,以便產生一套完整的配置方案,供開發人員在使用架構時能對架構進行配置。對於某些大型的架構,其配置節點的結構可能相當複雜,比如某個配置節點(Configuration Element)可以有屬性,還可以在其下掛載多個其它的配置節點或者多個配置節點集合(Configuration Element Collection)。如果使用手動編寫代碼的方式來維護與配置相關的代碼,勢必會出現大量的重複勞動,比如,需要給每個配置屬性添加System.Configuration.ConfigurationPropertyAttribute特性,需要為各個配置節點集合採用相同的代碼編寫入模式(例如重寫其中的CreateElement等方法)。這樣做不僅耗時而且容易出錯。更進一步,Visual Studio支援智能感知技術,如果我們在設定檔編輯器上設定了所要用到的配置資訊XSD Schema檔案,我們就可以利用智能感知方便快速地編寫設定檔。然而,如果我們的配置節點採用手工代碼維護,那麼在編寫完代碼之後,還需要另外編寫一套XSD Schema檔案,以使得開發人員在使用我們的架構時,能夠享受到智能感知帶來的便捷,這樣做不僅工作量大,而且一旦配置資訊結構變得複雜,我們就很難確保代碼與XSD Schema之間的對應關係。
今天我向大家介紹一款個人覺得比較不錯的基於.NET的設定檔設計工具:Configuration Section Designer。它是一款使用Microsoft Visualization & Modeling SDK開發的面向設定檔設計領域的領特定領域語言(DSL,有關DSL的知識,我會在後續的部落格文章中向大家介紹)。如果你使用的是Visual Studio 2005/2008,你可以去Configuration Section Designer的首頁下載安裝包。如果你使用的是Visual Studio 2010,那麼你還可以使用VS2010的Extension Manager來找到這個設計工具。
下載與安裝
可以到Configuration Section Designer的下載頁面下載並安裝該工具。如果你使用的是Visual Studio 2005/2008,你將得到一個EXE的安裝程式;如果你使用的是Visual Studio 2010,你將得到一個VSIX的擴充包。我使用的是Visual Studio 2010,因此接下來都會以Visual Studio 2010進行介紹。
在得到VSIX擴充包後,雙擊直接開啟運行就可以將其安裝到Visual Studio 2010的開發環境中。
建立設定檔項目
首先你可以使用Visual Studio 2010隨便建立一個項目(比如Class Library或者Console Application都可以),然後在這個項目上單擊滑鼠右鍵,選擇Add –> New Item菜單,這將開啟Add New Item對話方塊,在Installed Templates –> Visual C# Items節點下,找到ConfigurationSectionDesigner,更改名稱後單擊Add按鈕。
在完成Designer Surface的建立之後,我們可以看到在項目中多了一個.csd的檔案,在Toolbox中,也出現了與設定檔設計相關的工具:
看上去是不是有點像ADO.NET Entity Framework的設計器?不錯,這就是Microsoft Visualization & Modeling SDK給我們帶來的強大功能:它允許開發人員設計自己的領特定領域語言(Domain Specific Language, DSL),並以VSIX等擴充包的方式整合到Visual Studio開發環境中。
功能介紹
本文不打算講解如何使用Configuration Section Designer來設計設定檔,只對其中的一些非常不錯的功能進行介紹。
自動化代碼與檔案的產生
對於一款DSL來說,自動化代碼產生不算是什麼強大的功能,但是這款工具不僅僅會產生代碼,而且還會產生與之相關的XSD Schema以及設定檔樣本(Sample Configuration File),能讓開發人員直觀地看到最終效果:
設定檔樣本測試
在設計設定檔的過程中,你可以直接雙擊產生的.csd.config(比如上面的ConfigurationSectionDesigner1.csd.config)檔案,然後在裡面進行編輯,來測試你的設計是否正確。注意這個編輯過程是內建有智能感知的:
Windows Forms設計器的支援
一個專業的開發架構在向使用者提供配置相關的代碼以及XSD Schema的同時,還應該為使用者提供方便的設定檔編輯器(例如Microsoft Patterns & Practices Enterprise Library通常都會帶有面向各種Application Block的配置編輯器)。試想我們將用Windows Forms及其Property Grid控制項來設計一款設定檔編輯器,在將設定物件綁定到Property Grid時,Property Grid會通過反射將該對象下所有的屬性都顯示出來。然而對於配置編輯器而言,我們不僅需要控制設定物件中各個屬性的顯示方式,而且還需要對這些屬性進行一些描述和歸類。如果是手工維護架構中的配置代碼,這個問題好解決:直接向每個屬性手工添加諸如System.ComponentModel.DescriptionAttribute、System.ComponentModel.BrowsableAttribute等特性即可。但如果整個配置代碼都是由某工具自動化產生的,那麼你就不能直接在產生的程式碼上就行手工修改,而只能通過System.ComponentModel.DataAnnotations.AssociatedMetadataTypeTypeDescriptionProvider、System.ComponentModel.CustomTypeDescriptor以及System.ComponentModel.PropertyTypeDescriptor類來擴充MetadataType Description,然後使用System.ComponentModel.DataAnnotations.MetadataTypeAttribute特性以在用於描述源類型的中繼資料類型上進行特性設定。以下是這種實現方式的相關代碼:
public class MyAssociatedMetadataTypeTypeDescriptionProvider : AssociatedMetadataTypeTypeDescriptionProvider{ public MyAssociatedMetadataTypeTypeDescriptionProvider(Type type) : base(type) { } public MyAssociatedMetadataTypeTypeDescriptionProvider(Type type, Type associatedMetadataType) : base(type, associatedMetadataType) { } private ICustomTypeDescriptor Descriptor { get; set; } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { if (null == this.Descriptor) this.Descriptor = new MyCustomTypeDescriptor(base.GetTypeDescriptor(objectType, instance)); return this.Descriptor; }}public class MyCustomTypeDescriptor : CustomTypeDescriptor{ public MyCustomTypeDescriptor(ICustomTypeDescriptor wrappedTypeDescriptor) { this.WrappedTypeDescriptor = wrappedTypeDescriptor; } private ICustomTypeDescriptor WrappedTypeDescriptor { get; set; } public override AttributeCollection GetAttributes() { return this.WrappedTypeDescriptor.GetAttributes(); } public override PropertyDescriptorCollection GetProperties() { PropertyDescriptorCollection properties = this.WrappedTypeDescriptor.GetProperties(); List<PropertyDescriptor> list = new List<PropertyDescriptor>(); foreach (PropertyDescriptor descriptor in properties) list.Add(new MyPropertyDescriptor(descriptor)); return new PropertyDescriptorCollection(list.ToArray(), true); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return this.GetProperties(); }}public class MyPropertyDescriptor : PropertyDescriptor{ public MyPropertyDescriptor(PropertyDescriptor wrappedPropertyDescriptor) : base(wrappedPropertyDescriptor) { this.WrappedPropertyDescriptor = wrappedPropertyDescriptor; } private PropertyDescriptor WrappedPropertyDescriptor { get; set; } public override void AddValueChanged(object component, EventHandler handler) { this.WrappedPropertyDescriptor.AddValueChanged(component, handler); } public override bool CanResetValue(object component) { return this.WrappedPropertyDescriptor.CanResetValue(component); } public override Type ComponentType { get { return this.WrappedPropertyDescriptor.ComponentType; } } public override bool IsReadOnly { get { return this.WrappedPropertyDescriptor.IsReadOnly; } } public override object GetValue(object component) { return this.WrappedPropertyDescriptor.GetValue(component); } public override Type PropertyType { get { return this.WrappedPropertyDescriptor.PropertyType; } } public override void RemoveValueChanged(object component, EventHandler handler) { this.WrappedPropertyDescriptor.RemoveValueChanged(component, handler); } public override void ResetValue(object component) { this.WrappedPropertyDescriptor.ResetValue(component); } public override void SetValue(object component, object value) { List<Attribute> attributes = new List<Attribute>(); this.FillAttributes(attributes); foreach (Attribute attribute in attributes) { ValidationAttribute validationAttribute = attribute as ValidationAttribute; if (null == validationAttribute) continue; if (!validationAttribute.IsValid(value)) throw new ValidationException(validationAttribute.ErrorMessage, validationAttribute, component); } this.WrappedPropertyDescriptor.SetValue(component, value); } public override bool ShouldSerializeValue(object component) { return this.WrappedPropertyDescriptor.ShouldSerializeValue(component); } public override bool SupportsChangeEvents { get { return this.WrappedPropertyDescriptor.SupportsChangeEvents; } }}// 以下是使用方式:[MetadataType(typeof(ApplicationElementMetadata))]public partial class ApplicationElement{ static ApplicationElement() { TypeDescriptor.AddProvider( new MyAssociatedMetadataTypeTypeDescriptionProvider( typeof(ApplicationElement)), typeof(ApplicationElement)); }}public class ApplicationElementMetadata{ [Description("Indicates the provider of the Application.")] public string Provider { get; set; }}
然而對於Configuration Section Designer而言,這種繁雜的實現方式已經不複存在。它本身就支援Component Model相關特性的設定,然後會在產生的代碼中添加相應的特性描述,大大減輕了開發人員的負擔。
產生的代碼如下:
Configuration Section Designer應該還有很多不錯的功能,時間關係我也沒有進行深入研究,有興趣的朋友不妨下載一個Configuration Section Designer體驗一下。