While I think the .NET provider model is useful as a means of introducing dependency inversion if you really don’t want a container (?), it’s a bit of work to create so many peripheral classes in order to use it.
For example, we need to create a strongly-typed collection class that contains them all (presumably a left-over from the .NET 1.x days where there were no generic types), we need a configuration section class just to support an addition to the [web|app].config
file, we need the provider class itself (effectively a factory class) and we need the class(es) of which it provides instances. Oh, and the interface that our provider stuff actually provides.
Here’s a code snippet (what’s a code snippet?) for creating a .NET provider and all the associated paraphernalia. It creates all the classes into one .cs file so you’ll need to use your favourite refactoring tool to extract them as appropriate. The snippet will also generate XML for you that can be copied/pasted directly into your [web|app].config
file.
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>provider</Title>
<Shortcut>provider</Shortcut>
<Description>Code snippet for a .NET Provider implementation</Description>
<Author>Andrew Harcourt</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>providerName</ID>
<ToolTip>The name of the provider (e.g. "Cache", "Licence").</ToolTip>
<Default>Stupid</Default>
</Literal>
<Literal>
<ID>interfaceName</ID>
<ToolTip>The name of the interface that the provider will return (e.g. "ICache", "ILicence").</ToolTip>
<Default>IStupid</Default>
</Literal>
<Literal>
<ID>defaultProvider</ID>
<ToolTip>The name of the default provider instance to use (e.g. "Web", "File"). The suffix "$providerName$Provider" will be added automatically.</ToolTip>
<Default>Default</Default>
</Literal>
<Literal Editable="false">
<ID>className</ID>
<ToolTip>The type of the owning class.</ToolTip>
<Function>ClassName()</Function>
<Default>StupidClass</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Diagnostics;
using System.Reflection;
using System.Web.Configuration;
#region $providerName$Provider
[Serializable]
public abstract class $providerName$Provider : ProviderBase
{
protected abstract string DefaultName { get; }
protected abstract string DefaultDescription { get; }
public abstract $interfaceName$ Get$providerName$();
protected static void CheckForUnrecognizedAttributes(NameValueCollection config)
{
if (null == config)
{
throw new ArgumentNullException("config");
}
if (config.Count > 0)
{
string attr = config.GetKey(0);
if (!string.IsNullOrEmpty(attr))
{
throw new ProviderException("Unrecognized attribute: " + attr);
}
}
}
protected string VerifyInitParams(NameValueCollection config, string name)
{
if (null == config)
{
throw new ArgumentNullException("config");
}
if (string.IsNullOrEmpty(name))
{
name = DefaultName;
}
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", DefaultDescription);
}
return name;
}
}
#endregion
#region $defaultProvider$$providerName$Provider
public class $defaultProvider$$providerName$Provider : $providerName$$end$Provider //TODO Implement abstract class "$providerName$$end$Provider"
{
//TODO Add or merge the following into your (web|app).config file.
/*
<configuration>
<configSections>
<section name="$providerName$ProviderService" type="FULL_NAMESPACE_HERE.$providerName$ProviderSection, ASSEMBLY_NAME_HERE" />
</configSections>
<$providerName$ProviderService defaultProvider="$defaultProvider$$providerName$Provider">
<providers>
<clear />
<add name="$defaultProvider$$providerName$Provider" type="FULL_NAMESPACE_HERE.$defaultProvider$$providerName$Provider, ASSEMBLY_NAME_HERE" />
</providers>
</$providerName$ProviderService>
</configuration>
*/
}
#endregion
// The code below here is auto-generated and shouldn't need any manual
// editing unless you want to do interesting stuff. -andrewh 18/9/08
#region $providerName$ProviderSection
[Obfuscation(Feature = "renaming", Exclude = true, ApplyToMembers = false)]
public class $providerName$ProviderSection : ConfigurationSection
{
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get { return (ProviderSettingsCollection)base["providers"]; }
}
[StringValidator(MinLength = 1)]
[ConfigurationProperty("defaultProvider", DefaultValue = "$defaultProvider$$providerName$Provider")]
public string DefaultProvider
{
get { return (string)base["defaultProvider"]; }
set { base["defaultProvider"] = value; }
}
}
#endregion
#region $providerName$ProviderService
[Serializable]
public class $providerName$ProviderService
{
private static $interfaceName$ _instance;
private static $providerName$Provider _provider;
private static $providerName$ProviderCollection _providers;
private static object _lock = new object();
public static $providerName$Provider Provider
{
get { return _provider; }
}
public static $providerName$ProviderCollection Providers
{
get {
LoadProviders();
return _providers;
}
}
public static $interfaceName$ $providerName$
{
get
{
if (_instance == null)
{
_instance = LoadInstance();
}
return _instance;
}
}
private static $interfaceName$ LoadInstance()
{
LoadProviders();
$interfaceName$ instance = _provider.Get$providerName$();
// if the default provider fails, try the others
if (instance == null)
{
foreach ($providerName$Provider p in _providers)
{
if (p != _provider) // don't retry the default one
{
instance = p.Get$providerName$();
if (instance != null) // success?
{
_provider = p;
break;
}
}
}
}
Debug.Assert(instance != null);
return instance;
}
private static void LoadProviders()
{
if (null == _provider)
{
lock (_lock)
{
// do this again to make sure _provider is still null
if (null == _provider)
{
$providerName$ProviderSection section = LoadAndVerifyProviderSection();
BuildProviderCollection(section);
}
}
}
}
private static void BuildProviderCollection($providerName$ProviderSection section)
{
_providers = new $providerName$ProviderCollection();
ProvidersHelper.InstantiateProviders(section.Providers, _providers, typeof($providerName$Provider));
if (_providers.Count == 0)
{
throw new ProviderException("No providers instantiated");
}
_provider = _providers[section.DefaultProvider];
if (null == _provider)
{
throw new ProviderException("Unable to load provider");
}
}
private static $providerName$ProviderSection LoadAndVerifyProviderSection()
{
// fetch the section from the application's configuration file
$providerName$ProviderSection section = ($providerName$ProviderSection)ConfigurationManager.GetSection("$providerName$ProviderService");
if (section == null)
{
throw new ProviderException("$providerName$ProviderService section missing from (web|app).config");
}
return section;
}
}
#endregion
#region $providerName$ProviderCollection
[Serializable]
public class $providerName$ProviderCollection : ProviderCollection
{
public new $providerName$Provider this[string name]
{
get { return ($providerName$Provider)base[name]; }
}
public override void Add(ProviderBase provider)
{
if (null == provider)
{
throw new ArgumentNullException("provider");
}
if (!(provider is $providerName$Provider))
{
throw new ArgumentException("Invalid provider type", "provider");
}
base.Add(provider);
}
}
#endregion
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>