Provider Model was introduced in 2004 during the beta’s for .net 2.0.
Rob Howard: “The pattern itself is exceedingly simple and is given the name “provider” since it provides the functionality for an API. Defined, a provider is simply a contract between an API and the Business Logic/Data Abstraction Layer. The provider is the implementation of the API separate from the API itself. For example, the new Whidbey Membership feature has a static method called Membership.ValidateUser(). The Membership class itself contains no business logic; instead it simply forwards this call to the configured provider. It is the responsibility of the provider class to contain the implementation for that method, calling whatever Business Logic Layer (BLL) or Data Access Layer (DAL) is necessary.”
This is how provider model works for the .net framework provider abstractions. But if you follow the same pattern for your own abstractions you can tweak it for your needs. The base pattern requires 4 classes for each abstraction. I consider this a little extreme and really 2 of those classes are practically identical across all your abstractions. I have created those two classes in a generic way so that they can be reused for all my abstractions. Now all I create per abstraction is the base abstract provider and the static API class, and of coarse any implementations of the abstraction that I need.
The code below is available on github, project ‘candor-common’. Or you can consume it from NuGet, packageId ‘Candor.Core’.
You only need this once. It should be in the framework somewhere, but I don’t believe that it is.
using System.Configuration; namespace Candor.Configuration.Provider { /// <summary> /// A custom configuration section for any provider abstraction. /// </summary> public class ProviderConfigurationSection : ConfigurationSection { /// <summary> /// Creates a new instance of ProviderConfigurationSection /// </summary> public ProviderConfigurationSection() { } /// <summary> /// The provider definitions. /// </summary> [ConfigurationProperty("providers")] public ProviderSettingsCollection Providers { get { return ((ProviderSettingsCollection)base["providers"]); } } /// <summary> /// Gets or sets the name of the default provider. /// </summary> [ConfigurationProperty("defaultProvider", IsRequired = false, DefaultValue = "")] public string DefaultProvider { get { return ((string)base["defaultProvider"]); } set { base["defaultProvider"] = value; } } } }
Applications using xml configuration will use this in the configuration file when defining a new configuration section for your abstraction.
Your static API class will need to store a collection of all the configured provider implementations currently active in the application. You need a strongly typed collection, but you can use generics. And with that said, why create this generic more than once? Here is what I use:
using System; using System.Configuration; using System.Configuration.Provider; using System.Web.Configuration; using Common.Logging; using prov = System.Configuration.Provider; namespace Candor.Configuration.Provider { /// <summary> /// Represents a strongly typed collection of provider implementations. /// </summary> /// <typeparam name="T">The type of provider that will be in the list. The provider MUST inherit /// from System.Configuration.Provider.ProviderBase.</typeparam> public sealed class ProviderCollection<T> : prov.ProviderCollection where T : prov.ProviderBase { private Type _parentType; private string _configSectionName; private ProviderConfigurationSection _config = null; private T _provider = null; /// <summary> /// Instantiates a new empty provider collection that does not use configuration, /// and instead allows for adding providers at runt time. /// </summary> public ProviderCollection() { } /// <summary> /// Instantiates a new ProviderCollection and initializes it from the configuration for the specified parent type. /// </summary> /// <param name="parentType"></param> public ProviderCollection( Type parentType ) { _parentType = parentType; _configSectionName = _parentType.Namespace + "/" + _parentType.Name; InstantiateProviders(); } /// <summary> /// Instantiates a new ProviderCollection and initializes it from the configuration /// for the specified parent type and the specified configuration section. /// </summary> /// <param name="parentType"></param> /// <param name="configSectionName"></param> public ProviderCollection( Type parentType, string configSectionName ) { _parentType = parentType; _configSectionName = configSectionName; InstantiateProviders(); } private ILog logProvider_ = null; /// <summary> /// Gets or sets the log destination for this collection. If not set, it will be automatically loaded when needed. /// </summary> public ILog LogProvider { get { if (logProvider_ == null) logProvider_ = LogManager.GetLogger(typeof(T)); return logProvider_; } set { logProvider_ = value; } } /// <summary> /// Gets the provider to be activated for the current environment. /// </summary> public T ActiveProvider { get { AssertProviderDefined(); return _provider; } } /// <summary> /// Instantiates all configured providers. /// </summary> public void InstantiateProviders() { InstantiateProviders(_configSectionName); } /// <summary> /// Instantiates all configured providers. /// </summary> public void InstantiateProviders( string configSectionName ) { _configSectionName = configSectionName; if (LogProvider != null) LogProvider.Debug("Initializing " + (_configSectionName ?? "")); try { _config = ConfigurationManager.GetSection(_configSectionName) as ProviderConfigurationSection; if (_config == null) throw new ProviderException(string.Format("'{0}' section missing.", _configSectionName)); if (_config.Providers == null || _config.Providers.Count == 0) throw new ProviderException(string.Format("No '{0}' providers have been configured.", _configSectionName)); ProvidersHelper.InstantiateProviders(_config.Providers, this, typeof(T)); SetActiveProvider(_config.DefaultProvider); } catch (Exception ex) { if (LogProvider != null) LogProvider.Fatal("Could not Instantiate providers from configuration.", ex); throw; } } /// <summary> /// Throws an exception if no active provider is defined. /// </summary> public void AssertProviderDefined() { try { if (this.Count == 0) throw new ProviderException(string.Format("No '{0}' providers have been configured.", _configSectionName)); if (_provider == null) throw new ProviderException(string.Format("The {0} provider to use was not specified. Use the 'defaultProvider' attribute or set it via code.", typeof(T).FullName, _configSectionName)); } catch (Exception ex) { if (LogProvider != null) LogProvider.Fatal(ex.Message, ex); throw; } } /// <summary> /// Sets the current provider to the provider of the specified name. /// </summary> /// <param name="name"></param> /// <returns></returns> public bool SetActiveProvider( string name ) { _provider = null; if (string.IsNullOrEmpty(name)) return false; _provider = this[name]; if (_provider == null) throw new ProviderException(string.Format("{0} provider '{1}' is not defined.", typeof(T).FullName, name)); if (LogProvider != null) LogProvider.Info(string.Format("ActiveProvider set to:'{0}'", _provider.Name)); return true; } /// <summary> /// Sets the current provider to the specified instance. If it does not currently exist in the collection then it will be added. /// </summary> /// <param name="provider"></param> /// <returns></returns> public bool SetActiveProvider( T provider ) { _provider = null; if (provider == null) return false; T existing = (T)base[provider.Name]; if (existing == null) Add(provider); else if (existing != provider) { base.Remove(provider.Name); Add(provider); } _provider = provider; if (LogProvider != null) LogProvider.Info(string.Format("ActiveProvider set to:'{0}'", _provider.Name)); return true; } /// <summary> /// Adds a provider to the collection. /// </summary> /// <param name="provider">The provider to add to the collection.</param> public override void Add( prov.ProviderBase provider ) { if (provider == null) throw new ArgumentNullException("provider"); if (!(provider is T)) throw new ArgumentException("Input must be of type " + typeof(T).FullName); base.Add(provider); } /// <summary> /// Adds a provider to the collection. /// </summary> /// <param name="provider">The provider to add to the collection.</param> /// <remarks>This method overload helps chaining for code configuration.</remarks> public T Add( T provider ) { if (provider == null) throw new ArgumentNullException("provider"); base.Add(provider); return (T)provider; } /// <summary> /// Indexer that get a provider from the collection by name. /// </summary> /// <param name="name">The name of the provider to get.</param> /// <returns>The provider that had the given name.</returns> public new T this[string name] { get { return (T)base[name]; } } } }
This allows for xml configuration or code configuration.
You use the two classes above within your static API classes. Here is an example:
using prov = Candor.Configuration.Provider; namespace Candor.Security { public static class UserManager { private static prov.ProviderCollection<UserProvider> _providers; /// <summary> /// Gets the default provider instance. /// </summary> public static UserProvider Provider { get { return Providers.ActiveProvider; } } /// <summary> /// Gets all the configured User providers. /// </summary> public static prov.ProviderCollection<UserProvider> Providers { get { if (_providers == null) _providers = new prov.ProviderCollection<UserProvider>( typeof(UserManager)); return _providers; } } /// <summary> /// Gets a user by the unique identity. /// </summary> /// <param name="userID">The unique identity.</param> /// <param name="result">A ExecutionResults instance to add applicable /// warning and error messages to.</param> public static User GetUser( Guid userID, ExecutionResults result ) { return Provider.GetUser(userID, result); } } }
The traditional method to configure a provider model abstraction is using xml configuration. The first part below is to define the configuration section. This is where you reference the reusable ProviderConfigurationSection class. If you have a database provider implementation, then I recommend the connectionStrings section to define the connection string since it is relatively easy to encrypt this value in the configuration. Then just define the configuration section by creating a node for the namespace of your static API class, and then a node under of of the class name. This is the standard convention expected by one of the ProviderCollection
<configuration> <configSections> <sectionGroup name="Candor.Security"> <section name="UserManager" type="Candor.Configuration.Provider.ProviderConfigurationSection, Candor.Configuration" /> </sectionGroup> </configSections> <connectionStrings> <add name="MainConnection" connectionString="[...]"/> </connectionStrings> <Candor.Security> <UserManager defaultProvider="AppFabric"> <providers> <!-- These are just examples: Copy just these provider types that make sense for your project. The type attributes below show the suggested project name and class name for each type of provider for consistency across projects. --> <add name="AppFabric" type="Candor.Security.Caching.AppFabricProvider.AppFabricUserProvider, Candor.Security.Caching.AppFabricProvider" TargetProviderName="SQL" CacheName="[SomeCacheNameHere]" /> <add name="WebService" type="Candor.Security.WebServiceProvider.WebServiceUserProvider, Candor.Security.WebServiceProvider" webServiceUrl = "http://test.domain.com/[ProjectName]/[WebServiceName].asmx" webServiceUserName="[AppSpecificIDOrLeaveEmptyForAnonymous]" webServicePassword="[AppSpecificPasswordOrLeaveEmptyForAnonymous]" /> <add name="SQL" type="Candor.Security.Data.SQLProvider.SQLUserProvider, Candor.Security.Data.SQLProvider" connectionName="MainConnection" /> <add name="Mock" type="Candor.Security.Tests.MockProviders.MockUserProvider, Candor.Security.Tests" /> </providers> </UserManager> </Candor.Security> </configuration>
You should only include the providers above that you have created and want active in your development environment. You should use configuration transforms to change which provider(s) are setup for other deployment environments, such as production.
If you want compile time support of your configuration you can use code configuration. The only issue with code configuration is that you have less flexibility to change providers on the fly without recompilation. However. you can instantiate one of a couple possible configurations based on an application setting.
You should put code configuration in your application startup where it only runs once.
UserManager.Providers = new ProviderCollection(); UserManager.Providers.SetActiveProvider( new AppFabricUserProvider("cache"){ DelegateProvider = UserManager.Providers.Add( new SQLUserProvider("db"){ ConnectionName = "MainConnection" }) });
Provider model is not set in stone by what is in the .net framework. Provider model is a pattern, not just a Microsoft library. Microsoft uses provider model in it’s own way. But you can define your own abstractions to use provider model like the above to gain a few more benefits.
Update 1/11/2013: Much of the code above has been added to candor-common on Github
https://github.com/michael-lang/candor-common
1 Comment