.NET 2.0 配置和提供程序模型






4.44/5 (7投票s)
使用 .NET 2.0 配置功能为您的应用程序构建可插拔的提供程序框架。
引言
本文展示了如何在构建支持可插拔提供程序模型的应用程序时使用 .NET 2.0 配置类。
想象一下,我们为一家名为 MainCompany Inc. 的公司工作,并且我们正在开发一个进行某些数据处理的复杂应用程序。我们想要一个开放的架构,允许其他开发人员实现可以
- 轻松插入主应用程序的提供程序
- 支持任意数据源
下图概述了我们的架构应该是什么样子
 
 
主应用程序开发人员事先不知道可以使用的数据源的性质,但可能会指定其使用数据的方式。为简单起见,我们将假定主应用程序只需要偶尔从每个数据源获取一些文本数据。
配置文件
以下是一个示例配置文件,它定义了两个提供程序和三个数据源
<configuration>
  <configSections>
    <!-- Define custom configuration section group here -->
    <sectionGroup name="dataSourceProviders">
      <!-- Define custom configuration sections -->
      <section name="firstProviderDataSources" 
            type="FirstCompany.ProviderDemo.DataSource.ConfigurationSection, 
            FirstCompany.ProviderDemo.FirstProvider"/>
      <section name="secondProviderDataSources" 
            type="SecondCompany.ProviderDemo.DataSource.ConfigurationSection, 
            SecondCompany.ProviderDemo.SecondProvider"/>
    </sectionGroup>
  </configSections>
  <!-- This is our custom configuration section group -->
  <dataSourceProviders>
    <!-- Custom configuration sections: each defines a set of data sources 
    managed by a single provider -->
    <firstProviderDataSources type="FirstCompany.ProviderDemo.DataSource.DataSource, 
        FirstCompany.ProviderDemo.FirstProvider">
      <!-- Data sources managed by first provider -->
      <firstProviderDataSource name="DataSourceA" 
        connectionString="server=box-a;database=SomeDatabase"/>
      <firstProviderDataSource name="DataSourceB" 
        connectionString="server=box-b;database=SomeOtherDatabase"/>
    </firstProviderDataSources>
    <secondProviderDataSources type="SecondCompany.ProviderDemo.DataSource.DataSource, 
            SecondCompany.ProviderDemo.SecondProvider">
      <!-- Data sources managed by second provider -->
      <secondProviderDataSource name="DataSourceC" sourceMachine="192.168.0.1"/>
    </secondProviderDataSources>
  </dataSourceProviders>
</configuration>
如果您想熟悉配置节、配置节组和配置元素,可以查看Jon Rista 的精彩文章。以下是配置文件的简要概述。
- 定义一个名为 dataSourceProviders的自定义配置节组,其中包含由相应类处理的自定义配置节firstProviderDataSources和secondProviderDataSources
- FirstCompany.ProviderDemo.DataSource.ConfigurationSection
- SecondCompany.ProviderDemo.DataSource.ConfigurationSection
- 添加 firstProviderDataSources配置节,其中包含两个元素,每个元素都指示应用程序创建一个FirstCompany.ProviderDemo.DataSource.DataSource类的实例,并将connectionString 参数传递给新创建的数据源。Name属性用作数据源的唯一 ID,主应用程序使用它来标识数据源。
- 添加 secondProviderDataSources配置节,其中包含一个元素。该元素指示应用程序创建一个SecondCompany.ProviderDemo.DataSource.DataSource类的实例,并将sourceMachine 参数传递给新创建的数据源。Name属性用作数据源的唯一 ID,主应用程序使用它来标识数据源。
需要理解的关键点是
- 配置元素的 name属性是一个“知名”属性,主应用程序可以使用它
- connectionString和- sourceMachine属性是特定于提供程序的,主应用程序对此一无所知。
接口
让我们讨论“数据访问提供程序模型”模块的内容及其用法。主应用程序应该熟悉的所有接口都定义在此模块中,并且应由第三方数据源提供程序实现。
 
 
DataSource 接口
此接口定义了主应用程序从提供程序获取数据的方式。让我们保持简单:Open、ReadData 和 Close 方法就足够了。
using System.Configuration;
namespace MainCompany.ProviderDemo.DataSource
{
    /// Sample IDataSource interface that declares a couple of simple methods
    public interface IDataSource
    {
        void Open(ConfigurationElement configurationElement);
        void Close();
        string ReadData();
    }
}
关键是传递给数据源的 ConfigurationElement 对象。它可能包含数据源可能需要的任何特定于提供程序的设置。
配置接口
IDataSourceConfigurationSection 允许主应用程序访问提供程序管理的数据源,并告诉它为每个提供程序需要实例化哪个实际的数据源类。
namespace MainCompany.ProviderDemo.DataSource
{
    public interface IDataSourceConfigurationSection
    {
        string Type
        {
            get;
        }
        System.Configuration.ConfigurationElementCollection DataSources
        {
            get;
        }
    }
}
IDataSourceConfigurationElement 接口将数据源名称暴露给主应用程序。
namespace MainCompany.ProviderDemo.DataSource
{
    public interface IDataSourceConfigurationElement
    {
        string Name
        {
            get;
        }
    }
}
提供程序实现
考虑一个由某个第三方(例如 FirstCompany)开发的简单数据源提供程序。
IDataSource 实现
实现非常直接。请注意,此实现使用 FirstCompany.ProviderDemo.DataSource.ConfigurationElement 对象来访问特定于提供程序的设置(在本例中为 connectionString)。
namespace FirstCompany.ProviderDemo.DataSource
{
    /// Sample implementation of IDataSource interface
    internal class DataSource : 
        MainCompany.ProviderDemo.DataSource.IDataSource, IDisposable
    {
        private ConfigurationElement _configurationElement;
        #region IDataSource Members
        /// Sample implementation, just caches configuration settings
        public void Open(System.Configuration.ConfigurationElement configurationElement)
        {
            _configurationElement = configurationElement as ConfigurationElement;
        }
        /// Sample implementation, does nothing
        public void Close()
        {
            // Close if needed
        }
        /// Sample implementation, returns provider description and 
        /// provider-specific property value
        public string ReadData()
        {
            return ("Hello from the first provider, ConnectionString is " + 
                    _configurationElement.ConnectionString);
        }
        #endregion
        #region IDisposable Members
        /// Sample implementation
        public void Dispose()
        {
            Close();
        }
        #endregion
    }
}
使用 .NET 2.0 配置框架
现在让我们完成所有配置文件的设置。以下几类将完成最小的配置节/元素处理。
namespace FirstCompany.ProviderDemo.DataSource
{
    public class ConfigurationSection : System.Configuration.ConfigurationSection, 
        MainCompany.ProviderDemo.DataSource.IDataSourceConfigurationSection
    {
        #region Fields
        private static System.Configuration.ConfigurationPropertyCollection _properties;
        private static System.Configuration.ConfigurationProperty _type;
        private static System.Configuration.ConfigurationProperty _dataSources;
        #endregion
        #region Constructors
        static ConfigurationSection()
        {
            // Type of the data source object, our framework will use it when
            // creating an instance of data source through Activator
            _type = new System.Configuration.ConfigurationProperty(
                "type",
                typeof(string),
                null,
                System.Configuration.ConfigurationPropertyOptions.IsRequired
            );
            // This is the default property of the section and it holds all data source
            // configuration elements that are managed by this provider
            _dataSources = new System.Configuration.ConfigurationProperty(
                "",
                typeof(ConfigurationElementCollection),
                null,
                System.Configuration.ConfigurationPropertyOptions.IsRequired | 
                    System.Configuration.ConfigurationPropertyOptions.IsDefaultCollection
                );
            // Add property definitions to the collection
            _properties = new System.Configuration.ConfigurationPropertyCollection();
            _properties.Add(_type);
            _properties.Add(_dataSources);
        }
        #endregion
        #region Properties
        /// This property implements IDataSourceConfigurationSection method 
        /// so the framework can read the "type" property value 
        /// and instantiate correspondent data source object
        [System.Configuration.ConfigurationProperty("type", IsRequired = true)]
        public string Type
        {
            get
            {
                return (string)base[_type];
            }
        }
        /// This property implements IDataSourceConfigurationSection method 
        /// so the framework can walk through all data sources managed by this provider
        public System.Configuration.ConfigurationElementCollection DataSources
        {
            get { return (System.Configuration.ConfigurationElementCollection)
                    base[_dataSources]; }
        }
        /// ConfigurationSection override, returns the list of all properties
        /// (including the default property - element collection)
        protected override 
               System.Configuration.ConfigurationPropertyCollection Properties
        {
            get
            {
                return _properties;
            }
        }
        #endregion
    }
    public class ConfigurationElementCollection: 
              System.Configuration.ConfigurationElementCollection
    {
        #region Properties
        /// ConfigurationElementCollection override, defines collection type
        public override 
             System.Configuration.ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return System.Configuration.ConfigurationElementCollectionType.BasicMap;
            }
        }
        /// ConfigurationElementCollection override, defines XML element name
        protected override string ElementName
        {
            get
            {
                return "firstProviderDataSource";
            }
        }
        #endregion
        #region Overrides
        /// ConfigurationElementCollection override, 
        /// creates a new element of our custom type
        protected override System.Configuration.ConfigurationElement CreateNewElement()
        {
            return new ConfigurationElement();
        }
        /// ConfigurationElementCollection override, gives element key
        /// (it's the Name property in our implementation, we assume it is unique)
        protected override object GetElementKey
           (System.Configuration.ConfigurationElement element)
        {
            return (element as ConfigurationElement).Name;
        }
        #endregion
    }
    public class ConfigurationElement : System.Configuration.ConfigurationElement, 
        MainCompany.ProviderDemo.DataSource.IDataSourceConfigurationElement
    {
        #region Static Fields
        private static System.Configuration.ConfigurationPropertyCollection _properties;
        private static System.Configuration.ConfigurationProperty _name;
        private static System.Configuration.ConfigurationProperty _connectionString;
        #endregion
        #region Constructors
        static ConfigurationElement()
        {
            // Name of the data source, required for 
            // IDataSourceConfigurationElement support
            _name = new System.Configuration.ConfigurationProperty(
                "name",
                typeof(string),
                null, System.Configuration.ConfigurationPropertyOptions.IsRequired
                );
            // This is a purely provider-specific property
            _connectionString = new System.Configuration.ConfigurationProperty(
                "connectionString",
                typeof(string),
                null,
                System.Configuration.ConfigurationPropertyOptions.IsRequired
                );
            // Add property definitions to the collection
            _properties = new System.Configuration.ConfigurationPropertyCollection();
            _properties.Add(_name);
            _properties.Add(_connectionString);
        }
        #endregion
        #region Properties
        /// connectionString property
        [System.Configuration.ConfigurationProperty
              ("connectionString", IsRequired = true)]
        public string ConnectionString
        {
            get { return (string)base[_connectionString]; }
            set { base[_connectionString] = value; }
        }
        /// name property and IDataSourceConfigurationElement Name method implementation
        [System.Configuration.ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get { return (string)base[_name]; }
            set { base[_name] = value; }
        }
        #endregion
    }
}
我将不在此处解释细节,Jon 已经在他的文章中涵盖了所有这些细节。只需注意,我们的配置类还实现了主应用程序可以识别的 IDataSourceConfigurationSection 和 IDataSourceConfigurationElement 接口。
整合
现在让我们开发一个(极其简单)的应用程序来利用我们的提供程序模型。它遍历配置文件中描述的所有数据源,打开它们,读取数据并关闭它们。
using System.Configuration;
using MainCompany.ProviderDemo.DataSource;
using System.Runtime.Remoting;
namespace MainCompany.ProviderDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Configuration configuration = ConfigurationManager.OpenExeConfiguration
                (ConfigurationUserLevel.None);
            ConfigurationSectionGroup sectionGroup = configuration.GetSectionGroup
                ("dataSourceProviders");
            foreach (IDataSourceConfigurationSection dataSourceSection in 
                sectionGroup.Sections)
            {
                foreach (IDataSourceConfigurationElement dataSourceElement in 
                dataSourceSection.DataSources)
                {
                    // Instantiate a datasource
                    string typeName = dataSourceSection.Type.Split(',')[0];
                    string assemblyName = dataSourceSection.Type.Split(',')[1];
                    IDataSource dataSource = Activator.CreateInstance(assemblyName, 
                        typeName).Unwrap() as IDataSource;
                    // Open data source and get some data from it
                    dataSource.Open(dataSourceElement as ConfigurationElement);
                    Console.WriteLine("Message from datasource " + 
                        dataSourceElement.Name +": " + dataSource.ReadData());
                    dataSource.Close();
                }
            }
        }
    }
}
构建应用程序,并确保配置文件和提供程序程序集与应用程序二进制文件位于同一文件夹中。运行应用程序。它应该与配置文件中指定的所有提供程序进行通信,并显示来自所有数据源的消息。
Message from datasource DataSourceA: Hello from the first provider, 
    ConnectionString is server=box-a;database=SomeDatabase
Message from datasource DataSourceB: Hello from the first provider, 
    ConnectionString is server=box-b;database=SomeOtherDatabase
Message from datasource DataSourceC: Hello from the second provider, 
    sourceMachine is 192.168.0.1
后续步骤
下一步可能包括
- 查看一个名为 CSWorks 的商业产品,它使用了这种技术
- 使 IDataSource接口看起来更逼真
- 使提供程序框架可热插拔:所有配置更改都应在用户更改配置文件后立即生效,无需重新启动应用程序/服务
- 实现一个利用 .NET 2.0 配置类并允许应用程序以便捷方式修改配置文件的 API
- 享受开放架构带来的好处
历史
- 2008 年 5 月 21 日:首次发布
- 2010 年 3 月 26 日:文章已更新


