Friday, November 21, 2008

Provider design pattern implementation in .NET

The provider design pattern allows developers to define a well-documented, easy-to-understand API, with complete control over the internals of what occurs when those APIs are called. 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.

In short the pattern allows the developers to create a pluggable abstraction layer. For example, Commerce Server supports a pluggable layer for its personalization system, requiring developers to write a component implementing OleDb; something only a handful of people truly can accomplish, or for that matter desire to accomplish. The difference with the provider pattern is that it is a simple and approachable pattern for abstracting the APIs themselves.

The provider pattern introduced in ASP.NET 2.0 is merely a set of rules dictating one way to build pluggable software. The process of creating a provider model involves the creation of 4 main classes.
  • Main data provider class: The abstract base class has all the abstract methods and properties required to implement the public API it supports. For example, the new ASP.NET 2.0 Membership feature defines an abstract base class, MembershipProvider. This class mirrors the methods and properties defined in the Membership API. The MembershipProvider derives from another base class, ProviderBase. ProviderBase is a class common to all providers. Developers create their own classes, derive from the MembershipProvider, and override methods and properties with their own implementation. This resulting class is known as the provider.
  • Data provider collection class: The data provider collection class is the portion of your implementation that takes care of provider configuration. Here you implement the behavior of your configuration elements for a list of providers.
  • Data provider configuration class: This class contains the logic to manage the configuration of your provider and to get user's parameters and pass them to your provider. Data providers work based on the configuration files of your applications.
  • Data provider manager class: This is a singleton class with the main purpose to initialize a concrete provider and load user configuration settings in memory. During initialization, you need to retrieve the configuration section for your data provider and check its correctness. Once an implementation of the feature provider class is created, it must be described in the configuration section. For a given feature, an unlimited number of providers may be defined. The appropriate provider is instantiated at run time from information defined in the configuration file. It is possible to change providers at run time too, allowing for a dynamic change between, say, a SQL Server provider and an Oracle provider.
The class diagram given below describes the components in the provider model and show the relationships between them.


[Click on the image to enlarge it]

As you can see I have created a sample provider model for Sql, Oracle data access.

Main Data provider implementation

public abstract class DataAccessProvider : ProviderBase

{

public abstract IDataReader ExecuteReader(string storedProcedureName, IDataParameterCollection dbParameterCollection);

public abstract IDataReader ExecuteReader(string sql);

public abstract void ExecuteNonQuery(string storedProcedureName, IDataParameterCollection dbParameterCollection);

public abstract void ExecuteNonQuery(string sql);

}

The Data Provider Collection implementation

public class DataAccessProviderCollection : ProviderCollection

{

new public DataAccessProvider this[string name]

{

get { return (DataAccessProvider)base[name]; }

}

}

The Data Provider Configuration implementation

public class DataAccessProviderConfiguration : ConfigurationSection

{

[ConfigurationProperty("providers")]

public ProviderSettingsCollection Providers

{

get

{

return (ProviderSettingsCollection)base["providers"];

}

}

[ConfigurationProperty("default", DefaultValue = "SqlDataAccessProvider")]

public string DefaultProviderName

{

get

{

return base["default"] as string;

}

}

}

The Data Provider Manager implementation

public class DataAccessProviderManager

{

static DataAccessProviderManager()

{

Initialize();

}

private static DataAccessProvider _default;

///

/// Returns the default configured data provider

///

public static DataAccessProvider Default

{

get { return _default; }

}

private static DataAccessProviderCollection _providerCollection;

///

/// .Returns the provider collection

///

public static DataAccessProviderCollection Providers

{

get { return _providerCollection; }

}

private static ProviderSettingsCollection _providerSettings;

public static ProviderSettingsCollection ProviderSettings

{

get { return _providerSettings; }

}

///

/// Reads the configuration related to the set of configured

/// providers and sets the default and collection of providers and settings.

///

private static void Initialize()

{

DataAccessProviderConfiguration configSection = (DataAccessProviderConfiguration)ConfigurationManager.GetSection("DataAccessProviderDataProvider");

if (configSection == null)

throw new ConfigurationErrorsException("Data provider section is not set.");

_providerCollection = new DataAccessProviderCollection();

ProvidersHelper.InstantiateProviders(configSection.Providers, _providerCollection, typeof(DataAccessProvider));

_providerSettings = configSection.Providers;

if (_providerCollection[configSection.DefaultProviderName] == null)

throw new ConfigurationErrorsException("Default data provider is not set.");

_default = _providerCollection[configSection.DefaultProviderName];

}

}

Now you can create concrete providers like

public class SqlDataAccessProvider : DataAccessProvider

{

public override void ExecuteNonQuery(string sql)

{

//Implement SQL based functionality here.

}

public override void ExecuteNonQuery(string storedProcedureName, IDataParameterCollection dbParameterCollection)

{

//Implement SQL based functionality here.

}

public override IDataReader ExecuteReader(string sql)

{

//Implement SQL based functionality here.

return null;

}

public override IDataReader ExecuteReader(string storedProcedureName, IDataParameterCollection dbParameterCollection)

{

//Implement SQL based functionality here.

return null;

}

}

You can use the Provider as

DataAccessProvider provider = DataAccessProviderManager.Default;

provider.ExecuteNonQuery(@"Update SampleTable set ColumnName = 'TEST' where Id = 1");


And finally the configuration file settings

<configuration>

<configSections>

<section name="DataAccessProviderDataProvider"

type="ProviderModel.DataAccessProvider.DataAccessProviderDataProviderConfiguration, ProviderModel" />

configSections>

<DataAccessProviderDataProvider default="SqlDataAccessProvider">

<providers>

<add name="SqlDataAccessProvider"

type="ProviderModel.DataAccessProvider.SqlDataAccessProvider, ProviderModel" />

providers>

DataAccessProviderDataProvider>

configuration>


No comments: