While I think the .NET provider model is useful as a means of introducing dependency inversion if you don't want a container (!!), it really irritates me that we have 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. Unfortunately it dumps all the classes into one .cs file, but AFAIK there's no way to get a single snippet to create multiple files. You can (and should) do that yourself, though.

Handily, 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>