How to Write a Provider Model
In this article Keyvan teaches you how to write your own data provider for .NET applications using the provider model.
After the birth of .NET 2.0, the provider model became common in .NET development. Most built-in features that deal with data support providers. Many third party applications, components and libraries are using data providers, too. In my opinion talking about data providers in .NET is unnecessary because almost all the readers of this article know the role of data providers in .NET development. After the .NET Framework 2.0 was released, data providers became a very common part of .NET development. Not only were there many common built-in data providers in .NET, but community members and companies created third party components for other data storage systems like Oracle or MySQL.
Nowadays you can't find anything in .NET that doesn't have appropriate data providers for all common data storage systems. Usually these data providers are free and open source so you can adapt them for your own needs.
However, one thing that isn't documented very well is how to write your data provider model for .NET applications. I looked for such a resource but didn't obtain any good results, so I decided to write it myself.
In this article you will learn how to write your own data provider in order to bring this common feature to your .NET components or applications.
There are some benefits in using the data provider model in a component or application. You can probably guess, but let me outline them here:
- You abstract your data manipulation portion of the code and isolate it from other. In this way, code becomes easier to write.
- It lets you easily switch between different data storage systems. You can use SQL Server or Oracle or XML just by implementing the appropriate data provider.
- You can use multiple data storage systems in parallel for different parts of your application or your components. In other words you can, for example, use SQL Server to store data about products and use XML to store details about orders.
But how can a data provider be written in .NET? To demonstrate the concepts in this article I will use the C# language, but Visual Basic can be used as well, without any differences in the structure. Only a code translation is required.
In a nutshell, the process of implementing the provider model consists of creating four main classes:
- Main data provider class.
- Data provider collection class.
- Data provider configuration class.
- Data provider manager class.
After writing these classes you usually need to implement at least a default data provider for your application or component, as well as a SQL Server or XML data provider. This lets your users be able to use your data provider without implementing a custom one. It also acts as a good implementation example to everyone who wants to write a new provider.
In the next section I'll discuss the implementation of each of the above mentioned classes with the help of a simple example. I will also implement a default SQL provider for this example.
Main Data Provider Class
The main data provider class is where you define the abstract representation of all the data manipulation methods that you need in your provider. Here you write a public abstract class that must be derived from the System.Configuration.Provider.ProviderBase
base class and has some public abstract methods that must be implemented by different data providers.
As a result, you always need to have a reference to System.Configuration
in your provider project.
Let's start by taking a look at Listing 1, which shows the code for the main data provider class.
Listing 1: Code for the main data provider class
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration.Provider;
- namespace SampleProvider
- {
- public abstract class DataProvider : ProviderBase
- {
- // Adds a text value for a specified ID to the storage
- public abstract void AddText(int id, string text);
- // Gets the text associated to a specified ID
- public abstract string GetText(int id);
- }
- }
As you see, the main provider class is an abstract class derived from ProviderBase
. It defines two public abstract methods to add a text value for a given ID or get a text for a specified ID. These two methods must be implemented in different data provider classes.
You should understand that this class is the place where you write the abstract definition of your provider methods.
Data Provider Collection
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.
To implement the data provider collection class you need to derive it from the System.Configuration.ProviderCollection
base class. ProviderCollection
is a class that exposes some properties and methods to work with the list of various data providers declared in your configuration file. This class is simply a collection class that implements the ICollection
and IEnumerable
base interfaces, so there isn't any major point to mention about it.
The key point here is overriding the default behavior of the ProviderCollection
class in order to return an instance of your main data provider class for a specified index.
Listing 2 shows the code for the DataProviderCollection
class, where I've implemented my data provider collection.
Listing 2: Data provider collection class
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration.Provider;
- namespace SampleProvider
- {
- public class DataProviderCollection : ProviderCollection
- {
- // Return an instance of DataProvider
- // for a specified provider name
- new public DataProvider this[string name]
- {
- get { return (DataProvider)base[name]; }
- }
- }
- }
Usually, it is enough to start from a skeleton implementation like the one shown above and then use it for your concrete providers by replacing the names of the parameters and types.
Data Provider Configuration
The next step in developing a provider is writing a data provider configuration class. It 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. For desktop applications, this is the App.config file and, for web applications, it is the Web.config file. Please note that there is no difference in the implementation with respect to the structure of these files.
To implement this class you need to derive it from the System.Configuration.ConfigSection
base class and add some code for your own data provider requirements.
In your configuration class, you need to define a property of type System.Configuraiton.ProviderSettingsCollection
for the collection of provider settings. The property needs to return an instance of this type by getting it from the base class.
In addition, you can also add as many properties as you like, based on the different parameters that you need to extract from the configuration sections.
All these properties must be decorated with the ConfigurationProperty
attribute. This attribute gets a string name as a parameter and has an optional DefaultValue
property for specifying a default value.
Listing 3 updates the sample provider with a data provider configuration class.
Listing 3: The data provider configuration class
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration;
- namespace SampleProvider
- {
- public class DataProviderConfiguration : ConfigurationSection
- {
- [ConfigurationProperty("providers")]
- public ProviderSettingsCollection Providers
- {
- get
- {
- return (ProviderSettingsCollection)base["providers"];
- }
- }
- [ConfigurationProperty("default", DefaultValue = "SqlProvider")]
- public string Default
- {
- get
- {
- return (string)base["default"];
- }
- set
- {
- base["default"] = value;
- }
- }
- }
- }
In this code I have defined two properties. The first property is called Providers and it's of type ProviderSettingsCollection
. It represents the <providers
/>
element in the application's configuration file. The value is retrieved by the base ConfigurationSection
class.
The second property, Default, gets or sets the default provider name as a string. By default, this value is set to SqlProvider
.
Data Provider Manager
The last step in developing the data provider is creating the data provider manager class. To implement this class you don't need to inherit from a base class or implement an interface. Actually, you just need to write code based on your requirements.
The main purpose of the data provider manager is 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. Then, you create an instance of the concrete provider in memory.
In order to work, your data provider manager must have a static public constructor that calls the Initialize
method.
Listing 4 shows the code for my DataProviderManager
class.
Listing 4: The data provider manager class
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration;
- using System.Web.Configuration;
- namespace SampleProvider
- {
- public class DataProviderManager
- {
- private static DataProvider defaultProvider;
- private static DataProviderCollection providers;
- static DataProviderManager()
- {
- Initialize();
- }
- private static void Initialize()
- {
- DataProviderConfiguration configuration =
- (DataProviderConfiguration)
- ConfigurationManager.GetSection("SampleProvider");
- if (configuration == null)
- throw new ConfigurationErrorsException
- ("SampleProvider configuration section is not set correctly.");
- providers = new DataProviderCollection();
- ProvidersHelper.InstantiateProviders(configuration.Providers
- , providers, typeof(DataProvider));
- providers.SetReadOnly();
- defaultProvider = providers[configuration.Default];
- if (defaultProvider == null)
- throw new Exception("defaultProvider");
- }
- public static DataProvider Provider
- {
- get
- {
- return defaultProvider;
- }
- }
- public static DataProviderCollection Providers
- {
- get
- {
- return providers;
- }
- }
- }
- }
In the above code snippet, I’ve declared two static fields that hold the current provider (Provider) and a collection of all defined providers in the configuration file (Providers). I’m going to set appropriate values for these two fields in the initialization phase.
In the static public constructor I've called theInitialize
method, which simply gets an instance of the DataProviderConfiguration
class by calling the ConfigurationManager.GetSection
method with the configuration element name that I've chosen for my provider (in this case, it is <ProviderSample
/>).
The next step was to set the Providers
field to the appropriate value by calling the ProvidersHelper.InstantiateProviders
method, which is a very good helper method provided by the .NET Framework. Finally, I set my providers to be read-only and then I set the default provider.
Default SQL Data Provider
At this point we're done with the implementation of the data provider model. It's very common to have a default data provider in your model to let users use it for their needs. This provider is often implemented for the most appropriate storage system.
Here I'm going to implement a SQL Server data provider for my sample provider model, to show you how easy the implementation can be. This is a result of the abstraction that we achieve by leveraging the provider model.
The code in Listing 5 shows a SqlDataProvider
class that implements the SQL data provider.
Listing 5: The SQLDataProvider class
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Data.SqlClient;
- using System.Configuration;
- using System.Collections.Specialized;
- namespace SampleProvider
- {
- public class SqlDataProvider : DataProvider
- {
- private string _connectionString = string.Empty;
- public override void Initialize(string name, NameValueCollection config)
- {
- base.Initialize(name, config);
- this._connectionString = config["connectionString"];
- if (string.IsNullOrEmpty(this._connectionString))
- throw new ConfigurationErrorsException
- ("connectionString must be set to the appropriate value");
- }
- public override void AddText(int id, string text)
- {
- using (SqlConnection connection = new SqlConnection(this._connectionString))
- {
- // For real world scnearios use parameterized SQL to avoid SQL injections!
- string commandText = string.Format
- ("INSERT INTO SampleTable (Id, Value) VALUES ({0}, '{1}')", id, text);
- SqlCommand command = new SqlCommand(commandText, connection);
- command.CommandType = System.Data.CommandType.Text;
- connection.Open();
- command.ExecuteNonQuery();
- connection.Close();
- command.Dispose();
- }
- }
- public override string GetText(int id)
- {
- string result = string.Empty;
- using (SqlConnection connection = new SqlConnection(this._connectionString))
- {
- string commandText = string.Format("SELECT Value FROM SampleTable WHERE Id = {0}", id);
- SqlCommand command = new SqlCommand(commandText, connection);
- command.CommandType = System.Data.CommandType.Text;
- connection.Open();
- result = command.ExecuteScalar() as string;
- connection.Close();
- command.Dispose();
- }
- return result;
- }
- }
- }
Note that you need to inherit from your main data provider class and implement this abstract base class for your data provider. In addition override methods and write your own code, you usually need to override the Initialize method to get the name of the provider model along with a NameValueCollection instance with all the attributes in your provider configuration section. Using this NameValueCollection instance, you can retrieve information (like a connection string) from the configuration file.
Configuration
Okay, the provider model and a sample SQL data provider for this model are ready. Now we can configure our applications to use the provider model.
To configure the application for using the provider model first we need to define our provider configuration section in <configSections
/>
element, which is the direct child element of <configuration
/>. Here we can add a <section
/>
element with the name of the provider model configuration section element and the type of our data provider configuration class. Based on your needs, you can also embed the <section
/>
element within a <sectionGroup
/>
element.
Listing 6 shows the configuration for my sample provider model in an ASP.NET 3.5 web application configuration file. Please pay more attention to the last <section
/>
element in bold.
Listing 6: Configuration of the sample provider
- <configSections>
- <sectionGroup name="system.web.extensions"
- type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions,
- Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
- <sectionGroup name="scripting"
- type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions,
- Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
- <section name="scriptResourceHandler"
- type="System.Web.Configuration.ScriptingScriptResourceHandlerSection,
- System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false"
- allowDefinition="MachineToApplication"/>
- <sectionGroup name="webServices"
- type="System.Web.Configuration.ScriptingWebServicesSectionGroup,
- System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
- PublicKeyToken=31BF3856AD364E35">
- <section name="jsonSerialization"
- type="System.Web.Configuration.ScriptingJsonSerializationSection,
- System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
- PublicKeyToken=31BF3856AD364E35" requirePermission="false"
- allowDefinition="Everywhere" />
- <section name="profileService"
- type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions,
- Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
- requirePermission="false" allowDefinition="MachineToApplication" />
- <section name="authenticationService"
- type="System.Web.Configuration.ScriptingAuthenticationServiceSection,
- System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
- PublicKeyToken=31BF3856AD364E35" requirePermission="false"
- allowDefinition="MachineToApplication" />
- <section name="roleService"
- type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions,
- Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
- requirePermission="false" allowDefinition="MachineToApplication" />
- </sectionGroup>
- </sectionGroup>
- </sectionGroup>
- <section name="SampleProvider"
- type="SampleProvider.DataProviderConfiguration, SampleProvider" />
- </configSections>
The next and last step is to add the main configuration section for the provider model within the <configuration
/>
element.
Listing 7 shows how I added this section to my configuration file.
Listing 7: Main configuration section
- <SampleProvider default="SqlProvider">
- <providers>
- <add name="SqlProvider"
- type="SampleProvider.SqlDataProvider, SampleProvider"
- connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\DB.mdf;Integrated
- curity=True;User Instance=True" />
- </providers>
- </SampleProvider>
Test
Now we're ready to test the provider model. I created an ASP.NET 3.5 web application project that has a simple page which gets input from the user and adds data to database. Then, it retrieves a value associated to a specified ID.
Listing 8 shows the source code for the main page of this web application, which is written using a code-inline style.
Listing 8: The main page of the web application
- <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SampleSite._Default" %>
- <%@ Import Namespace="SampleProvider" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <script runat="server" language="c#">
- protected void btnAdd_Click(object sender, EventArgs e)
- {
- try
- {
- DataProviderManager.Provider.AddText
- (Convert.ToInt32(txtId.Text), txtValue.Text);
- lblResult.Text = "Data added successfully!";
- }
- catch (Exception ex)
- {
- lblResult.Text = ex.Message + "<br /> " + ex.StackTrace;
- }
- }
- protected void btnSelect_Click(object sender, EventArgs e)
- {
- try
- {
- lblData.Text = DataProviderManager.Provider.GetText
- (Convert.ToInt32(txtIdToSelect.Text));
- }
- catch (Exception ex)
- {
- lblData.Text = ex.Message + "<br /> " + ex.StackTrace;
- }
- }
- </script>
- <head runat="server">
- <title>Untitled Page</title>
- </head>
- <body>
- <form id="form1" runat="server">
- <div>
- <h1>
- Add Data</h1>
- Enter ID: <asp:TextBox ID="txtId" runat="server"></asp:TextBox><br />
- Enter Value: <asp:TextBox ID="txtValue" runat="server"></asp:TextBox><br />
- <asp:Button ID="btnAdd" runat="server" Text="Add" OnClick="btnAdd_Click" /><br />
- <asp:Label ID="lblResult" runat="server" Text=""></asp:Label>
- </div>
- <div>
- <h1>
- Select Data</h1>
- Enter ID: <asp:TextBox ID="txtIdToSelect" runat="server"></asp:TextBox><br />
- <asp:Button ID="btnSelect" runat="server" Text="Select" OnClick="btnSelect_Click" /><br />
- <asp:Label ID="lblData" runat="server" Text=""></asp:Label>
- </div>
- </form>
- </body>
- </html>
Now, I run this application and add my values to the database. Figure 1 shows the output when I add my data to the database.
Figure 1: Adding data
Figure 2 shows the output when I select the data from the database.
Figure 2: Selecting the data
Summary
In this article I showed you how to implement the data provider model for your .NET applications and libraries. You can use it for your own needs or as a template for implementing providers for various data storage systems.
After giving an introduction and a quick overview of the process of implementing a provider model in .NET, I talked about implementing the main data provider class. Then, I talked about implementing the data provider collection and data provider configuration classes that can be used for your data provider configuration.
After that, I discussed about the data provider manager class, which is responsible for loading and using a concrete provider.
Next, I wrote a default SQL data provider for my example, to show you how easy it can be to implement a data provider model in .NET. I've then configured an ASP.NET 3.5 web application to test the provider model.