Hacking Connection String Settings

The .NET Framework configuration system was welcomed with a number of significant improvements in the 2.0 release. Despite the many enhancements to the configuration system, particularly the XML-to-class mapping, there are still a number of failings. A few of the most significant issues in my opinion are: the inability to simply point to any valid *.xml file and load it into a Configuration object, limited or no ability to make configuration changes at run-time, and numerous BCL implementations that only use the configuration system without an alternate means of configuration via code. I'm not going to discuss all of these topics; however, I thought I would share some recent experiences where I was required to effectively hack the configuration system in order to get it to do what I wanted.

The story begins when I wanted to create a custom SqlProfileProvider implementation. By default, the SqlProfileProvider does everything I needed except that it only allows for the connectionStringName attribute in the application configuration. Following the wisdom of my astute colleague, James Zimmerman, I'm in the habit of entering connection string information in UDDI. This is a cogent assertion as a data source can be thought of a type of service and the connection string is kind of binding for that service. I assumed it would be simple to subclass the SqlProfileProvider class and override the Initialize method to supply required connection string or IDbConnection object dynamically. I was wrong.

Unfortunately, the SqlProfileProvider will only work if the connection string is present in the <connectionStrings/> section in the application configuration. This is obviously a problem since the desired result is to resolve the connection string dynamically. The next idea was to resolve the connection string and add the expected item into the ConfigurationManager.ConnectionStrings collection. Again, I was stopped in my tracks; the collection is read-only. At this point, I had pretty much given up, but I thought it was also worth adding an empty connection string entry and subsequently updating the value at run-time. Once more, I was quickly met by epic failure.

Most people probably would have concluded that dynamically adding the connection string is not possible and moved on. In a last ditch effort, I began to research the feasibility of performing invasive surgery on the configuration system - enter Reflector. Using Reflector, I was able to perform a biopsy that confirms that the connection strings settings collection is made read-only as soon as it is parsed. Fortunately, the read-only state is governed by the simple toggling of a private Boolean field. One caveat is that the ConfigurationElement and ConfigurationElementCollection have different read-only flags. Since the ConfigurationElementCollection inherits from ConfigurationElement, both values must be set to false in order to make any changes. Armed with this information, I was able to use Reflection to perform a bypass and dynamically insert a connection string.

The first step to implement this solution is to resolve the fields via Reflection.

private static FieldInfo elementField;

private static FieldInfo collectionField;

private static void EnsureFieldInfo()

{

if ( elementField != null )

return;

new ReflectionPermission( ReflectionPermissionFlag.MemberAccess | ReflectionPermissionFlag.RestrictedMemberAccess ).Assert();

var flags = BindingFlags.Instance | BindingFlags.NonPublic;

try

{

elementField = typeof( ConfigurationElement ).GetField( "_bReadOnly", flags );

collectionField = typeof( ConfigurationElementCollection ).GetField( "bReadOnly", flags );

}

finally

{

CodeAccessPermission.RevertAssert();

}

if ( elementField == null )

throw new MemberAccessException( string.Format( null, "The operation to access field '{0}' failed.", "_bReadOnly" ) );

if ( collectionField == null )

throw new MemberAccessException( string.Format( null, "The operation to access field '{0}' failed.", "bReadOnly" ) );

}

Now that we have the fields, we can build a couple of simple methods to do the work.

private static void SetElementIsReadOnly( ConfigurationElement element, bool readOnly )

{

elementField.SetValue( element, readOnly );

}

private static void SetCollectionIsReadOnly( ConfigurationElementCollection collection, bool readOnly )

{

SetElementIsReadOnly( collection, readOnly );

collectionField.SetValue( collection, readOnly );

}

We now have everything we need to dynamically add a connection string.

protected virtual void AddConnectionString( ConnectionStringSettings settings )

{

if ( !ConfigurationManager.ConnectionStrings.IsReadOnly() )

{

ConfigurationManager.ConnectionStrings.Add( settings );

return;

}

EnsureFieldInfo();

new ReflectionPermission( ReflectionPermissionFlag.MemberAccess | ReflectionPermissionFlag.RestrictedMemberAccess ).Assert();

try

{

SetCollectionIsReadOnly( ConfigurationManager.ConnectionStrings, false );

ConfigurationManager.ConnectionStrings.Add( settings );

SetCollectionIsReadOnly( ConfigurationManager.ConnectionStrings, true );

}

finally

{

CodeAccessPermission.RevertAssert();

}

}

I'll leave you to your own devices with regard to how you resolve your connection strings. The implementation of Initialize will look something similar to the following:

public override void Initialize( string name, NameValueCollection config )

{

var key = config["connectionStringName"];

// TODO: your method to get a ConnectionStringSettings object

var settings = new ConnectionStringSettings( key, string.Empty, string.Empty );

this.SetConnectionString( settings );

if ( string.IsNullOrEmpty( name ) )

name = this.GetType().Name;

base.Initialize( name, config );

}

While this is solution is analogous to lighting a hula-hoop on fire while diving through it, it does demonstrate that if we carefully use a scalpel, we can perform cosmetic surgery to make the configuration system look the way we want. The ultimate goal of this implementation was to resolve the connection string for the profile service dynamically, but it could be used any number of additional ways. I'll let you use your imagination. Enjoy!

CustomSqlProfileProvider.cs