Provider Pattern

原文地址:http://www.codeproject.com/Articles/18222/Provider-Pattern

Introduction

Provider pattern is one of the most interesting features that Microsoft introduced in .NET 2.
A lot of features including membership providers, roles providers, profile providers, health monitor event providers, site map providers, and more had the same design concept. These features are not changeable but extendable, and that is the beauty of the provider pattern.

In this article, I will demonstrate the concept of provider pattern itself, and how easy it is to create your own services designed by provider pattern, and make your services extendable and reusable.

Using the Code

Using the code is very simple. Just open the solution file in Visual Studio 2005 and run the application. There is no database or web service to connect, it is simply a console application to give you a full idea of the provider pattern.

Provider Pattern in More Detail

It is not a brand new idea; it came basically from the popular strategy pattern. It is a good idea to have a quick look at the strategy pattern inhere.

I see the strategy pattern as a changeable algorithm depending on different situations or different context. This algorithm is encapsulated in object, this object implements an interface or inherits from an abstracted class.

For example, the Paintbrush saves the same picture in different formats, GIF, BMP,...

Or Microsoft Word saves the document in different formats, doc, rtf, txt,... In my work, we used strategy pattern very often. Maybe, we will come to the top of our design pattern list, one of the implementations that we used, we used in collection builder to build the same collection in different ways.

For example, Customers collection has the same methods and behavior in a certain context, once the context changes, all the behavior changes. We haveAllCustomerscontext and InvoiceCustomer context, ProductCustomer context, and each of them has a different database, sometimes it is a simple table in database or 1-many or many-many relation tables in database. So we create one collection but with different behaviors. I developed an authentication module. This module is very complex, but after I applied a strategy pattern with composite pattern, it became very simple to maintain, or to extend. The user object has a property calledRole, and depending on the context theRole changes. The strategy pattern is beyond the scope of this article, but it is worth studying and implementing this pattern.

However, I still can see a few differences between strategy pattern and provider pattern.

Strategy pattern is a Generic concept, not sticking to a specific technology or global scenarios, but provider pattern in most cases is a configurable service, provided by external source, that you can plug-in to your application, or you may extend this service to create your own custom service, then plug-in to your application.

The service provider should have the same features or contract, that is defined in the base class as contract, but with different behaviors, different formats or different connectivity like using web services, database(s) or XML files or streams.

Creating Your Own Provider

There are 3 major steps to create your own provider.

Step 1: Create Your ServiceBase

To create your own provider pattern, you must inherit your service from System.Configuration.Provider.ProviderBase class:

using System;
using System.Collections.Specialized;

namespace System.Configuration.Provider
{
    // Summary:
    //     Provides a base implementation for the extensible provider model.
    public abstract class ProviderBase
    {
        // Summary:
        //     Initializes a new instance of the 
        //     System.Configuration.Provider.ProviderBase
        //     class.
        protected ProviderBase();

        // Summary:
        //     Gets a brief, friendly description suitable for display in administrative
        //     tools or other user interfaces (UIs).
        //
        // Returns:
        //     A brief, friendly description suitable for display in administrative tools
        //     or other UIs.
        public virtual string Description { get; }
        //
        // Summary:
        //     Gets the friendly name used to refer to the provider during configuration.
        //
        // Returns:
        //     The friendly name used to refer to the provider during configuration.
        public virtual string Name { get; }

        // Summary:
        //     Initializes the provider.
        //
        // Parameters:
        //   config:
        //     A collection of the name/value pairs representing the 
        //     provider-specific attributes
        //     specified in the configuration for this provider.
        //
        //   name:
        //     The friendly name of the provider.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     The name of the provider is null.
        //
        //   System.InvalidOperationException:
        //     An attempt is made to call 
        //     System.Configuration.Provider.ProviderBase.Initialize
        //     (System.String,System.Collections.Specialized.NameValueCollection)
        //     on a provider after the provider has already been initialized.
        //
        //   System.ArgumentException:
        //     The name of the provider has a length of zero.
        public virtual void Initialize(string name, NameValueCollection config);
    }
}

In our sample, I created CurrencyProviderBase:

using System;
using System.Configuration.Provider;

namespace Cli.Lsp.Provider.CurrencyProvider {
    public abstract class CurrencyProviderBase : ProviderBase {
        protected string _name;

        public override string Name {
            get { return _name; }
            }
        protected string _description;

        public override string Description {
            get { return _description; }
            }
        public abstract decimal Convert(Currency cur, decimal value);
        public abstract void Add(Currency cur, DateTime date, decimal factor);
        public abstract void Remove(Currency cur, DateTime date);
        public abstract CurrencyConversionItem[] List
		(Currency cur, DateTime startDate, DateTime endDate);
        }
    }

Step2: Create Your ServiceCollection

To define your own collection class, you should inherit from System.Configuration.Provider.ProviderCollection.

using System;
using System.Collections;
using System.Reflection;

namespace System.Configuration.Provider
{
    // Summary:
    //     Represents a collection of provider objects that 
    //     inherits from System.Configuration.Provider.ProviderBase.
    public class ProviderCollection : ICollection, IEnumerable
    {
        // Summary:
        //     Initializes a new instance of the 
        //     System.Configuration.Provider.ProviderCollection
        //     class.
        public ProviderCollection();

        // Summary:
        //     Gets the number of providers in the collection.
        //
        // Returns:
        //     The number of providers in the collection.
        public int Count { get; }
        //
        // Summary:
        //     Gets a value indicating whether access to the collection is synchronized
        //     (thread safe).
        //
        // Returns:
        //     false in all cases.
        public bool IsSynchronized { get; }
        //
        // Summary:
        //     Gets the current object.
        //
        // Returns:
        //     The current object.
        public object SyncRoot { get; }

        // Summary:
        //     Gets the provider with the specified name.
        //
        // Parameters:
        //   name:
        //     The key by which the provider is identified.
        //
        // Returns:
        //     The provider with the specified name.
        public ProviderBase this[string name] { get; }

        // Summary:
        //     Adds a provider to the collection.
        //
        // Parameters:
        //   provider:
        //     The provider to be added.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     The System.Configuration.Provider.ProviderBase.Name of provider is null.-
        //     or -The length of the System.Configuration.Provider.ProviderBase.Name of
        //     provider is less than 1.
        //
        //   System.ArgumentNullException:
        //     provider is null.
        //
        //   System.NotSupportedException:
        //     The collection is read-only.
        public virtual void Add(ProviderBase provider);
        //
        // Summary:
        //     Removes all items from the collection.
        //
        // Exceptions:
        //   System.NotSupportedException:
        //     The collection is set to read-only.
        public void Clear();
        //
        // Summary:
        //     Copies the contents of the collection to the given array starting at the
        //     specified index.
        //
        // Parameters:
        //   array:
        //     The array to copy the elements of the collection to.
        //
        //   index:
        //     The index of the collection item at which to start the copying process.
        public void CopyTo(ProviderBase[] array, int index);
        //
        // Summary:
        //     Returns an object that implements the 
        //     System.Collections.IEnumerator interface
        //     to iterate through the collection.
        //
        // Returns:
        //     An object that implements System.Collections.IEnumerator to iterate through
        //     the collection.
        public IEnumerator GetEnumerator();
        //
        // Summary:
        //     Removes a provider from the collection.
        //
        // Parameters:
        //   name:
        //     The name of the provider to be removed.
        //
        // Exceptions:
        //   System.NotSupportedException:
        //     The collection has been set to read-only.
        public void Remove(string name);
        //
        // Summary:
        //     Sets the collection to be read-only.
        public void SetReadOnly();
    }
}

In our sample, CurrencyCollection is the service collection:

using System;
using System.Configuration.Provider;

namespace Cli.Lsp.Provider.CurrencyProvider
{
    public class CurrencyCollection : ProviderCollection
    {
        public override void Add(ProviderBase provider)
        {
            if (provider == null)
                throw new ArgumentNullException("The provider parameter cannot be null.");

            if (!(provider is CurrencyProviderBase))
                throw new ArgumentException
		("The provider parameter must be of type MyProviderProvider.");

            base.Add(provider);
        }

        new public CurrencyProviderBase this[string name]
        {
            get { return (CurrencyProviderBase)base[name]; }
        }

        public void CopyTo(CurrencyProviderBase[] array, int index)
        {
            base.CopyTo(array, index);
        }
    }
}

Step 3: Implement First Concrete Provider

You should to implement at least one of the providers and test it carefully before releasing it in your own company library.

using System;
using System.Configuration;
using System.Configuration.Provider;

namespace Cli.Lsp.Provider.CurrencyProvider {
    public class SQLCurrencyConversion : CurrencyProviderBase {
        private String connectionString;

        public String ConnectionString {
            get { return connectionString; }
            set { connectionString = value; }
            }


        public override void Initialize(string name, 
		System.Collections.Specialized.NameValueCollection config) {
            #region Attributes check
            if ((config == null) || (config.Count == 0))
                throw new ArgumentNullException
		("You must supply a valid configuration parameters.");
            this._name = name;
            #endregion

            #region Description
            if (string.IsNullOrEmpty(config["description"])) {
                throw new ProviderException("You must specify a description attribute.");
                }
            this._description = config["description"];
            config.Remove("description");
            #endregion

            #region ConnectionString
            if (String.IsNullOrEmpty(config["connectionStringName"]))
                throw new ProviderException("The connection string is invalid.");
            connectionString = config["connectionStringName"];
            config.Remove("connectionStringName");

            ConnectionStringsSection cs =
                (ConnectionStringsSection)ConfigurationManager.GetSection
			("connectionStrings");
            if (cs == null)
                throw new ProviderException
		("An error occurred retrieving the connection strings section.");
            if (cs.ConnectionStrings[connectionString] == null)
                throw new ProviderException("The connection string could not be 
			found in the connection strings section.");
            else
                ConnectionString = 
		cs.ConnectionStrings[connectionString].ConnectionString;
            #endregion

            #region Extra Attributes validations
            if (config.Count > 0) {
                string extraAttribute = config.GetKey(0);
                if (!String.IsNullOrEmpty(extraAttribute))
                    throw new ProviderException
			("The following unrecognized attribute was found in 
				" + Name + "'s configuration: '" +
                                                extraAttribute + "'");
                else
                    throw new ProviderException("An unrecognized attribute was 
				found in the provider's configuration.");
                }
            #endregion
            }

        public override decimal Convert(Currency cur, decimal value) {
            Console.WriteLine
		("---- Convert method was Invoked SQL Currency conversion ----");
            Console.WriteLine("Provider Description ={0}", this.Description);
            Console.WriteLine("Connection string ={0}", this.ConnectionString);
            Console.WriteLine("Convert({0},{1}) ", 
			Enum.GetName(typeof(Currency), cur), value);
            Console.ReadLine();
            return 3.4m;
            }

        public override void Add(Currency cur, DateTime date, decimal factor) {
            Console.WriteLine("---- Add method was Invoked SQL Currency conversion ----");
            Console.WriteLine("Provider Description ={0}", this.Description);
            Console.WriteLine("Connection string ={0}", this.ConnectionString);
            Console.WriteLine("Add({0},{1},{2}) ", 
		Enum.GetName(typeof(Currency), cur), date, factor);
            Console.ReadLine();
            }

        public override void Remove(Currency cur, DateTime date) {
            Console.WriteLine
		("---- Remove method was Invoked SQL Currency conversion ----");
            Console.WriteLine("Provider Description ={0}", this.Description);
            Console.WriteLine("Connection string ={0}", this.ConnectionString);
            Console.WriteLine("Remove({0},{1}) ", 
		Enum.GetName(typeof(Currency), cur), date);
            Console.ReadLine();
            }


        public override CurrencyConversionItem[] List
		(Currency cur, DateTime startDate, DateTime endDate) {
            Console.WriteLine
		("---- List method was Invoked SQL Currency conversion ----");
            Console.WriteLine("Provider Description ={0}", this.Description);
            Console.WriteLine("Connection string ={0}", this.ConnectionString);
            Console.WriteLine("List({0},{1},{2}) ", 
		Enum.GetName(typeof(Currency), cur), startDate, endDate);
            Console.ReadLine();
            return new CurrencyConversionItem[0];
            }
        }
    }    

In the previous code, one of the most important codes is Initialize(…) method. You should retrieve the minimum information that should be in the configuration file.

Step 3: Creating ProviderManger

Moreover, to simplify the pattern, create a static class ProviderManagerthat has twostatic properties; Provider (for default provider), andProviders(for other providers that are registered in application configuration), to simplify the access in runtime.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值