![]() |
Platforms, Frameworks & Libraries »
.NET Framework »
General
Intermediate
License: The GNU General Public License (GPL)
.NET 2.0 Configuration and Provider ModelBy Sergey SorokinUse .NET 2.0 configuration features for building a pluggable provider framework for your application. |
C# (C# 1.0, C# 2.0, C# 3.0), .NET (.NET 2.0, .NET 3.0, .NET 3.5), Visual Studio (VS2008), Architect, Dev, Design
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
The article shows how one can use .NET 2.0 configuration classes when building an application that supports pluggable provider model.
Imagine we are working for a company called, say, MainCompany Inc. and we are developing a complex application that does some data processing. What we want is an open architecture that would allow other developers to implement providers that:
The following diagram gives the idea of what our architecture should look like:
The main application developer has no a priori knowledge about the nature of the data sources that can be used, but may specify the way it uses the data. For the sake of simplicity, we will assume that the main application just needs to get some text data from every data source once in a while.
The following is a sample config file that defines two providers and three data sources:
<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>
If you want to get familiar with config sections, config section groups and config elements, you may want to check out an excellent article by Jon Rista. Here is a brief overview of the config file.
dataSourceProviders that contain custom config sections firstProviderDataSources and secondProviderDataSources handled by correspondent classes:FirstCompany.ProviderDemo.DataSource.ConfigurationSection SecondCompany.ProviderDemo.DataSource.ConfigurationSectionfirstProviderDataSources config section with two elements, each element says the application should create an instance of FirstCompany.ProviderDemo.DataSource.DataSource class and pass connectionString parameter to the newly created data source. Name property serves as a unique id for the data source and is used by the main application to identify data sources.secondProviderDataSources config section with one element. That element says the application should create an instance of SecondCompany.ProviderDemo.DataSource.DataSource class and pass sourceMachine parameter to the newly created data source. Name property serves as a unique id for the data source and is used by the main application to identify data sources.The key things to understand are:
name property of the config element is a “well-known” property and can be used by the main applicationconnectionString and sourceMachine properties are provider-specific, the main application knows nothing about them. Let's discuss the “Data Access Provider Model” module contents and how they are used. All interfaces that the main application should be familiar with are defined in this module and should be implemented by third party data source providers.
This interface defines the way in which the main application gets data from providers. Let’s keep it simple: Open, ReadData and Close methods are enough.
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();
}
}
The key is the ConfigurationElement object passed to the data source. It may contain any provider-specific settings that data source may need.
IDataSourceConfigurationSection gives the main application access to the data sources managed by the provider and tells it which actual data source class it has to instantiate for every provider.
namespace MainCompany.ProviderDemo.DataSource
{
public interface IDataSourceConfigurationSection
{
string Type
{
get;
}
System.Configuration.ConfigurationElementCollection DataSources
{
get;
}
}
}
IDataSourceConfigurationElement interface exposes data source name to the main application.
namespace MainCompany.ProviderDemo.DataSource
{
public interface IDataSourceConfigurationElement
{
string Name
{
get;
}
}
}
Consider a simple data source provider developed by some third party called, say, FirstCompany.
The implementation is pretty straightforward. Please note that this implementation uses the FirstCompany.ProviderDemo.DataSource.ConfigurationElement object to access provider-specific settings (connectionString in this case).
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
}
}
Now let’s get all the config file plumbing done. The following couple of classes will do the minimal config section/element handling.
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
}
}
I am not going to explain the details here, Jon has them all covered in his article. Just note that our config classes also implement IDataSourceConfigurationSection and IDataSourceConfigurationElement interfaces that are recognized by the main application.
Now let’s develop an (extremely simple) application that utilizes our provider model. It walks through all data sources described in the config file, opens them, reads data and closes them.
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();
}
}
}
}
}
Build the app and make sure that config file and provider assemblies are in the same folder as the app binary. Run the application. It should communicate with all providers specified in the config file and display messages from all data sources:
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
Next steps probably are:
IDataSource interface look more realistic
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 21 May 2008 Editor: Deeksha Shenoy |
Copyright 2008 by Sergey Sorokin Everything else Copyright © CodeProject, 1999-2009 Web15 | Advertise on the Code Project |