Introduction
There are several features of the .NET framework that we generally overlook or are left unimplemented in our business solutions, but these small, although very useful, features can make our life really cool. From the stack of those features, today, I want to discuss the powerful feature of custom configuration, which enables us to integrate our application layers from a single configuration file, in a clear, declarative way.
Background
Some days ago, I was working on a business solution using ASP.NET 2.0, C#, SQL Server 2005, etc. It was a Workflow based large Enterprise application. There were several generic (please don't get confused with the .NET Framework Generic feature) layers which were responsible for serving different types of services. These layers were supposed to share some common configuration related information (e.g.: database connection strings, LDAP connection string, and much more) which were declared in the web.config file (in the ConnectionStrings
block).
Now, the problem was how to share these connection strings (or various settings) around the layers, so that we don't need to hardcode the key names.
The solution was very simple, and was inbuilt in the .NET Framework. We just took the help of System.Configuration
, and we were up with a declarative syntax to share the configuration from the web.config file.
Implementing a custom configuration
It is always a better option for me, and I believe for all, to go through a concrete implementation for grasping the concept. So, I would like to give an example of a class library project which in turn needs to be integrated in a web application, and needs to share some configurations (like database connection string and Active Directory connection string)
For declaring the connection strings, .NET Framework 2.0 has a very unique section in the configuration file: the <connectionStrings>
section. Suppose we have two entries in the <connectionStrings>
section, something like in the following snippet:
<connectionStrings>
<add name="DBConnection"
connectionString="string to connect database"
providerName="System.Data.SqlClient"/>
<add name="ADConnection"
connectionString="string to connect Active Directory"/>
</connectionStrings>
Now, let's go to the class library project which needs these two connections (the ASP.NET application also needs these connections for its internal business logic implementation).
There are two ways to achieve sharing of these connection strings:
- Hard coding the key names (
DBConnection
, Adconnection
) in the class library. - Sharing the connection string keys with a declarative syntax for the class library.
The first option is not quite acceptable because in case of any changes in the key names, the class library code needs to modified, or if it is a generic module (something like a utility module) that we can plug-in to various applications, then it will be too hard to accomplish (the same reason that the keys are hardcoded in the code). But in the case of the second option, we need not modify the code of the class library, so it's much easier when it comes to integration and manageability.
Now, to accomplish this task, we just need to add a class which needs to inherit from System.Configuration.ConfigurationSection
.
Please go though the following class declaration to get an understanding on this:
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
namespace CustomSettingsProvider
{
internal class CustomSettings:ConfigurationSection
{
[ConfigurationProperty("DBConnection",IsRequired=true)]
public string DataBaseConnection
{
get
{
return GetDBConnectionString(this["DBConnection"]);
}
set
{
this["DBConnection"] = value;
}
}
[ConfigurationProperty("AdConnectionString", IsRequired = true)]
public string ADConnection
{
get {
return GetADConnectionString(this["AdConnectionString"];
}
set {
this["AdConnectionString"] = value;
}
}
private string GetDBConnectionString(string connectionKey)
{
return ConfigurationManager.ConnectionStrings[connectionKey].ConnectionString;
}
private string GetADConnectionString(string adConnectionKey)
{
return ConfigurationManager.ConnectionStrings[adConnectionKey].ConnectionString;
}
}
}
This class will be used internally by the class library to consume the configuration settings that will be done declaratively in the configuration file.
Now, going to the most interesting elements of the above class:
ConfigurationProperty attribute
This attribute exposes the properties that need to be configured from any configuration file. There are a good number of parameters that we can use, for the default value, the regular expression for validating the values assigned to these properties.
ConfigurationSection class
This class provides the basic plumbing for creating the custom configuration sections and configuration properties. An instance of this class exposes a collection of all the configurable properties.
The private methods
These methods actually extract the connection strings depending upon the key provided in the configuration property attribute.
Now, let's see how any other class in the class library can consume these configuration properties. For this, we will go through another class implementation and also the custom configuration section of the configuration file:
public class CustomSettingsConsumer
{
private string _dbConnection;
private string _adConnection;
public string Connection
{
get { return this._dbConnection; }
}
public string ADConnection
{
get { return this._adConnection; }
}
public CustomSettingsConsumer()
{
this._dbConnection = ((CustomSettings)
ConfigurationManager.GetSection("CustomSettings")).DataBaseConnection;
this._adConnection = ((CustomSettings)
ConfigurationManager.GetSection("CustomSettings")).ADConnection;
}
}
Here is the configuration file:
<configSections>
<section name="CustomSettings"
type="CustomSettingsProvider.CustomSettings"/>
</configSections>
<CustomSettings DBConnection="DBConnection"
AdConnectionString="ADConnection" />
<section name=""CustomSettings""
type=""CustomSettingsProvider.CustomSettings"">
</customsettings>
The <ConfigSections>
contains our custom configuration. The value of the name
attribute should match the GetSection
method's parameter value (refer to the above consumer code). Otherwise, we will not be able to extract the configuration values for the class library. The type
attribute represents the class name which is inherited from the System.Configuration.ConfigurationSection
class. Now, going to the declarative initialization of the configuration property. The <CustomSettings>
element does the initialization here. If you follow the CustomSettings
class, you will see that there are two custom configuration properties: DBConnection
and ADConnectionString
. We are actually initializing the values here (declaratively) just by passing the connection string key names. So, total integration becomes quite easy.
Now, if you follow the constructor of the CustomSettingsConsumer
class, you can see how easy it is to get the custom configuration values and to implement any business logic using those resources.
Point of interest
The most important thing that we do after creating the individual layers of a solution is Integration. .NET Framework makes this step quite easy by exposing the System.Configuration assembly. There are many potential classes that we can dig in for making the configuration portion of any application easy and as per our needs.