在一般的项目中,为了使你的代码更加灵活,更方便调整,减少不必要的hard code,我们都在config中添加许多配置信息,一般可以选择.NET自带的配置文件形式app.config或者web项目中的web.config来完成配置工作。
.NET中提供了几个和配置有关的类来支持用完轻松的完成配置文件的读写设置:
System.Configuration.ConfigurationSectionGroup
一般和你项目中使用的Assambly保持1:1的对应关系,这样划分使得结构相对清晰,权责明确。当然你可以不使用它,这样一旦你的Assambly在别的地方要被重用时,找出相应的config信息就变得很困难。
System.Configuration.ConfigurationSection
维护一个相对独立的配置节,使用时需现在<ConfigSections></ConfigSections>节点下声明。我们熟悉的<appSettings></appSettings>以及<connectionStrings></connectionStrings/>就是.NET为我们预留的一个Section。
System.Configuration.ConfigurationElementCollection & System.Configuration.ConfigurationElement
就是Section下具体的配置信息和配置信息的集合了。
下面来看看怎么使用这些类玩转app.config
1.初级使用
最初级的用法当然是使用<appSettings/>,我们在app.config 中添加
<configuration> <appSettings> <add key="MyConfigString" value="Test Config Data"/> </appSettings> </configuration>
访问它
public class AppSettingConfig { public string resultValue; public AppSettingConfig() { this.resultValue = ConfigurationManager.AppSettings["MyConfigString"].ToString(); } } [TestMethod] public void TestAppSettingConfigNode() { AppSettingConfig appCon = new AppSettingConfig(); Assert.AreEqual("Test Config Data", appCon.resultValue); }
没有问题!
我们加个Section来看看如何访问:
<configuration> <configSections> <sectionGroup name="MySectionGroup"> <section name="MyFirstSection" type="System.Configuration.DictionarySectionHandler"/> <section name="MySecondSection" type="System.Configuration.DictionarySectionHandler"/> </sectionGroup> </configSections> <MySectionGroup> <MyFirstSection> <add key="First" value="First Section"/> </MyFirstSection> <MySecondSection> <add key="Second" value="Second Section"/> </MySecondSection> </MySectionGroup> </configuration>
注意我们在section的type中给出了System.Configuration.DictionarySectionHandler,这也限制了我们在具体的ConfigurationElement中只能使用<add key=”” value=””/>的形式,使得我们GetSection()方法返回的是一个IDictory对象,我们可以根据Key来取得相应的值
public class SectionConfig { public string resultValue; public SectionConfig() { System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); IDictionary dic = ConfigurationManager.GetSection("MySectionGroup/MySecondSection") as IDictionary; this.resultValue = dic["Second"].ToString(); } } [TestMethod] public void TestSectionGroupConfigNode() { SectionConfig sc = new SectionConfig(); Assert.AreEqual("First Section", sc.resultValue); }
还是没问题。
2. 中级使用
.NET支持对上述提到的configuration类进行扩展,我们可以定义自己的Section。
继承自基类System.Configuration.ConfigurationSection,ConfigurationSection已经提供了索引器用来获取设置数据。
在类中加上ConfigurationProperty属性来定义Section中的Element:
public class CustomSection:System.Configuration.ConfigurationSection { [ConfigurationProperty("sectionId", IsRequired=true, IsKey=true)] public int SectionId { get { return (int)base["sectionId"]; } set { base["sectionId"] = value; } } [ConfigurationProperty("sectionValue", IsRequired = false)] public string SectionValue { get { return base["sectionValue"].ToString(); } set { base["sectionValue"] = value; } } }
操作此Section,我们将其动态加入app.config中,并读出来:
public class CustomSectionBroker { private CustomSection customSection = null; public void InsertCustomSection() { customSection = new CustomSection(); customSection.SectionId = 1; customSection.SectionValue = "The First Value"; System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.Sections.Add("CustomSection", customSection); config.Save(ConfigurationSaveMode.Minimal); } public int GetCustomSectionID() { System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); CustomSection cs = config.GetSection("CustomSection") as CustomSection; return cs.SectionId; } } [TestMethod] public void TestCustomSection() { CustomSectionBroker cb = new CustomSectionBroker(); cb.InsertCustomSection(); Assert.AreEqual(1, cb.GetCustomSectionID()); }
可以看下现在app.config文件的变化:
<configuration> <configSections> <section name="CustomSection" type="Tonnie.Configuration.Library.CustomSection, Tonnie.Configuration.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <sectionGroup name="MySectionGroup"> <section name="MyFirstSection" type="System.Configuration.DictionarySectionHandler"/> <section name="MySecondSection" type="System.Configuration.DictionarySectionHandler"/> </sectionGroup> </configSections> <CustomSection sectionId="1" sectionValue="The First Value" /> <MySectionGroup> <MyFirstSection> <add key="First" value="First Section"/> </MyFirstSection> <MySecondSection> <add key="Second" value="Second Section"/> </MySecondSection> </MySectionGroup> </configuration>
增加了一个单独的Section,名为"CustomSection",并且包含了我们创建的2个configurationProperty。
我们还可以继续作扩展,现在我们的config中section的部分呈现的是<CustomSection sectionId="1" sectionValue="The First Value" /> ,这样对于复杂的配置信息仍然不方便,我们是不是可以继续扩展,将其变成比较合理的
<CustomSection>
<ChildCustomSectionA childId=1 childValue=”ChildA”></ChildCustomSectionA>
<ChildCustomSectionB childid=2 childValue=”ChildB”></ChildCustomSectionB>
</CustomSection>
这种方式呢? 我们为<ChildCustomSectionA></ChildCustomSectionA>创建扩展自ConfigurationElement类的子类CustomSectionElementA,然后修改CustomSection类中的Property,使得类型不再是int 或 string,而是我们创建的新类CustomSectionElementA.
由于ChildCustomSectionA 和ChildCustomSectionB 的结构相对一致,根据面向对象的开发封闭原则,我们可以先抽象出一个base类,然后让ChildCustomSectionA,ChildCustomSectionB分别继承自此base类,当以后要添加更多的ChildCustomSectionC,ChildCustomSectionD…时,使用这种Template的设计模式,将更加灵活。
public abstract class CustomSectionElementBase:System.Configuration.ConfigurationElement { [ConfigurationProperty("childId", IsRequired=true, IsKey=true)] public int ChildID { get{return (int)base["childId"];} set{base["childId"] = value;} } [ConfigurationProperty("childValue", IsRequired=true)] public string ChildValue { get{return base["childValue"].ToString();} set{base["childValue"] = value;} } } public class CustomSectionElementA:CustomSectionElementBase { public CustomSectionElementA() { base.ChildID = 1; base.ChildValue = "ChildA"; } } public class CustomSectionElementB:CustomSectionElementBase { public CustomSectionElementB() { base.ChildID = 2; base.ChildValue = "ChildB"; } }
完成了ConfigurationElement的实现,我们可以改写我们上一个例子中定义的CustomSection类了:
public class CustomSectionWithChildElement:System.Configuration.ConfigurationSection { private const string elementChildA = "childSectionA"; private const string elementChildB = "childSectionB"; [ConfigurationProperty(elementChildA, IsRequired=true, IsKey=true)] public CustomSectionElementA ChildSectionA { get { return base[elementChildA] as CustomSectionElementA; } set { base[elementChildA] = value; } } [ConfigurationProperty(elementChildB, IsRequired = true)] public CustomSectionElementB ChildSectionB { get { return base[elementChildB] as CustomSectionElementB; } set { base[elementChildB] = value; } } } public class CustomSectionWithChildElementBroker { private CustomSectionWithChildElement customSection = null; public void InsertCustomSection() { customSection = new CustomSectionWithChildElement(); customSection.ChildSectionA = new CustomSectionElementA(); customSection.ChildSectionB= new CustomSectionElementB(); System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.Sections.Add("CustomSectionWithChildElement", customSection); config.Save(ConfigurationSaveMode.Minimal); } public int GetCustomSectionChildAID() { System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); CustomSectionWithChildElement cswe = config.GetSection("CustomSectionWithChildElement") as CustomSectionWithChildElement; return cswe.ChildSectionA.ChildID; } }
红色字体就是修改的地方了,将Property改成我们自定义类的形式.测试代码如下:
[TestMethod] public void TestCustomSectionWithChildElement() { CustomSectionWithChildElementBroker cweb = new CustomSectionWithChildElementBroker(); cweb.InsertCustomSection(); Assert.AreEqual(1, cweb.GetCustomSectionChildAID()); }
看看运行后我们的app.config变成什么样子了:
<configuration> <configSections> <section name="CustomSectionWithChildElement" type="Tonnie.Configuration.Library.CustomSectionWithChildElement, Tonnie.Configuration.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <section name="CustomSection" type="Tonnie.Configuration.Library.CustomSection, Tonnie.Configuration.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <sectionGroup name="MySectionGroup"> <section name="MyFirstSection" type="System.Configuration.DictionarySectionHandler"/> <section name="MySecondSection" type="System.Configuration.DictionarySectionHandler"/> </sectionGroup> </configSections> <CustomSectionWithChildElement> <childSectionA childId="1" childValue="ChildA" /> <childSectionB childId="2" childValue="ChildB" /> </CustomSectionWithChildElement> <CustomSection sectionId="1" sectionValue="The First Value" /> <MySectionGroup> <MyFirstSection> <add key="First" value="First Section"/> </MyFirstSection> <MySecondSection> <add key="Second" value="Second Section"/> </MySecondSection> </MySectionGroup> </configuration>
cool,好像完成了我们的要求。
下面为我们的CustomSectionWithChildElement外面再加一层SectionGroup.
public class CustomSectionGroup : System.Configuration.ConfigurationSectionGroup { [ConfigurationProperty("customSectionA", IsRequired = true, IsKey = true)] public CustomSectionWithChildElement SectionA { get { return base.Sections["customSectionA"] as CustomSectionWithChildElement; } set { this.Sections.Add("customSectionA", value); } } } public class CustomSectionGroupWithChildElementBroker { private CustomSectionWithChildElement customSection = null; public void InsertCustomSectionGroup() { customSection = new CustomSectionWithChildElement(); customSection.ChildSectionA = new CustomSectionElementA(); customSection.ChildSectionB= new CustomSectionElementB(); CustomSectionGroup sectionGroup = new CustomSectionGroup(); System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); if (config.GetSectionGroup("customSectionGroup") == null) config.SectionGroups.Add("customSectionGroup",sectionGroup); sectionGroup.SectionA = customSection; config.Save(ConfigurationSaveMode.Minimal); } public int GetCustomSectionChildAID() { System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); CustomSectionWithChildElement cswe = config.GetSection("customSectionGroup/customSectionA") as CustomSectionWithChildElement; return cswe.ChildSectionA.ChildID; } }
测试一下:
[TestMethod] public void TestCustomSectionGroupWithChildElement() { CustomSectionGroupWithChildElementBroker cweb = new CustomSectionGroupWithChildElementBroker(); cweb.InsertCustomSectionGroup(); Assert.AreEqual(1, cweb.GetCustomSectionChildAID()); }
没问题,看下现在的app.config,是不是更加结构化了:
<configuration> <configSections> <sectionGroup name="MySectionGroup"> <section name="MyFirstSection" type="System.Configuration.DictionarySectionHandler"/> <section name="MySecondSection" type="System.Configuration.DictionarySectionHandler"/> </sectionGroup> <sectionGroup name="customSectionGroup" type="Tonnie.Configuration.Library.CustomSectionGroup, Tonnie.Configuration.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" > <section name="customSectionA" type="Tonnie.Configuration.Library.CustomSectionWithChildElement, Tonnie.Configuration.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </sectionGroup> </configSections> <MySectionGroup> <MyFirstSection> <add key="First" value="First Section"/> </MyFirstSection> <MySecondSection> <add key="Second" value="Second Section"/> </MySecondSection> </MySectionGroup> <customSectionGroup> <customSectionA> <childSectionA childId="1" childValue="ChildA" /> <childSectionB childId="2" childValue="ChildB" /> </customSectionA> </customSectionGroup> </configuration>
3 高级使用
到目前为止可能大家对app.config有了一定的认识了,我们自己可以不断的去扩展.NET Framework提供给我们的类,从SectionGroup,Section,ElementCollection,Element 从上自下的一级一级的组装成符合工程化项目配置文件需要的形式。当遇到可能配置元素的类型属性差不多时,可以抽象出一个base类来。比如可以抽象出Section这一层面的base类,或者ElementCollection,Element这一层的抽象类(可以是抽象的泛型类)来。同时增加泛型来更好的支持扩展。