System.Configuration.ConfigurationSettings 类
相信大家对这个类都不陌生吧。 ConfigurationSettings类重要的方法是(在我下面的分析中,方法也包括属性):
AppSettings属性 用于获取 元素配置节中的配置设置。
GetConfig方法 返回用户定义的配置节的配置设置。
在我们的项目开发中,我们经常通过 ConfigurationSettings.AppSettings["myKey"] 的方法来获取 web.config 配置项上 appSettings 的配置值。调用这个ConfigurationSettings.AppSettings["myKey"]索引器我们就可以获取到 web.cofing 配置项 appSettings 的配置值,这太方便了。
如果要我们设计一个这样的功能的时候,我们会有什么想法呢。 我的想法大概的是这样的:
1. 加载 web.config 配置文件的内容
2. 分析 web.config 配置文件配置项 appSettings 节点的内容,并加载到配置项管理类中。
3. 配置项管理类中应该有一个索引器,方便外部系统访问。
让我们来分析大师们是如何实现这个类的。看看大师级人物的代码和设计思路有何高明之处。
//ConfigurationSettings类的定义public sealed class ConfigurationSettings{}
C# 关键字 sealed 表明此类是不能被继承的。
//静态构造函数static ConfigurationSettings(){_initState = InitState.NotStarted;_initLock = new object();}
一个类最先运行的代码段就是静态构造函数,并且对于整个程序域而言静态构造函数只运行一次。 C#关键字 static 加上类名称的方法函数就是静态构造函数。 对于一个类来说,只能有一个静态构造函数。 静态构造函数的作用主要是初始化静态变量。 以 C#关键字 static 约束的类方法里面的代码都只能调用静态变量或者静态方法,静态属性等。
静态方法:C#关键字 static 约束的方法就是静态方法(有些教材可能会称为类方法),里面的代码都只能调用 静态变量或者静态方法,静态属性等。
//静态变量的定义代码private static object _initLock;
C#关键字 static 表明此变量为静态变量。
//构造函数private ConfigurationSettings(){}
发现上面的构造函数跟我们平时所写的类的构造函数有什么不同吗?
对了,就是访问权限的约束关键字 private,平时构造函数的约束关键字都是 public 的。 那么将构造函数访问权限设置为 private有什么目的呢?
1. 防止别人的代码通过 new 操作生成对象实例。
如:System.Configuration.ConfigurationSettings config = new System.Configuration.ConfigurationSettings();
你会发现上面的代码编译不通过,原因就是访问了 private 的构造函数,当然编译不通过啦!
2. 保证一个类仅有一个实例。
这里就是设计模式中的 Singleton 单件模式了,设置构造函数的访问权限为 private 是实现 Singleton 模式的前提
//AppSettings静态只读属性public static NameValueCollection AppSettings{get{ReadOnlyNameValueCollection config = (ReadOnlyNameValueCollection) GetConfig("appSettings");if (config == null){config = new ReadOnlyNameValueCollection(newCaseInsensitiveHashCodeProvider(CultureInfo.InvariantCulture), newCaseInsensitiveComparer(CultureInfo.InvariantCulture));config.SetReadOnly();}return config;}}
通过上面的代码我们可以知道,此属性为静态只读属性(static 关键字,只有 get 操作,而没有 set 操作)。 因为 NameValueCollection 类定义了索引访问器,平时我们都是这样写:
ConfigurationSettings.AppSettings["myKey"]
对于["myKey"]这种使用 []号访问的索引器,我们下面分析NameValueCollection类时再说明索引器。
ReadOnlyNameValueCollection config = (ReadOnlyNameValueCollection) GetConfig("appSettings");
注意到参数的值是 appSettings 了吗? 是不是跟我们 web.config 里面的 appSettings 的配置节点项有关联呢?他们有什么关系吗?我们往下看。
这段代码调用了ConfigurationSettings类的另外一个静态方法,代码如下:
public static object GetConfig(string sectionName)//当然这时 sectionName == "appSettings"{if ((sectionName == null) || (sectionName.Length == 0))//判断 string 的值是不是为Empty时,应该用 sectionName.Length == 0 来判断{return null;}if (_initState < InitState.Usable){EnsureConfigurationSystem();}
if (_initError != null){throw _initError;}return _configSystem.GetConfig(sectionName);}
代码段:
if (_initState < InitState.Usable){EnsureConfigurationSystem();}
InitState 只是一个私有的枚举类型 enum:
private enum InitState{NotStarted,Started,Usable,Completed}
刚才ConfigurationSettings类的静态构造函数是设置 initState = InitState.NotStarted; 那么第一次运行时,肯定会执行 EnsureConfigurationSystem()方法了,我们接着看看代码的实现
private static void EnsureConfigurationSystem(){lock (_initLock){if (_initState < InitState.Usable){_initState = InitState.Started;try{_configSystem = new DefaultConfigurationSystem();_initState = InitState.Usable;}catch (Exception exception){_initError = exception;_initState = InitState.Completed;throw;}}}}_configSystem = new DefaultConfigurationSystem();private static IConfigurationSystem _configSystem;
_configSystem 是一个接口变量。 先看看接口 IConfigurationSystem 的定义:
public interface IConfigurationSystem{// Methodsobject GetConfig(string configKey);void Init();}
接着我们跟踪实现了 IConfigurationSystem 接口的 DefaultConfigurationSystem 类看看 类的定义:
internal class DefaultConfigurationSystem : IConfigurationSystem{}
C# 关键字 internal 表明此类只能被当前的 dll 里面的类使用。 顺便提一提 protected internal 这样的二个关键字的约束。它表明这个只能被当前 dll 里面的类使用或者不是当前 dll 里面的子类使用,记得是 或者 的关系 我们还是先从这个类的构造函数分析开始:
internal DefaultConfigurationSystem(){}
这里的构造函数使用 internal,并不是像 ConfigurationSettings 类构造函数的 private 。 它的访问权限比 ConfigurationSettings 的类的松一点,允许当前 dll 里面的类可以通过 new 操作来生成多个 DefaultConfigurationSystem 实例。 所以这里才有上面的 _configSystem = new DefaultConfigurationSystem(); 代码调用。
重要方法GetConfig的部分关键代码内容:
object IConfigurationSystem.GetConfig(string configKey)//当然这里还是 configKey == "appSettings"{if (!this._isAppConfigInited){this.EnsureInit(configKey);}ConfigurationRecord record = null;if (record != null){return record.GetConfig(configKey);}return null;}
接下来我们就要分析 EnsureInit 方法。
private void EnsureInit(string configKey){try{ConfigurationRecord record = new ConfigurationRecord();bool flag2 = record.Load(this._machineFilename);//加载配置文件信息,这里是加载 machine.config 的信息,并不是 web.config的信息this._machineConfig = record;//....省略}catch (Exception exception){this._initError = exception;ConfigurationSettings.SetInitError(this._initError);this._isMachineConfigInited = true;this._isAppConfigInited = true;throw;}finally{lock (this){ConfigurationSettings.CompleteConfigInit();Monitor.PulseAll(this);}}}
这里只是加载 .Net Framework 的 machine.config 全局配置信息,在这个配置信息项中,你可以找到这样的配置项。
<configuration><configSections><section name="appSettings" type="System.Configuration.NameValueFileSectionHandler, System"/></configSections><configuration>
这个配置项是什么意思呢? name="appSettings"这里表示自定义的配置项键,也就是 appSettings type = "System.Configuration.NameValueFileSectionHandler, System" 表示 appSettings 这个自定义配置键的处理类是 System.Configuration.NameValueFileSectionHandler,后面的 System表示此类所在的 dll的名称。
System.Configuration.NameValueFileSectionHandler这个类必须实现 IConfigurationSectionHandler 这个接口。下面的代码就会调用这个接口里面的方法来获取配置数据。
通过调用 ConfigurationRecord.GetConfig -->> ConfigurationRecord.ResolveConfig -->> ConfigurationRecord.Evaluate
private object Evaluate(string configKey)// 当然这里的值还是 appSettings{IConfigurationSectionHandler factory = this.GetFactory(configKey); //工厂模式object config = (this._parent != null) ? this._parent.GetConfig(configKey) : null;string[] keys = configKey.Split(new char[] { '/' });XmlTextReader reader = null;object obj3 = null;try{//加载我们的 web.config 文件reader = this.OpenXmlTextReader(this._filename);//调用自定义配置项处理类的接口方法获取数据。下面分析。obj3 = this.EvaluateRecursive(factory, config, keys, 0, reader);}catch (ConfigurationException)//catch 的用法,先获取自定义的异常类,这才是好方法。{throw;}catch (Exception exception){throw this.TranslateXmlParseOrEvaluateErrors(exception);}finally{if (reader != null){reader.Close();}}//省略.....return obj3;}
接下来的 GetFactory 的方法就是关键啦,为我们以后写自定义配置项类做参考了。
private IConfigurationSectionHandler GetFactory(string configKey){if ((this._factories != null) && this._factories.Contains(configKey)){object obj2 = this._factories[configKey];if (obj2 == RemovedFactorySingleton){return null;}IConfigurationSectionHandler handler = obj2 as IConfigurationSectionHandler;//as 运算符用于执行可兼容类型之间的转换。//as 运算符类似于类型转换,所不同的是,当转换失败时,as 运算符将产生空,而不是引发异常。if (handler == null){string typeName = (string) obj2;obj2 = null;new ReflectionPermission(PermissionState.Unrestricted).Assert();try{Type c = Type.GetType(typeName);if (c != null){if (!typeof(IConfigurationSectionHandler).IsAssignableFrom(c)){throw new ConfigurationException(SR.GetString("Type_doesnt_implement_IConfigSectionHandler", new object[] { typeName }));}obj2 = Activator.CreateInstance(c, BindingFlags.CreateInstance| BindingFlags.NonPublic | BindingFlags.Public| BindingFlags.Instance, null, null, null);//Activator 就是 Net Framework 著名的反射机制的实现类啦。//Activator.CreateInstance可以动态创建某种类型的实例对象。//这里,它创建System.Configuration.NameValueFileSectionHandler 类对象啦
}}catch (Exception exception){throw new ConfigurationException(exception.Message, exception);}finally{CodeAccessPermission.RevertAssert();}//省略}return handler;}if (!this._factoriesNoInherit && (this._parent != null)){return this._parent.GetFactory(configKey);}return null;}
接下来的 EvaluateRecursive 方法将会调用 System.Configuration.NameValueFileSectionHandler 类的接口方法
Create private object EvaluateRecursive(IConfigurationSectionHandler factory, object config, string[] keys, int iKey, XmlTextReader reader){string text = keys[iKey];int depth = reader.Depth;do{if (!reader.Read()){break;}}while (reader.NodeType != XmlNodeType.Element);while (reader.Depth == (depth + 1)){if (reader.Name == text){if (iKey < (keys.Length - 1)){config = this.EvaluateRecursive(factory, config, keys, iKey + 1, reader);continue;}CreatePermissionSetFromLocation(this._filename).PermitOnly();try{int line = reader.LineNumber;try{ConfigXmlDocument document = new ConfigXmlDocument();document.LoadSingleElement(this._filename, reader);config = factory.Create(config, null, document.DocumentElement);//调用 System.Configuration.NameValueFileSectionHandler 类的方法了,//下面要进入 System.Configuration.NameValueFileSectionHandler 类的分析了。}catch (ConfigurationException){throw;}catch (XmlException){throw;}catch (Exception exception){throw new ConfigurationException(SR.GetString("Exception_in_config_section_handler"), exception, this._filename, line);}continue;}finally{CodeAccessPermission.RevertPermitOnly();}}this.StrictSkipToNextElement(reader);}return config;}
IConfigurationSectionHandler 接口成员
由所有配置节处理程序实现,以分析配置节的 XML。返回的对象被添加到配置集合中,并通过 GetConfig 访问。
object Create(object parent,object configContext,XmlNode section);
IConfigurationSectionHandler 接口是我们写自定义配置节点项时必须要实现的接口。下面再说。
NameValueFileSectionHandler 类的定义:
public class NameValueFileSectionHandler : IConfigurationSectionHandler{// Methodspublic NameValueFileSectionHandler();public object Create(object parent, object configContext, XmlNode section);}
NameValueFileSectionHandler.Create 方法的实现非常简单,就是获取 参数中 XmlNode section 的数据,通过 key 和 value 对应的关系 保存到 私有的 HashTable类型的变量中 section 的参数值大概是这样的
<appSettings><add key="ConnectionString" value="xxxxxxxxxx" /><add key="__SystemID__" value="xxxxxxxxxx" /></appSettings>
分析了 System.Configuration.ConfigurationSettings 类 后,来看看如何实现我们自定义的配置节点管理。 下面的示例代码我采用 VS.NET 的企业代码示例 Duwamish 项目来说明。
第一步,我们要自定义配置节点,web.config 配置节点示例如下:
<configuration><configSections><section name="DuwamishConfiguration"type="Duwamish7.Common.DuwamishConfiguration,Duwamish7.Common" /></configSections></configuration>
<DuwamishConfiguration> //注意这里就是自定义配置项节点了<add key="Duwamish.DataAccess.ConnectionString"value="server=.;User ID=sa;Password=password;database=Duwamish;Connection Reset=FALSE" /><add key="Duwamish.Web.EnablePageCache" value="True" /><add key="Duwamish.Web.PageCacheExpiresInSeconds" value="3600" /><add key="Duwamish.Web.EnableSsl" value="False" /></DuwamishConfiguration>
第二步,让我们的自定义配置节点处理类实现IConfigurationSectionHandler接口。 在这里是 dll名称为 Duwamish7.Common的Duwamish7.Common.DuwamishConfiguration的类要实现这个接口代码:
public Object Create(Object parent, object configContext, XmlNode section){NameValueCollection settings;try{NameValueSectionHandler baseHandler = new NameValueSectionHandler();settings = (NameValueCollection)baseHandler.Create(parent, configContext, section);}catch{settings = null;}if ( settings == null ){dbConnectionString = DATAACCESS_CONNECTIONSTRING_DEFAULT;pageCacheExpiresInSeconds = WEB_PAGECACHEEXPIRESINSECONDS_DEFAULT;enablePageCache = WEB_ENABLEPAGECACHE_DEFAULT;enableSsl = WEB_ENABLESSL_DEFAULT;}else{dbConnectionString = ApplicationConfiguration.ReadSetting(settings, DATAACCESS_CONNECTIONSTRING, DATAACCESS_CONNECTIONSTRING_DEFAULT);pageCacheExpiresInSeconds = ApplicationConfiguration.ReadSetting(settings, WEB_PAGECACHEEXPIRESINSECONDS, WEB_PAGECACHEEXPIRESINSECONDS_DEFAULT);enablePageCache = ApplicationConfiguration.ReadSetting(settings, WEB_ENABLEPAGECACHE, WEB_ENABLEPAGECACHE_DEFAULT);enableSsl = ApplicationConfiguration.ReadSetting(settings, WEB_ENABLESSL, WEB_ENABLESSL_DEFAULT);}return settings;}
第三步,如何启动我们的自定义配置节点处理类:
System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");
到此,我们就搞定了自定义配置节点管理了。自定义配置节点管理可以避免将很多的配置信息都放到 appSettings 节点中,便于管理。通过类来管理配置信息,可以在多个项目中重用,我们也可以写一个基类,来获取统一的配置项键管理。如在我们的项目中差不多每个 web.config 都会有
<add key="__SystemID__" value="xxxx" /><add key="__SystemName__" value="xxxx" /><add key="__SystemPassword__" value="xxxx" />
如果我们的基类提供获取这三个对应 key值的配置项,其它的项目需要扩展时,只须继承我们的配置节点管理基类就行了.