VS 2005 Provider Pattern in V1.1 of the Framework
Implementing the Provider Pattern in the 1.1 Framework Rob Howard posted a couple of good articles on MSDN here about the new Provider Pattern in VS 2005 and how to implement it in the 1.1 Framework. His articles glossed over a few of the details that would complete a provider so I decided to create my own. Rather than just mimic something available in VS 2005 I decided to do a configuration file provider. This provider allows you to have your configuration settings for the application in any file in any location (security non-withstanding). Since it is a provider pattern that means I can put my configuration information anywhere, it’s really up to my provider to handler the details. I’m going to walk through the required code to create a complete provider. The code snippets below are complete. You should be able to cut and past them into a project(s) and recreate this provider. I’m not going to spend a lot of time discussing what the provider pattern is. If you want to know more about the pattern, how it works, and why it is important check out Rob Howard’s articles on MSDN. Suffice it to say that the pattern is used heavily in the V2.0 Framework so you need to know what it is all about. Namespace and Directory Structure I didn’t include namespace information in the below code. For my implementation I created a separate file for each class with the filename being the same as the class name. The actual physical folder layout was like this with namespaces mirroring the file structure: Company.Configuration: ConfigurationProvider.cs, SqlConfigProvider.cs, XmlConfigProvider.cs Helper: ProviderHelper.cs Providers: Provider.cs, ProviderBase.cs, ProviderCollection.cs SectionHandler: ConfigurationProviderHandler.cs Section Handler The first thing we need is a section handler to read our provider information from the configuration file. Section handlers are custom classes that implement the IConfigurationSectionHandler interface. For my provider I just passed the returned XmlNode to my configuration class. public
{
constructor#region
public ConfigurationProviderHandler(){}
#endregion
#region
IConfigurationSectionHander.Createobject IConfigurationSectionHandler.Create(object parent, object context, XmlNode node)
{
ProviderHelper config = new ProviderHelper();
config.LoadValuesFromConfigurationXml(node);
return config;
}
#endregion
}
Provider BaseNow we need to create our provider base class. This class is an abstract class that according to the pattern should contain a few methods as possible. We have one method and a single property.
public
abstract class ProviderBase{
Constructor#region
public ProviderBase(){}
#endregion
#region
Abstract provider methodspublic abstract void Initialize (string name, NameValueCollection configValue);
public abstract string Name { get; }
#endregion
}
Provider
The provider class isn’t the actual provider instead it is a container for provider information. I found this name to be rather confusing, but in order to stick with the pattern I’ve used this name.
public
class Provider{
variables#region
private string _name;
private string _providerType;
NameValueCollection _providerAttributes = new NameValueCollection();
#endregion
#region
Constructorpublic Provider(XmlAttributeCollection attributes)
{
_name=attributes["name"].Value;
_providerType = attributes["type"].Value;
foreach(XmlAttribute attribute in attributes)
{
if((attribute.Name!="name") && (attribute.Name!="type"))
{
_providerAttributes.Add(attribute.Name, attribute.Value);
}
}
}
#endregion
#region
Propertiespublic string Name
{
get
{
return _name;
}
}
public
string Type{
get
{
return _providerType;
}
}
public NameValueCollection Attributes
{
get
{
return _providerAttributes;
}
}
#endregion
}
ProviderCollection
The provider collection class is the collection of available providers. The Provider pattern dictates that the collection of available providers be accessible from the ConfigurationProvider base class.
public
class ProviderCollection:ICollection{
Hashtable _indexValues;private
private ArrayList _providers;
private bool _readOnly;
public ProviderCollection()
{
_indexValues = new Hashtable(10);
_providers = new ArrayList();
}
private ProviderCollection(Hashtable indexValues, ArrayList providers)
{
_indexValues = indexValues;
_providers= providers;
}
public virtual void Add(ProviderBase provider)
{
if(_readOnly)
{
throw new NotSupportedException("Adding providers to readOnly provider collection is not allowed");
}
if(provider==null)
{
throw new ArgumentNullException("Can't add a null provider to provider collection");
}
if((provider.Name==null)||(provider.Name.Length<1))
{
throw new ArgumentException("Provider name must be greater than 1 character in length");
}
int index = _providers.Add(provider);
try
{
_indexValues.Add(provider.Name, index);
}
catch(Exception)
{
_providers.Remove(index);
throw;
}
}
public void Clear()
{
if(_readOnly)
{
throw new NotSupportedException("Unable to clear read only provider collection");
}
_indexValues.Clear();
_providers.Clear();
}
public void CopyTo(ProviderBase[] array, int index)
{
CopyTo(array, index);
}
public IEnumerator GetEnumerator()
{
return _providers.GetEnumerator();
}
public void Remove(string name)
{
if(_readOnly)
{
throw new NotSupportedException("Unable to remove items from read only providers collection");
}
object temp = _indexValues[name];
if(temp==null)
{
throw new ArgumentException("Provider not found in collection");
}
int index = (int) temp;
if(index>=_indexValues.Count)
{
throw new ArgumentException("Index value is greater than collection item count");
}
_providers.RemoveAt(index);
_indexValues.Remove(name);
ArrayList list = new ArrayList();
foreach(DictionaryEntry entry in _indexValues)
{
if(((int)entry.Value<=index))
{
continue;
}
list.Add(entry.Key);
}
foreach(string providerName in list)
{
_indexValues[providerName]=(((int)_indexValues[providerName])-1);
}
}
void System.Collections.ICollection.CopyTo(Array array, int index)
{
_indexValues.CopyTo(array, index);
}
public void SetReadOnly()
{
if(_readOnly)
{
return;
}
_readOnly=true;
}
public int Count
{
get
{
return _providers.Count;
}
}
public bool IsSynchronized
{
get
{
return false;
}
}
public ProviderBase this[string name]
{
get
{
object temp = _indexValues[name];
if(temp==null)
{
return null;
}
int index = (int)temp;
if(index>_providers.Count)
{
return null;
}
return ((ProviderBase)_providers[index]);
}
}
public object SyncRoot
{
get
{
return this;
}
}
}
ProviderHelper
Following the VS 2005 provider model I moved some of my methods to a helper class. In this case I called it the ProviderHelper.
public
class ProviderHelper{
string _defaultProvider;
Hashtable _providers =
new Hashtable();#region
constructor public ProviderHelper(){}#endregion
#region
Static Methods#region
GetConfig (+1 overload) public static ProviderHelper GetConfig(){
return (ProviderHelper)ConfigurationSettings.GetConfig("companyConfigData/companyConfig");
}
public static ProviderHelper GetConfig(string sectionName){
return (ProviderHelper)ConfigurationSettings.GetConfig(sectionName);
}
#region
InstantiateProviders public static ProviderCollection InstantiateProviders(ProviderHelper validProviders){
new ProviderCollection(); foreach(Provider p in validProviders.Providers.Values)ProviderCollection providers=
{
providers.Add(InstantiateProvider(p));
}
return providers;}
#endregion
#region
InstantiateProvider public static ProviderBase InstantiateProvider(Provider provider){
null; //check cache string cacheKey = "XMLCONFIGURATION::" + provider.Name; if(cache[cacheKey]==null)Cache cache = HttpRuntime.Cache;
Type type=
{
try
{
type=Type.GetType(provider.Type);
Type[] paramTypes= System.Type.EmptyTypes;
cache.Insert(cacheKey, type.GetConstructor(paramTypes));
}
catch(Exception ex){
throw new Exception("Unable to load provider " + provider.Name, ex);}
}
//load configuration settings object[] paramArray = new object[0]; //paramArray[0] = xmlProvider.Attributes["path"];ConfigurationProvider configProvider =(ConfigurationProvider)(((ConstructorInfo)cache[cacheKey]).Invoke(paramArray));
configProvider.Initialize(provider.Name,provider.Attributes);
return (ProviderBase)configProvider;}
#endregion
#endregion
#endregion
#region
Instance Methods#region
LoadValuesFromConfigurationXml internal void LoadValuesFromConfigurationXml(XmlNode node){
///get the default providerXmlAttributeCollection attributeCollection =node.Attributes;
_defaultProvider = attributeCollection["defaultProvider"].Value;
//Read the child nodes foreach(XmlNode child in node.ChildNodes){
if(child.Name=="providers")
{
GetProviders(child);
}
}
}
#endregion
#region
GetProviders private void GetProviders(XmlNode node){
foreach(XmlNode provider in node.ChildNodes)
{
switch(provider.Name)
{
case "add":_providers.Add(provider.Attributes["name"].Value,
new Provider(provider.Attributes)); break; case "remove":_providers.Remove(provider.Attributes["name"].Value);
break; case "clear":_providers.Clear();
break;}
}
}
#endregion
#region
Properties public string DefaultProvider{
get
{
return _defaultProvider;
}
}
public Hashtable Providers{
get
{
return _providers;
}
}
#endregion
#endregion
}
ConfigurationProvider
The provider pattern provides for some naming conventions. The ConfigurationProvider serves as the base class for all of our actual consumable provider classes.
public
abstract class ConfigurationProvider:ProviderBase{
Constructor public ConfigurationProvider(){}#region
#endregion
#region
Abstract methods from base public abstract string GetKey(string key); internal abstract void GetConfig(NameValueCollection configValue);#endregion
#region
Instance (+2 overloads) public static ProviderBase Instance(string providerName){
return Instance("", providerName);
}
public static ProviderBase Instance(){
return Instance("","DefaultProvider");
}
public static ProviderBase Instance(string configSection, string providerName){
null; if(configSection=="")ProviderHelper config=
{
//Get the names of the providers
config = ProviderHelper.GetConfig();
}
else{
config = ProviderHelper.GetConfig(configSection);
}
//Read the configuration specific information if(providerName=="DefaultProvider")providerName = config.DefaultProvider;
Provider xmlProvider = (Provider)config.Providers[providerName];
return ProviderHelper.InstantiateProvider(xmlProvider);}
#endregion
#region
GetProviderCollection public static ProviderCollection GetProviderCollection(){
//Get the names of the providers
ProviderHelper config = ProviderHelper.GetConfig();
ProviderCollection providerCollection =
new ProviderCollection();Hashtable ht = config.Providers;
foreach(Provider provider in ht.Values){
providerCollection.Add((ProviderHelper.InstantiateProvider(provider)));
}
return providerCollection;}
#endregion
}
XmlConfigProvider
Now we are almost done. The next step is that we need an actual provider. This is an Xml provider. The name Xml means that it reads its information from an Xml data source. It inherits from the ConfigurationProvider base class.
public
class XmlConfigProvider:ConfigurationProvider{
Variables private string _name; private Hashtable _ht = new Hashtable();#region
#endregion
#region
Constructor public XmlConfigProvider(){}#endregion
#region
Initialize public override void Initialize (string name, NameValueCollection configValue ){
//load config file and get data_name = name;
GetConfig(configValue);
}
#endregion
#region
GetConfig internal override void GetConfig(NameValueCollection configValue){
if(_name ==null)
throw new InvalidOperationException("XmlConfigProvider has not been property initialized. Initialize method must be called first");
XmlDocument doc =
new XmlDocument();XmlTextReader rdr =
new XmlTextReader(configValue["path"]); try{
while(nodes.MoveNext())doc.Load(rdr);
IEnumerator nodes = doc.ChildNodes[1].GetEnumerator();
{
if(nodes.Current is XmlElement)
{
if(cur.Name=="add")XmlElement cur =(XmlElement) nodes.Current;
{
XmlAttributeCollection curAttributes =cur.Attributes;
_ht.Add(curAttributes["key"].Value, curAttributes["value"].Value);
}
}
}
}
finally{
rdr.Close();
}
}
#endregion
#region
GetKey public override string GetKey(string key){
object cacheItem = _ht[key] as string; if(cacheItem==null)
return string.Empty; else
return (string)cacheItem;
}
#endregion
#region
Name public override string Name{
get{ return _name;}
}
#endregion
}
Xml Data Source
This is our sample data source that is read by our XmlConfigProvider class.
xml version="1.0" encoding="utf-8" ?>
<
configuration> <add key="reloadRate" value="10"/> <add key="cacheDurationUser" value="15"/> <add key="errorRedirect" value="/showError.aspx"/> <add key="securityLevelDefault" value="1" /> <add key="site" value="network.com" />configuration>
Configuration File Entry
What Provider pattern would be complete without a configuration file entry. Here’s the entry I used for my providers. Note that you will have to adjust these entries to have the correct path as well as the correct namespaces for your classes.
xml version="1.0" encoding="utf-8" ?>
<
configuration><configSections>
<sectionGroup name="companyConfigData">
<section name="companyConfig" type="Company.Configuration.SectionHandler.ConfigurationProviderHandler, Company.Configuration" />
sectionGroup>
configSections>
<companyConfigData>
<companyConfig defaultProvider="XmlConfiguration">
<providers>
<add name="XmlConfiguration" type="Company.Configuration.XmlConfigProvider, Company.Configuration" path="D:\Development\Prototypes\Provider\Company.Configuration\configData.xml"/>
<add name="SqlConfiguration" type="Company.Configuration.SqlConfigProvider, Company.Configuration" procedure="p_sel_some_configinfo" connectionString="server=.;database=providerTest;uid=providerUID;pwd=providerPwd"/>
providers>
companyConfig>
companyConfigData>
configuration>
Using the Provider
Here’s a sample class that uses our provider class.
public
class ConfigData{
private static ConfigurationProvider _provider; private static ProviderCollection _providersCollection;
#region
Constructor private ConfigData(){}#endregion
#region
GetKey public static string GetKey(string key){
if(_provider==null)
{
_provider =(ConfigurationProvider)ConfigurationProvider.Instance();
}
return _provider.GetKey(key);}
#endregion
#region
Provider public static ConfigurationProvider Provider{
get
{
if(_provider==null)
{
_provider = (ConfigurationProvider)ConfigurationProvider.Instance();
}
return _provider;}
}
#endregion
#region
Providers public static ProviderCollection Providers{
get
{
if(_providersCollection==null)
{
_providersCollection = ProviderHelper.InstantiateProviders(ProviderHelper.GetConfig());
}
return _providersCollection;}
}
#endregion
#region
SetProvider (+1 Overload) public static void SetProvider(string name){
null;ConfigurationProvider provider=
provider = (ConfigurationProvider)ConfigurationProvider.Instance(name);
if(provider !=null)_provider = provider;
}
public static void SetProvider(string configSection, string name){
null;ConfigurationProvider provider =
provider =(ConfigurationProvider)ConfigurationProvider.Instance(configSection, name);
if(provider!=null)_provider=provider;
}
#endregion
}
NUnit TestI tend to follow the TDD approach of creating and running unit tests prior to writing my code. Here are a few of the unit tests I created.
[TestFixture]
public
class ConfigDataTest{
public ConfigDataTest()
{
}
[Test]
public void GetValidKeys()
{
int test = Convert.ToInt32(ConfigData.GetKey("reloadRate"));
Console.WriteLine("ReloadRate value {0}", test);
Assert.IsTrue(test>0);
test = Convert.ToInt32(ConfigData.GetKey("cacheDurationUser"));
Console.WriteLine("CacheDurationValue {0}", test);
Assert.IsTrue(test>0);
string testString = ConfigData.GetKey("errorRedirect");
Console.WriteLine("Error redirect value {0}", testString);
Assert.IsTrue(testString.Length>0);
test = Convert.ToInt32(ConfigData.GetKey("securityLevelDefault"));
Console.WriteLine("SecurityLevelDefault value {0}", test);
Assert.IsTrue(test>-1);
}
[Test]
[ExpectedException(typeof(FormatException))]
public void GetInvalidKeyWithCast()
{
int test = Convert.ToInt32(ConfigData.GetKey("SomeInvalidKey"));
}
[Test]
public void GetInvalidKey()
{
string test = ConfigData.GetKey("someInvalidKey");
Console.WriteLine("Invalid key is {0}", test);
Assert.IsTrue(test.Length==0);
}
[Test]
public void SetProvider()
{
Console.WriteLine("Provider name pre change {0}", ConfigData.Provider.Name);
ConfigData.SetProvider("SqlConfiguration");
Console.WriteLine("Provider name post change {0}", ConfigData.Provider.Name);
Assert.IsTrue(ConfigData.Provider.Name=="SqlConfiguration");
}
[Test]
[ExpectedException(typeof(NullReferenceException))]
public void SetInvalidProvider()
{
Console.WriteLine("Provider name pre change {0}", ConfigData.Provider.Name);
ConfigData.SetProvider("SqlConfigurationInvalid");
}
[Test]
public void GetProviderCollection()
{
Console.WriteLine("Providers count {0}", ConfigData.Providers.Count);
Assert.IsTrue(ConfigData.Providers.Count>0);
}
}
Final Thoughts
The Provider pattern is a useful and powerful pattern. I can see why it was leveraged so much in VS 2005. It will be nice when we can use the pattern without having to create all the infrastructure type classes that make this example so large. VS 2005 promises to handle most of this stuff for you.
As a side note I should point out that I didn’t exactly follow the pattern as Rob Howard laid it out. For one I had problems getting the thing to work and when it did work it was constantly reloading the configuration data, which killed performance. I suspect my problems were mostly due to my not understanding what Rob was telling me to do and not really a problem with the pattern. I also added an interface that allows the consumer to dictate which provider to use. This isn’t really all that useful, but I wanted to see how it would work with that functionality available.