做项目时,经常需要在自己设计的类库中使用很多用户配置。虽然在应用程序的App.config和Web应用程序web.config这样的文件里配置也能满足需求,但这样做不仅会让主配置文件的内容变得多、杂,还会让模块依赖主程序的配置文件。
我们知道在VS中,可以在类库项目里添加一种叫做“应用程序配置文件”的文件,这是标准的.NET配置文件,模板自带“configuration”元素,编辑时还会有智能提示。但是怎么在程序代码中使用写在App.config里的配置呢?近日在网上搜了一通,却一无所获。于是只好自已动手!
我以前做的一个项目里,用到过类型的实现方式。可以获取在类库App.config文件中“appSettings”和“conectionStrings”节添加的自定义配置,但是不能自定义配置节。从MSDN上了解到,要想在配置文件中自定义配置节,需要实现一个自定义的ConfigurationSection。两下结合起来,想在类库中用App.config彻底自定义配置的需求就可以实现了。
现在分享出来,希望对看到这篇文章的朋友有所帮助。
第一步:创建项目和类库:
新建一个Windows控制台应用程序“MyDemo”,然后再新建一个C#类库“MyDemo.Config”,并在MyDemo中添加对MyDemo.Config的引用。
第二步:添加引用,新建配置文件:
在MyDemo.Config中先删除除System之外的所有引用,然后添加对System.Configuration库的引用,并新建一个配置文件App.config。
第三步:在MyDemo.Config里面添加一个静态类“ConfigManager”
,代码里这样写:
using System; using System.Configuration; namespace MyDemo.Config { public static class ConfigManager { readonly static bool _Error; static Configuration _AppConfig; static ConfigManager() { string dllPath = string.Format( "{0}\\{1}.dll", AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory, "MyDemo.Config"); try { _AppConfig = ConfigurationManager.OpenExeConfiguration(dllPath); } catch(ConfigurationErrorsException) { _Error = true; } } public static KeyValueConfigurationCollection AppSettings { get { if (_Error) return null; return _AppConfig.AppSettings.Settings; } } public static ConnectionStringSettingsCollection ConnectionStrings { get { if (_Error) return null; return _AppConfig.ConnectionStrings.ConnectionStrings; } } } }
通过AppDomain.CurrentDomain.BaseDirectory和稳定的类库名称,来获取实际运行中该dll文件的具体物理路径,然后通过ConfigurationManager的OpenExeConfiguration方法就能获取到相应的dll.config文件中的配置。
为了保险起见,我增加了一个_Error字段,在静态构造器中使用了try语句,这样能保证程序的顺序运行!
通过这个ConfigManager,可以直接获取在App.config中添加的appSeetings和connectionStrings的配置。
第四步:添加对自定义的配置节的支持
要自定义配置节,需要实现ConfigurationSection。这个其实MSDN上就有很好的例子,我这里做一个简单的实现,比如我要增加这样的配置:
<smtp host="smtp.163.com" mail="abc@163.com" pass="123456"></smtp>
我们来新建一个SmtpSection,继承自ConfigurationSection。重写基类的IsReadOnly()方法,表示这些配置是只读的。
然后我们添加几个只读的属性:Host(string)、Mail(string)、Password(string)、Port(int);get访问器的实现很特殊,要用this[“自定义配置节的attribute名称”]这种方法,还要转换成相应属性的具体类型。所有将在配置中出现的属性,都要加上ConfigurationProperty标记。
来看具体的实现:
using System; using System.Configuration; namespace MyDemo.Config { public class SmtpSection : ConfigurationSection { public override bool IsReadOnly() { return true; } [ConfigurationProperty("host", IsRequired = true)] public string Host { get { return this["host"] as string; } } [ConfigurationProperty("mail", IsRequired = true)] //[RegexStringValidator(可以在这里用正则验证配置文件中的值是否正确)] public string Mail { get { return this["mail"] as string; } } [ConfigurationProperty("pass", IsRequired = true)] public string Password { get { return this["pass"] as string; } } [ConfigurationProperty("port", IsRequired = false, DefaultValue = 25)] [IntegerValidator(MaxValue = 65535, MinValue = 1)] public int Port { get { return (int)this["port"]; } } } }
可以看到,在声明属性的ConfigurationProperty标记时,不但可以指定此属性将在配置文件中出现的的名称,还可以指定默认值、是否必须属性等。字符串还可以使用RegexStringValidator标记来验证用户输入的配置是否合法;数字则可以用IntegerValidator来规定用户输入的数字范围。最贴心的是这些合法性的验证,都是在编译时进行的,而不是在程序运行时。
第五步:在App.Config中添加自定义配置节
新增一个configSections节,在下面添加自定义的section,nam的值是下面的xml元素的开始和结束标记,type的值的格式是这样的“自定义配置节类型的完全名称(包括命名空间), 命名空间”。这里增加一个appSetting和connectionString以便稍后测试。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="smtp" type="MyDemo.Config.SmtpSection, MyDemo.Config" /> </configSections> <smtp host="smtp.163.com" mail="abc@163.com" pass="123456"></smtp> <appSettings> <add key="domain" value="dream.net" /> </appSettings> <connectionStrings> <add name="default" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|NORTHWND.MDF;Integrated Security=True;Connect Timeout=30" /> </connectionStrings> </configuration>
第六步:修改ConfigManager,添加获取自定义配置节smtp的方法
给ConfigManager类增加一个方法,为了能最大限度的重用这个方法,我把方法写成了泛型方法! GetSection<T>(string name);
public static T GetSection<T>(string name) where T : ConfigurationSection { if (_Error) return null; return _AppConfig.GetSection(name) as T; }
第七步:测试
要想使这个配置起使用,前提是App.config文件必须以“dll名称.dll.config”的文件名存在应用程序的执行目录中。生成类库的时候,在类库的输出目录会自动生成这个配置文件,但是每次更新配置文件都要把配置文件复制一遍很麻烦。解决办法就是,把App.config改名。比如上面提到的MyDemo.Config中的App.config就可以改成“MyDemo.Config.dll.config”,然后设置属性“复制到输出目录”为“较新则复制”。这样,每次修改配置文件后,只要重新生成,配置文件就会被自动复制到应用程序的执行目录中。
解决了这个问题,我们在MyDemo中写一段代码测试一下,因为ConfigManager的一些属性和方法的返回值的类型位于System.Configuration命名空间,所以,要使用它必须在程序中也添加对System.Configuration的引用。当然,你也可以改变一下获取appSettings和connectionStrings的方式,直接返回配置的值,而不是配置集合!
using MyDemo.Config; class Program { static void Main(string[] args) { Console.WriteLine(ConfigManager.AppSettings["domain"].Value); Console.WriteLine(ConfigManager.ConnectionStrings["default"].ConnectionString); var smtp = ConfigManager.GetSection<MyDemo.Config.SmtpSection>("smtp"); Console.WriteLine(smtp.Host); Console.WriteLine(smtp.Mail); Console.WriteLine(smtp.Password); Console.WriteLine(smtp.Port); Console.Read(); } }
运行结果:
通过结果,可以确定配置在config里面的设置都已经被顺利的读出来了;这是ConfigurationSection的实现,另外,如果要实现的配置具有复杂数据结构,还要根据需要实现ConfigurationElement,具体实现请参考MSDN的例子
http://msdn.microsoft.com/zh-cn/library/2tw134k3(v=vs.100).aspx
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
PS:可以对例子的ConfigManager做一些改造,使之成为项目配置存取模块(Configuration是支持读写的)。另外,对于一些不想这么麻烦的懒人,我推荐一款免费的VS插件,叫“Configuration Section Designer”,安装插件后会安装一个配置节的设计模板,允许你像设计UML图一样设计你的配置,插件会帮你自动生成具体的实现,非常的实用。
http://www.cnblogs.com/shalves/archive/2013/03/11/use_appconfig_on_csharp_library.html