Enterprise LibraryV1.0-配置应用程序块
配置应用程序块
(不当之处请各位指出)
⑴概述
假设我们的一个应用程序可能会使用同类型的几个的数据库(如使用两个SQL Server),也可能使用几个不同类型的数据库(如同时使用SQL Server和Sybase),还可能现在使用一种(个)数据库,将来很有可能要换成另一种(个),我们一般的解决办法是利用factory pattern,操作数据库的类根据外部的一个配置文件来创建,也就是说我们会在代码中读取外部配置数据,然后利用System.Activator的CreateInstance方法或System.Reflection.ConstructorInfo方法创建具体的操作数据库的类。配置应用程序块就是用了这种想法,具体功能的Provider(如操作数据库、写日志、执行权限管理等)在运行时根据外部配置数据生成,这里外部配置数据不仅仅是Xml格式的,还可以是注册表、数据库等格式,配置应用程序块读取某种格式的外部配置数据,转换成一个应用程序可操作的类(就是前面所说的8个Settings类)。由于外部配置数据格式可能不同,配置应用程序块要首先创建一个能够读取外部配置数据格式的对象,将外部配置数据读取过来,如果有必要,还需将读取过来的某格式的配置数据,转换成我们需要的类,这就要用到转换器。创建何种读写外部配置数据对象和类型转换器,也是要根据配置数据的,这样的配置数据放到了主配置文件App.config或Web.config中。如下所示:
<configurationSection name="issueConfiguration" encrypt="true">
<storageProvider xsi:type="XmlFileStorageProviderData" name="XML File Storage Provider" path=".\config\issueConfiguration.config" />
<dataTransformer xsi:type="XmlSerializerTransformerData" name="Xml Serializer Transformer">
<includeTypes />
</dataTransformer>
</configurationSection>
看看storageProvider元素的含义,它有三个Attribute,xsi:type、name、path,我们知道在XSD定义中xsi:type="XmlFileStorageProviderData"是指明元素类型的,这里指外部配置数据是Xml格式的,name指明配置的名称,path指明外部配置数据的位置,在XmlFileStorageProviderData 类中有说明读取Xml格式外部配置数据的代码:typeof(XmlFileStorageProvider).AssemblyQualifiedName;从而说明读取Xml格式外部配置数据的类是XmlFileStorageProvider。
⑵ConfigurationSettings类代表了配置元数据
在配置应用程序块中ConfigurationSettings类代表了对外部配置数据的描述,注意它不是代表外部配置数据,代表的是外部配置数据的元数据,比较形象的展现如下图:
(图一:)ConfigurationSettings类进行XML串行化后,得到的内容
显然ConfigurationSettings所代表的内容位于元配置文件(App.config/Web.config)中。配置应用程序块用ConfigurationSettings的提供的信息,读取外部配置数据,生成相应的Provider,对外提供数据操作、日志记录、权限管理等功能。实际上配置应用程序块并不是直接使用ConfigurationSettings,而是用了ConfigurationSettings的一个Wrapper:RuntimeConfigurationView,这应该是Facade Pattern(不知对不对?),同理,其他应用程序块的配置信息也有对应的Wrapper,如下:
Security.SecurityConfigurationView
Caching.CachingConfigurationView
Configuration.RuntimeConfigurationView
Data.DatabaseConfigurationView
ExceptionHandling.ExceptionHandlingConfigurationView
Logging.LoggingConfigurationView
Security.Cryptography.CryptographyConfigurationView
他们都对应了相应的Settings类,这样Factory类就不用直接操作Settings类了,而是操作其Facade,即各个XXXXConfigurationView。
⑶ConfigurationSettings对象的建立过程
当我们要使用某项功能(数据操作、日志记录、权限管理等功能)时,配置应用程序块首先建立起ConfigurationSettings对象,从而使RuntimeConfigurationView可以实例化,配置工厂类根据RuntimeConfigurationView创建StorageProvider和TransformerProvider,从而在两者的配合下,读取外部配置文件,生成各个配置数据的Settings类,使XXXXConfigurationView可以实例化,工厂类根据XXXXConfigurationView创建相应的功能Provider,这样我们就可以开始消费各种功能了(注意,当我们使用自定义的配置数据时,读取我们自己命名外部配置文件,生成配置数据的Settings类就,算完成配置数据的读取了) 。
下面我们先看配置应用程序块是如何建立ConfigurationSettings对象的。
1、 当我们用ConfigurationManager读写配置数据(非配置元数据)时, 会首先得到一个ConfigurationManager.Current对象, 这将导致调用ConfigurationManager的构造函数。
2、 在ConfigurationManager的构造函数中, 会首先创建一个ConfigurationContext对象。
3、而 创建ConfigurationContext对象, 则首先要求要有一个ConfigurationBuilder对象, 这样,当初始化一个ConfigurationManager对象时,主要的工作在与建立一个ConfigurationBuilder对象。
4、 建立一个ConfigurationBuilder对象的步骤是:
-----------得到元配置文件(App.config/Web.config)的文件名。
----------根据元配置文件(App.config/Web.config)的文件名, 建立一个ConfigurationFile对象, 用于建立ConfigurationSettings对象。
----------建立ConfigurationSettings对象,这是上面两步的最终目的,l也是ConfigurationBuilder对象创建时的主要任务,还有一些建立缓存、建立通知等任务。
ConfigurationSettings对象具体创建过程是ConfigurationBuilder对象调用ConfigurationFile的如下方法 configFile.GetConfig(ConfigurationSettings.SectionName) as ConfigurationSettings,读取元配置文件中的enterpriselibrary.configurationSettings元素,得到一个XmlElement对象,然后对此XmlElement对象进行Deserialize便得到了ConfigurationSettings对象,XmlSerializer的Deserialize方法是在临时目录生成代码,然后将代码编译成一个Assembly,加载此Assembly,创建一个ConfigurationSettings对象,并给其各个字段、属性赋值,要注意建立后也给各个属性赋值了。那么是如何读取元配置文件中的enterpriselibrary.configurationSettings元素,得到一个XmlElement对象的呢?这里是使用了Xpath查询,其经典用法是:
XmlDocument doc = new XmlDocument();//XPathDocument doc = new XPathDocument(fileName);
XPathNavigator navigator = doc.CreateNavigator();
XPathExpression expr = navigator.Compile("XXXXXXXXXXXXXXXXXX");
XmlNamespaceManager nsmngr = new XmlNamespaceManager(navigator.NameTable);
xnm.AddNamespace("XXXXXXXXXX", "urn:XXXXXXXXXXX");
expr.SetContext(nsmgr);
XPathNodeIterator iterator = navigator.Select(expr);
while (iterator.MoveNext())
{
//XmlNode node = ((IHasXmlNode) iterator.Current).GetNode();
//如果是用XPathDocument建立导航器,则要用下面这句,先复制一个导航器,不要用原来的
//XPathNavigator nav2 = iterator.Current.Clone();
//具体功能代码写在此处;
}
当你用Xpath查询时,你可以复制上面的代码,然后根据你要求进行修改,就可以用了,我们可以看到配置应用程序块也是这样用的,不过它这里没有用循环while而是用了if,很好理解,因为元配置文件(App.config/Web.config)中只能搜索到一个节点。那么为什么不用SelectSingleNode方法呢?岂不更简单,可能是性能问题(具体我也不知道,SelectSingleNode要先调用SelectNodes,那么为什么不用doc.SelectSingleNode("enterpriselibrary.configurationSettings[position() = 1]");呢?)。
到这里我们就得到了enterpriselibrary.configurationSettings元素的XmlElement对象,下面一步就是进行Deserialize了。代码很简单,就不用解释了。
当ConfigurationSettings对象建立起来以后,我们就可以读写配置元数据了。ConfigurationBuilder有如下5个最常用的方法,
public object ReadConfiguration(string sectionName);
public void WriteConfiguration(string sectionName, object configValue);
public ConfigurationSettings ReadMetaConfiguration();
public ConfigurationSectionData ReadMetaConfiguration(string sectionName);
public void WriteMetaConfig(ConfigurationSectionData configurationSectionData);
public void WriteMetaConfiguration(ConfigurationSettings configurationSettings);
当我们具体使用时并不是直接使用ConfigurationBuilder,而要用ConfigurationContext,这应该也是Facade pattern的体现,所以ConfigurationBuilder干脆设计成为internal的,让外部Assembly无法访问,只要用ConfigurationContext就行了,如果你不读写配置元数据,只是读写外部配置数据就直接用ConfigurationManager好了,ConfigurationManager就是在ConfigurationContext的基础上屏蔽掉了读写配置元数据的功能。同时,得到ConfigurationSettings对象后,对其访问时,不是直接用它,而是要用RuntimeConfigurationView。
至此我们就介绍完了ConfigurationSettings对象的创建过程,我们接着介绍读写外部配置数据。
⑷读写外部配置数据
读写外部配置数据实际上就是执行ConfigurationBuilder的ReadConfiguration()和WriteConfiguration()方法。其执行过程我们看看下面的代码注释就行了。
{
ValidateSection(sectionName);
// 这里定义的变量configurationSection代表的是具体配置数据类,如ConfigurationQuickStart.EditorFontData或DatabaseSettings、LoggingSettings、ExceptionHandlingSettings、SecuritySettings、CacheSettings等。
// 如果在缓存理就直接返回
object configurationSection = sections.GetSection(sectionName);
if (IsConfigurationSectionCached(configurationSection))
{
return configurationSection;
}
IStorageProviderReader storageProviderReader = CreateStorageProvider(sectionName);
// 这里定义的变量configurationSettings代表的是具体配置数据中的配置项物理格式的数据。如果为XML格式的配置数据,
// configurationSettings的类型就是System.Xml.XmlElement.
// 如:<xmlSerializerSection type=\"ConfigurationQuickStart.EditorFontData, ConfigurationRuntimeData,
//Version=1.1.0.0, Culture=neutral, PublicKeyToken=null\">
//<EditorFontData xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
//<Name>Microsoft Sans Serif</Name><Size>9.25</Size><Style>0</Style></EditorFontData></xmlSerializerSection>
// 下面这句是核心功能,实际读取配置数据的功能由Provider完成,这是由Enterprise Library的设计思路
object configurationSettings = storageProviderReader.Read();
if (configurationSettings == null)
{
//return null;
// by this time, an exception should have been thrown by the storage provider
// this is a fail safe
throw new ConfigurationException(SR.ExceptionXmlStorageSectionNotFoundError(sectionName, ""));
}
ITransformer transformer = CreateTransformer(sectionName);
if (transformer != null)
{
// 这句代码完成的功能就是将配置数据由代表物理格式配置数据的类转变为代表应用程序直接访问的配置类。
// 如:XmlNode -----> EditorFontData或DatabaseSettings、LoggingSettings、ExceptionHandlingSettings、SecuritySettings、CacheSettings等。
configurationSection = transformer.Deserialize(configurationSettings);
}
else
{
configurationSection = configurationSettings;
}
// 增加到缓存中
AddSection(sectionName, configurationSection, storageProviderReader);
return configurationSection;
}
{
// 验证有效性
ValidateSection(sectionName);
// 注册写前事件
ConfigurationChangingEventArgs args = CreateConfigurationChangingEventArgs(sectionName, configValue);
OnConfigurationChanging(args);
if (!args.Cancel)
{
// 创建编写器Provider
IStorageProviderWriter configStorageWriter = GetConfigurationStorageWriter(sectionName);
// 将要保存的值转换成Provider可识别的格式,具体何种格式是由配置元数据决定的
object writeData = GetSerializedDataToWrite(sectionName, configValue);
ConfigurationWriterActionCommand writerActionCommand = new ConfigurationWriterActionCommand(configStorageWriter, writeData);
// we need to setup the section in case write is called first, in this case
// if we don't add, then the watcher is never created :(
// 如果配置节尚不存在就添加此配置数据
if (!sections.ContainsSection(sectionName))
{
AddSection(sectionName, configValue, configStorageWriter);
}
// 如果配置节已存在就更新此配置数据
sections.UpdateSection(sectionName, writerActionCommand, configValue);
// 注册写完成事件
ConfigurationChangedEventArgs changedArgs = new ConfigurationChangedEventArgs(currentConfigFileName, sectionName);
OnConfigurationChanged(changedArgs);
}
}
⑸配置应用程序块的使用步骤
配置应用程序块主要是用来读取应用程序配置数据的,包括我们自定义的外部配置数据,在具体应用时并不是手动去写配置数据(比如一个Xml格式的配置文件),而是建立一个自定义配置数据类,配置应用程序块自己根据这个配置类创建、读写配置数据(如果配置数据为Xml格式,这个Xml格式的配置文件的创建、读写对我们来说是透明的,我们不要去修改这个文件。再说了,如果这个文件加密了,想改都难)。我们只要维护这个配置数据类就行了。这里需要注意的就是建立这个配置类时必须要由一个默认的构造方法,这是因为XmlSerializer在串行化和反串行化一个对象时都会根据默认的构造函数先创建此对象(因为说不准会用哪个构造函数,就用了默认的构造函数,这个应该是最好的选择了)。下面先简要介绍一下使用步骤。
l第一步:建立一个自定义配置数据类。
第二步:用配置控制台设置配置数据。
第三步:用代码访问配置数据。
这里假设我们已经建立了项目,并且已经有了Web.config后者App.congfig,如果你是在做WinForm应用程序,而且还没有App.congfig就先建一个。
1、建立自定义配置类
自定义配置数据类的具体内容根据项目的要求决定,在项目开发中自定义配置数据类可能会不断修改。举个简单例子:
Public class MySettings
{
// 默认的构造函数(当然处理默认的构造函数,你完全可以重载更多的构造函数,但默认的不能少)
Public MySettings() {}
// Fields
Private string orderName;
Private int id;
// properties
Public string OrderName
{
Get{ return this. orderName ; }
Set{ this. orderName = value; }
}
Public int Id
{
Get{ return this. id; }
Set{ this. id= value; }
}
}
自定义配置数据类其内部也可以有自定义的类,只要是XmlSerializer能够串行化的类就行,这样我们就可以设置一些复杂的配置数据,一般来说XmlSerializer能够串行化大部分类,但有些是不能串行化的,如DataReader,这样的类串行化也没有现实意义。
再举个例子:
Public class MySettings
{
// 默认的构造函数(当然处理默认的构造函数,你完全可以重载更多的构造函数,但默认的不能少)
Public MySettings() {}
// Fields
Private string orderName;
Private OrderItem orderItem;
// properties
Public string OrderName
{
Get{ return this. orderName ; }
Set{ this. orderName = value; }
}
Public OrderItem OrderDetailItem
{
Get{ return this. orderItem; }
Set{ this. orderItem = value; }
}
}
当然,这里我们也要定义一个OrderItem类,定义方法跟MySettings类差不多,就不赘述了。上面这两个仅仅是非常简单的自定义配置数据类,我们可以写非常复杂的自定义配置数据类。可以综合应用实体类、集合类写出非常复杂的配置类(具体可参看其他应用程序块的配置数据类XxxxSettings的写法)。基本上想要多复杂的就能写出多复杂的,因此完全可以满足我们应用程序的配置要求。
当写完了配置类后就可以用配置控制台进行配置,从而可以操纵我们的配置类了。
2、用配置控制台设置配置应用程序块
①添加配置应用程序块。
②添加一个Configuration Section,取一个有意义的名字,如Project Configuration。将来读写配置数据就靠这个Configuration Section的名字。
③给这个Configuration Section添加一个XML File Storage Provider。设置其FileName属性,用于指定配置数据保存的外部配置文件,如project.config。
④给这个Configuration Section添加一个Xml Serializer Transformer。
设置完毕,保存所有设置。这样Web.config/App.config文件将会发生变化,并生成一个新的配置文件project.config。不过此时project.config是个空文件,里面什么都没有,而且,我们不需要添加、修改这个文件的任何内容,而是由程序去控制,你可以认为配置数据的保存是透明的。
3、用代码访问配置数据
写好配置类,设置好配置文件后,就可以读写配置数据了,不过在读之前要先写一次,要不然外部配置文件project.config是个空文件,你什么也读不出来。写一次后外部配置文件里就要扑内容了。
写配置数据:ConfigurationManager.WriteConfiguration("issueConfiguration ", settings);
注意WriteConfiguration 方法,有两个参数第一个就是我们前面提到的配置节名称,第二个就是自定义配置类的对象,这样,给我们的感觉就好像是将配置数据写到这个配置节里去了,这样理解完全是正确的,程序对此的处理是透明的。
读配置数据:issueTracerSettings settings = ConfigurationManager.GetConfiguration("issueConfiguration") as IssueTracerSettings;
GetConfiguration方法就是一个参数,即我们定义的配置节名称。注意别忘了进行强制类型转换。
这样就完成了全部工作,不过现在应用程序都讲究安全,配置数据应该加密。
4、加密配置数据
Configuration Application Block----->Encrypting Settings点击右键,NewàFile Key Algorithm Storage Provider,将出现配置向导,只要根据向导设置就行了。在向导的最后将创建一个Key Algorithm Pair File,这个文件保存有Secure Key,这是加密和解密用的对称密钥,我们要妥善保管。如果你选择DPAPI保护,密钥将由系统保管,安全性更高,其缺点是只能在本机上使用。这就要求程序部署后,在本机上设置。