The .NET 2.0 Framework Provider Pattern

Part I. Provider Design Pattern Overview

The Provider Model Design Pattern  was first introduced with the .NET 1.1 framework, specifically in the ASP.NET starter kits and was formalized in ASP.NET Whidbey as a membership management provider API (Application Program Interface). It's primary purpose is to separate the definition for an API from the implementation.  This keeps the API flexible by enabling the core functionality to be flexible and easily changed.

More details on the origins of the pattern in the .NET 1.1 Framework can be found on MSDN:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspnet/html/asp02182004.asp

and:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspnet/html/asp02182004.asp

The Provider Model Design Pattern is basically a fusion of the two GOF patterns: strategy and abstract factory.  The API is defined and the functionality is "pluggable" through a variation of the strategy design pattern.  This functionality is loaded into memory through a rough implementation of the abstract factory design pattern.

Here's a basic outline of the parts and how they interact.

  1. API Class:  This is a class that defines and exposes all the desired functionality through static methods.  There is no implementation in the API Class.  The API Class keeps a reference to a Application Provider Base which is basically a wrapper for the API functionality.

    For this article our API Class will be a store from which we can purchase products, specifically Coke, Snickers, and the diet pills that are needed from eating all the Snickers and drinking all the Coke. 


     
    Here are the parts of our class:
     
    1. We'll have API functionality to do basic store stuff like AddProductToStock, GetProductPrice, GetProductStockCount, and RemoveProductFromStock (the core methods)
    2. We have an Initialize() method which is used to load any existing concrete stores from the application configuration.
    3. We have references to all available providers.
    4. We have a reference to the default provider whose functionality is being wrapped.

      Once implemented, all calls will be made to this API class, which will in turn, pass the request to the default provider.

  2. Provider Base Class: This is an internal abstract .NET class in the System.Configuration.Provider namespace that is used to define a provider.  The Initialize() method takes information from the configuration file to construct the concrete provider.  Eventually, we will be overriding the Initialize() method when building our own custom providers by implementing this abstract class.



  3. Application Provider Base: This is an abstract class used to mirror the structure of the API Class, and inherit from the ProviderBase class.  The Application Provider Base inherits from ProviderBase and defines the structure for parent classes by exposing the API methods as abstract members to be implemented by a parent class.

    In our application we'll call the Application Provider Base "StoreProvider".  Notice how the Store Provider class has abstract definitions for the methods defined in the Store class and inherits from the abstract class ProviderBase.



  4. Concrete Provider:  This class is where the methods of the Application Provider Base are implemented.  The Concrete Provider overrides the Initialize() method in order to load information specific to the Concrete Provider from the configuration file.

    So these are the four core classes needed to implement the provider pattern.  The rest of the classes are either used to define what the provider is providing (which, in this case, is a Product class) or utilities for supporting building the objects on the fly or managing the application configuration file.

It takes a bit to wrap one's mind around the basic structure, but once you see how requests are executed against the static Store class and then passed to a reference of a StoreProvider which is implemented by a CornerStoreProvider, you get the basic idea of how the cogs of this machine all work together.

Part II.  Details, Details, Details

Now that we have covered the basics, let's dig a little further into the structure of our program.  Basically we are a building an API for interacting with a store and products within that store.

Let's look at the Product class.


 
This class is not part of the provider pattern, however, we are using it to move data back and forth.  We are defining the name and the wholesale cost for each product.  Each store will have it's own markup on each product so we can calculate the retail cost.   I have also added static methods to get all available products: GetProducts() and Initialize().

The Store class defines our core API for interactions between us (the client), the store and products.


 
The Store Provider exposes these same methods in an abstract class as we discussed earlier.


 
The CornerStoreProvider is our concrete implementation of the StoreProvider.


 
The VendingMachineStoreProvider is another concrete implementation of the StoreProvider. 


 
One interesting method implemented by both the concrete providers is Initialize(). This is the method called from the ProviderBase which gets called from the ProvidersHelper.  This method overrides the ProviderBase implementation and then we are using it to load markup information specific to the store which is in the <store> section of the configuration file.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section name="store" type="ProviderSample.StoreProviderConfigurationSection,

      ProviderSample, Version=1.0.0.0,

      Culture=neutral,PublicKeyToken=null" />

  </configSections>

  <store defaultProvider="CornerStoreProvider">

    <providers>

      <add name="VendingMachineStoreProvider"

           type="VendingMachineStoreProvider"

           Coke="0.75"

           Snickers="0.5"

           Diet_Pills="1.25"

           ></add>

      <add name="CornerStoreProvider"

           type="CornerStoreProvider"

           Coke="0.70"

           Snickers="0.65"

           Diet_Pills=".80"

           ></add>

    </providers>

  </store>

</configuration>

 

public override void Initialize(string name, NameValueCollection config)

{

    if (string.IsNullOrEmpty(name))

        name = "CornerStoreProvider";

 

    if (null == config)

        throw new ArgumentException("config parameter can not be null");

 

    if (string.IsNullOrEmpty(config["description"]))

    {

        config.Remove("description");

        config.Add("description", "A Corner store from which to get a product");

    }

 

    base.Initialize(name, config);

 

    m_inventory.Clear();

 

    foreach (Product p in Product.GetProducts())

    {

        if (string.IsNullOrEmpty(config[p.Name]))

        {

            m_markup.Add(p, 0);

        }

        else

        {

            m_markup.Add(p, Convert.ToDouble(config[p.Name]));

        }

    }
}

 

There is a  ProvidersHelper  in the System.Web.Configuration namespace, but because this is a windows app, I re-wrote this utility class specifically for windows applications using reflection so we wouldn't need to pull in the entire web namespace just for the one class. ProvidersHelper  is used to instantiate the concrete providers and set values defined in the configuration file.  If you want to implement the Provider pattern in a web application, use System.Web.Configuration.ProvidersHelper instead (it has the same interface so it is easy to swap out).


 
There is also a  StoreProviderConfigurationSection which inherits from System.Configuration.ConfigurationSection  This is used to retrieve data about the Providers and default provider from the configuration file.  


 
Part III. Execution.


To use our API, we'll be calling methods through the static Store object .  In this example, if our default provider has stock of some products we can interface with the default provider through the API definition object (the Store).

 

// Get some coke

Product p1 = Product.GetProduct("Coke");

 

// find out the cost

double cost = Store.GetProductPrice(p1);

 

// get it from the store

Store.RemoveProductFromStock(p1);  // remove from inventory         

If we want a different default provider, we just change the configuration file and basically can have another implementation of the API loaded into memory.  Try running the sample project and change the provider to see how the prices change due to the change in markup values defined in the config file.

<store defaultProvider="CornerStoreProvider">

This could be taken a lot further by not only having different markup prices, but by also having completely different functionality in each provider. 

Part IV. Wrap Up

An example of having completely different functionality exposed through one interface is the difference between the ASP.NET 2.0 System.Web.Security.ActiveDirectoryMembershipProvider and the System.Web.Security.SqlMembershipProvider.  The underlying functionality is completely different for each of these objects, but they are both exposed through the same System.Web.Security.Membership object so there is one easy API to code against. 

Now that we have covered how the Provider Pattern works, you should have enough knowledge to be able to write your own MembershipProvider.  If we wanted to build a MembershipProvider that gets information from an xml file instead of active directory or sql, we could declare the following class definition and implement the abstract methods of the base class.

class XmlMembershipProvider: System.Web.Security.MembershipProvider
{
}

We covered a lot of ground and hopefully you have a bit better understanding of how the Provider pattern works.  In most cases, we'll probably be implementing a custom provider to an existing .NET Framework API instead of rolling our own, so if you are interested in seeing the provider classes that can be customized in ASP.NET 2.0 Framework check out the MSDN article:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/ASPNETProvMod_Intro.asp

Until next time,

Happy coding