How to Build Your Very Own Provider Model - Part 1

A few months ago I wrote a popular article on DotNetSlackers entitled How to Write a Provider Model that described the process of writing a provider model for your .NET applications based on the built-in provider mechanism in the .NET Framework.

Publishing that article, I received some feedbacks/requests about the future articles about this topic in more details and in a more advanced level. Some guys were looking for a way to write their own provider model from the scratch.

Writing such a provider model is helpful for your applications in many ways. There are many circumstances where you can't do much with the default provider model in .NET. Before the birth of .NET Framework 2.0 and introduction of provider models in this version, there were many third party implementations of providers for different products so the provider model is not a very complicated thing to implement but it's not documented very well so this post series would help it a lot.

Actually building a provider model is nothing but dealing with some basic knowledge in .NET programming and wrapping up those topics to build it.

Here an in some separate posts, I want to show you how to write your own provider model from the scratch step by step. All these concepts can be inspired for any version of .NET Framework easily and you can extend it based on your needs in any application even though this implementation is almost the main thing and there are just a few things that you need to change/apply for your own applications.

Here I would say that I inspired most of this implementation from the Community Server source code 2-3 years ago and have used it in some projects to now. So somehow this post series is also discovering the Community Server data provider model in some details.

Overview

Building a custom provider model consists of several steps and implementing different components.

You need some basic classes that will be used by your provider model to load and keep data for configuration,

Like the built-in .NET provider models, an XML configuration mechanism is required for the providers to load some information like the name of the model, its type and its custom properties.

On the other hand, the main part of the provider model is where you manage the implementation of an abstraction in your code and you need to implement it.

You also need to implement the base provider model and its final implementation based on your provider.

I try to cover all these steps one by one in details.

Main Classes

There are some main classes that play basic roles in the provider model. These classes are responsible to represent a provider instance and to manage the process of creating instances of providers from their types.

You need to implement two classes for the provider model.

The first class is DataProvider class that simply represents a data provider instance. A data provider would have some obvious properties:

Regarding this background, I can write a simple DataProvider class that has abovementioned properties and has a public constructor that gets an XmlAttributeCollection from the configuration mechanism to set the values of properties and attributes. Knowing the above background, this code is easy to understand.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Collections.Specialized;

using System.Xml;

 

namespace ProviderModelSample.Code

{

    public class DataProvider

    {

        #region Properties

 

        public string Name { get; set; }

 

        public string Type { get; set; }

 

        public NameValueCollection Attributes { get; set; }

 

        #endregion

 

        #region Public Constructors

 

        public DataProvider(XmlAttributeCollection attributes)

        {

            this.Name = attributes["name"].Value;

            this.Type = attributes["type"].Value;

 

            foreach (XmlAttribute attribute in attributes)

            {

                if ((attribute.Name != "name") && (attribute.Name != "type"))

                {

                    if (this.Attributes == null)

                        this.Attributes = new NameValueCollection();

                    this.Attributes.Add(attribute.Name, attribute.InnerText);

                }

            }

        }

 

        #endregion

    }

}

The second main class that you need to build your provider model is a class the manages the process of converting a pure provider (as a DataProvider class instance and an abstract base class) to a working instance to be used in your code.

DataProvider class represents the properties and attributes of a data provider and nothing more! Beside this, later in the next posts you'll see that you need abstract base classes to keep the abstraction of your data provider model (like what you have in .NET provider model). You need a way to create instances of these abstract base classes based on the properties and attributes that you have in your configuration file. Here a third class comes into the play and helps you. I call this class DataProviders. Finally and shortly, in DataProviders class you convert your abstraction to instances.

In DataProviders class you're able to pass parameters to your provider constructor and of course, you're also able to pass properties and attributes to it.

Here is the code for the DataProviders class. Later I talk about each method in details.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Reflection;

using System.Configuration;

using System.Collections.Specialized;

 

namespace ProviderModelSample.Code

{

    public class DataProviders

    {

        #region Private Constructors

 

        private DataProviders()

        {

        }

 

        #endregion

 

        #region Public Methods

 

        public static object CreateInstance(DataProvider dataProvider)

        {

            string connectionString = ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString;

 

            Type type = Type.GetType(dataProvider.Type, true);

 

            object newObject = null;

            if (type != null)

            {

                newObject = Activator.CreateInstance(type,

                    new object[] { connectionString, dataProvider.Attributes });

            }

 

            if (newObject == null)

                throw new ArgumentException(dataProvider.Name);

 

            return newObject;

        }

 

        public static object Invoke(DataProvider dataProvider)

        {

            object[] paramArray = new object[2];

 

            string connectionString = ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString;

 

            paramArray[0] = connectionString;

            paramArray[1] = dataProvider.Attributes;

 

            return CreateConstructorInfo(dataProvider).Invoke(paramArray);

        }

 

        public static ConstructorInfo CreateConstructorInfo(DataProvider dataProvider)

        {

            ConstructorInfo providerConstructor = null;

            try

            {

                Type type = Type.GetType(dataProvider.Type);

 

                Type[] paramTypes = new Type[2];

                paramTypes[0] = typeof(string);

                paramTypes[1] = typeof(NameValueCollection);

 

                providerConstructor = type.GetConstructor(paramTypes);

            }

            catch (Exception ex)

            {

                // Handle the exception

                throw ex;

            }

 

            if (providerConstructor == null)

                throw new ArgumentException(dataProvider.Name);

 

            return providerConstructor;

        }

 

        #endregion

    }

}

DataProviders class consists of a default private constructor as well as three public static methods. The fundamental of DataProviders logic is based on the reflection techniques.

The first method in this class is CreateInstance. CreateInstance gets an instance of  the DataProvider object and returns an object that is actually an instance of the provider that you've passed to it. First I get the database connection string from configuration then load the data provider type and finally use Activator.CreateInstance method to create an instance of the provider model by passing two parameters to its constructor: connection string and NameValueCollection of attributes. At the end I simply return this object.

public static object CreateInstance(DataProvider dataProvider)

{

    string connectionString = ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString;

 

    Type type = Type.GetType(dataProvider.Type, true);

 

    object newObject = null;

    if (type != null)

    {

        newObject = Activator.CreateInstance(type,

            new object[] { connectionString, dataProvider.Attributes });

    }

 

    if (newObject == null)

        throw new ArgumentException(dataProvider.Name);

 

    return newObject;

}

Invoke is the second method in the DataProviders class. Like the CreateInstance, it gets an instance of the DataProvider object and returns another object. But it calls the constructor to initialize the object instance. In this code first I create an array of objects that should be passed to the constructor including the connection string and attributes objects and then call ConstructorInfo.Invoke method to initialize and return the object. Here I need an instance of the ConstructorInfo class that I get from the third method, CreateConstructorInfo.

public static object Invoke(DataProvider dataProvider)

{

    object[] paramArray = new object[2];

 

    string connectionString = ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString;

 

    paramArray[0] = connectionString;

    paramArray[1] = dataProvider.Attributes;

 

    return CreateConstructorInfo(dataProvider).Invoke(paramArray);

}

And finally, the third method, the CreateConstructorInfo method, is a helper method that you saw its application. This function gets an instance of the DataProvider class and returns the constructor info for it. In this code first I load the type for the provider instance then adds constructor parameter types to an array and calls the Type.GetConstructor method to return my ConstrcutorInfo object.

public static ConstructorInfo CreateConstructorInfo(DataProvider dataProvider)

{

    ConstructorInfo providerConstructor = null;

    try

    {

        Type type = Type.GetType(dataProvider.Type);

 

        Type[] paramTypes = new Type[2];

        paramTypes[0] = typeof(string);

        paramTypes[1] = typeof(NameValueCollection);

 

        providerConstructor = type.GetConstructor(paramTypes);

    }

    catch (Exception ex)

    {

        // Handle the exception

        throw ex;

    }

 

    if (providerConstructor == null)

        throw new ArgumentException(dataProvider.Name);

 

    return providerConstructor;

}

These are two main classes that you'll use in your implementation frequently. Now let's move to the second step.

Configuration

In order to build a provider model, you need to have a configuration mechanism that loads the provider configuration from the XML files and then load them into your programming objects. Later you can use these programming objects to work with the provider. These programming objects are instances of the classes that you built in the first step.

This configuration implementation should vary based on your application to be integrated with your configuration system but the code logic would be as same as what I'm going to represent here. In general, this implementation is nothing but some XML manipulations to load values from the XML configuration file to objects.

Below is the source code of SampleConfiguration class that acts as the configuration system for my sample application.

using System;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using System.Xml;

using System.Collections;

 

namespace ProviderModelSample.Code

{

    public class SampleConfiguration

    {

        #region Fields

 

        private XmlDocument _xmlDoc = null;

 

        #endregion

 

        #region Properties

 

        #region Providers

 

        private Hashtable _providers = new Hashtable();

        public Hashtable Providers

        {

            get

            {

                return this._providers;

            }

        }

 

        #endregion

 

        #endregion

 

        #region Public Constructors

 

        public SampleConfiguration()

        {

        }

 

        public SampleConfiguration(XmlDocument doc)

        {

            this._xmlDoc = doc;

            try

            {

                LoadValuesFromXmlDocument();

            }

            catch (Exception ex)

            {

                // Handle the exception

                throw ex;

            }

        }

 

        #endregion

 

        #region Public Methods

 

        public static SampleConfiguration Get()

        {

            SampleConfiguration configuration = new SampleConfiguration();

 

            string path = ConfigurationManager.AppSettings["ConfigurationPath"];

 

            XmlDocument doc = new XmlDocument();

            doc.Load(path);

 

            configuration = new SampleConfiguration(doc);

 

            return configuration;

        }

 

        public XmlNode GetConfigSection(string nodePath)

        {

            return this._xmlDoc.SelectSingleNode(nodePath);

        }

 

        #endregion

 

        #region Private Methods

 

        private void LoadValuesFromXmlDocument()

        {

            // Providers

            XmlNode providers = GetConfigSection("Sample/Providers");

            if (providers != null)

                GetProviders(providers, this._providers);

        }

 

        private void GetProviders(XmlNode node, Hashtable hashTable)

        {

            foreach (XmlNode provider in node.ChildNodes)

            {

                switch (provider.Name)

                {

                    case "add":

                        hashTable.Add(provider.Attributes["name"].Value, new DataProvider(provider.Attributes));

                        break;

                    case "remove":

                        hashTable.Remove(provider.Attributes["name"].Value);

                        break;

                    case "clear":

                        hashTable.Clear();

                        break;

                }

            }

        }

 

        #endregion

    }

}

This configuration class keeps the configuration data as an XmlDocument. It also has a property of Hashtable type that keeps the providers list. This Hashtable consists of provider names as keys and DataProvider instances as values. In the public constructors and public methods, I simply load the XML data into memory and call private LoadValuesFromXmlDocument method to load the configuration into properties. GetProviders is a helper method that gets an XmlNode and a Hashtable and loads the DataProvider instances into the Hashtable. The logic is all about XML manipulation so I don't step in details.

As the final note about the configuration, I would say that some caching can help to improve the performance significantly. Suppose that you need to load all these XML manipulations for each database operation. Of course, it has a direct effect on your performance so usage of caching seems to be mandatory. I eliminated the caching side of things for this sample project just to simplify the code.

In the second post, I'll show you how to write your abstract base classes to define your providers.

[advertisement] Axosoft OnTime 2008 is four developer tools in one: bug tracking, project wiki, feature management, and help desk. It manages your development process so developers can focus on coding. Installed or Hosted – Free Single-user license -- Free 30-day team trial.

8 Comments : 05.23.08

Feedbacks

Pingback from Dew Drop - May 24, 2008 | Alvin Ashcraft's Morning Dew

 avatar
#2
Sam
05.26.2008 @ 12:40 AM

I like your post--although I don't fully understand all the provider stuff. I like your style of coding, thanks

Last Friday I started a post series about building a provider model from the scratch. In the first post

Pingback from Reflective Perspective - Chris Alcock » The Morning Brew #101

 avatar
#5
Paul Kinlan
05.27.2008 @ 3:48 AM

Hi,

Nice Article. I would seriously suggest that you use ConfigurationSections to hold your XML Config, you have implemented alot of functionality that is already handled by the .Net framework.

Sometimes Config Sections can be a pain to create but it is made really easy by the Configuration Section Designer on codeplex. http://www.codeplex.com/csd

In the first and second parts of this post about building a provider model from the scratch, I talked

 avatar
#7
Weekly Links #3 | GrantPalin.com
06.01.2008 @ 9:17 PM

Pingback from Weekly Links #3 | GrantPalin.com

 avatar
#8
My Best Blog Posts in 2008
12.31.2008 @ 1:40 PM

In the past 3.5 years of blogging, I haven’t had such best pick up collections in the end of the year, but now that everybody is writing one, why shouldn’t I write my own?! Collecting this list, I could realize some interesting facts that completely changed

Leave a Comment