Custom Handlers...
The web.config file is an excellent way to store configuration information, but can be restrictive. By creating a custom configuration section, information can be stored in whatever way possible inside the configuration file through the use of a custom handler.
In applications I've written, I've often found the web.config file constrictive. I've needed more functionality than just the appSettings section. While having a configuration file was a nice enhancement over ASP, I needed more capabilities than just a key/value pair.
With the .NET framework, it is possible to create a custom configuration section in the configuration file. Each configuration section handler is linked to a .NET class by an entry in the <configuration> element. The .NET framework, when loading all of the sections in the runtime, will call the Create method in the appropriate configuration handler, instantiate an object (either a standard class like NameValueCollection, or a custom one) with the data from the custom section, and return it to the caller. For example, the desired XML is this:
|
<configuration> <configSections> <section name="serverEnvironments" type="CustomHandlers.ConnectionSettingsHandler, CustomHandlers" /> </configSections> <serverEnvironments> <environment name="Development"> <database>myDB</database> <server>sqlDev</server> <userID>brianDev</userID> <password>pwdDev</password> </environment> <environment name="Production"> <database>myDB</database> <server>sqlProd</server> <userID>brianProd</userID> <password>pwdProd</password> </environment> </serverEnvironments> </configuration> |
Note the configSections section. This section defines the custom handler that will handle the <serverEnvironments> element. To parse this element, the entire XML segment is passed along to the Create method. The XML is available as an XmlNode; the approach this article will take is to loop through the child nodes, get the inner element text values, and assemble a connection string.
The credentials are repeated in case of any security measures varying the credentials. It could be possible to define a default entry, or to duplicate the credentials in the "Development" entry, but that wasn't the approach that was taken. Instead, the information must be duplicated if it remains the same.
To access an element in a custom section handler, the ConfigurationSettings object has a GetConfig method, which passes in a partial XPath statement mapping to the custom section. For example, the Custom Section Handler returns a Hashtable. The statement ConfigurationSettings.GetConfig("serverEnvironments") calls the configuration handler, which invokes the create method. The create method is responsible for parsing the Xml section, creating the object to handle the information (in this case a Hashtable), and return it to the caller. GetConfig returns an object type, and must be converted to the Hashtable type. The configuration handler is as follows, implementing IConfigurationSectionHandler, and implementing code in the Create method, as shown:
|
public class ConnectionSettingsHandler : IConfigurationSectionHandler { public object Create(object parent, object configContext, XmlNode section) { Hashtable _list = new Hashtable(); //Element node validation if (section.Name != "serverEnvironments") throw new ConfigurationException(); foreach (XmlElement node in section.ChildNodes) { //Element node validation if (node.Name != "environment") { throw new ConfigurationException(); } string key = node.GetAttribute("name"); string database = node["database"].InnerText; string server = node["server"].InnerText; string user = node["userID"].InnerText; string pwd = node["password"].InnerText; //Build the connection string StringBuilder conn = new StringBuilder(); if (database != null && database.Length > 0) conn.Append("Initial Catalog=" + database + ";"); if (server != null && server.Length > 0) conn.Append("Data Source=" + server + ";"); if (user != null && user.Length > 0) conn.Append("User ID=" + user + ";"); if (pwd != null && pwd.Length > 0) conn.Append("Password=" + pwd + ";"); _list.Add(key, conn.ToString()); } return _list; } } |
This object returns a Hashtable as the list. The configuration section has four parameters; however, only the connection string is desired, and thus, a Hashtable is a good object to retain this information. The key is the name attribute for the <environment> element, and will come into play a little later.
Instead of dealing with the Hashtable directly, the ConnectionSettings class defines an AppSettings parameter, retrieving the configuration section and returning the results for us. AppSettings is declared as static and is globally available to all of the users. Based on the key passed in, if a value exists, the Hashtable returns the appropriate connection string; otherwise, and empty string is returned.
|
public class ConnectionSettings { private static Hashtable _coll; public static string AppSettings(string key) { if (_coll == null) _coll = (Hashtable)ConfigurationSettings.GetConfig("serverEnvironments"); try { return _coll[key].ToString(); } catch { return string.Empty; } } } |
This custom object will return the concatenated connection string tThis custom connection string section is a more dynamic way of storing connection strings, rather than dealing with each segment individually. This approach can be more useful, but is confined to one database in one environment. It can be made more dynamic with some coding effort. For example, the database environment and the database name can be stored as attributes so the configuration file can store multiple databases for multiple database systems.
You may download the code here.