The ConfigurationManager facade class is a solid member of the base class library. It allows one to access the application settings, connection strings and other configuration sections from configuration files.
The problem with the facade class is that its members are static and there doesn’t seem to be an easy way to mock the internal implementation. Thus, testing becomes complicated as the internal implementation is bound to the *.config files.
A natural way to resolve this difficulty is to provide another facade but this time a mockable one. You start with an interface :
public interface IConfigurationManager
{
NameValueCollection AppSettings
{
get;
}
ConnectionStringSettingsCollection ConnectionStrings
{
get;
}
object GetSection( string sectionName );
}
and then you let it be injected into the facade
/// <summary>
/// Gateway/facade dla klienta
/// </summary>
public class ConfigurationManagerGateway
{
private static IConfigurationManager _provider =
new DefaultConfigurationManager();
public static void SetConfigurationProvider(
Func<IConfigurationManager> configurationProvider )
{
_provider = configurationProvider();
}
public static NameValueCollection AppSettings
{
get
{
return _provider.AppSettings;
}
}
public static System.Configuration.ConnectionStringSettingsCollection ConnectionStrings
{
get
{
return _provider.ConnectionStrings;
}
}
public static object GetSection( string sectionName )
{
return _provider.GetSection( sectionName );
}
}
where the default implementation points back to the old facade
public class DefaultConfigurationManager :
IConfigurationManager
{
#region IConfigurationManager Members
public System.Collections.Specialized.NameValueCollection AppSettings
{
get
{
return ConfigurationManager.AppSettings;
}
}
public System.Configuration.ConnectionStringSettingsCollection ConnectionStrings
{
get
{
return ConfigurationManager.ConnectionStrings;
}
}
public object GetSection( string sectionName )
{
return ConfigurationManager.GetSection( sectionName );
}
#endregion
}
This approach lets you provide any implementation and inject it into the new facade. And this is the major drawback of this approach – it forces the client code to switch to the new facade or at least to the new interface. For a large code base this means that a lot of code which uses the old facade must be inspected and manually switched to the new facade.
But, hey, this is not a big issue – you could think – I do it once and at least from the very moment I have a correct architecture of the system.
Alas, there is just another minor drawback. The new interface is a custom interface which means that it has to be provided in a custom class library. Suppose then you have two large code bases, X and Y, developed independently. The X code base solves the configuration manager problem by providing the new interface and the new facade. The Y code base solves the same problem by providing its own, yet another interface with yet another facade.
And then, you try to merge X and Y in the new composite solution, Z. For this, you need two different implementations of injected configuration providers (or better, a implementation and one adapter so that the actual implementation is provided only once).
All these major and minor issues made me rethink the issue once again. I started to find a way to inject a custom implementation of the configuration provider into the existing facade, the ConfigurationManager class. And, surprisingly, there is a way.
You see, if you decompile the ConfigurationManager facade, it turns out that internally all it does it is uses an internal provider of type System.Configuration.Internal.IInternalConfigSystem (in fact, in contrary to its name, the interface is not internal!). And the facade has an explicit method to inject this provider, ConfigurationManager.SetConfigurationSystem! The only subtle issue is that the injection method is, for an unknown reason, internal.
All this means that the architecture of the existing facade is prepared for an injectable provider but for some unknown reason someone has blocked the explicit possibility to inject a provider.
And of course, this is not a real issue if you have the reflection.
What you need then is to have a way to inject your own implementation somewhere early in the app lifecycle (the composition root perhaps). With a help of a concrete implementation, all you have to do is to provide actual settings:
public static class ConfigurationManagerConfigurator
{
public static void SetConfigurationSystem( IInternalConfigSystem configSystem )
{
Type configurationManagerType = typeof( ConfigurationManager );
// this does the magic. calls the internal method
// which allows me to inject a provider
// why, oh why this is not public?
configurationManagerType.InvokeMember(
"SetConfigurationSystem",
System.Reflection.BindingFlags.InvokeMethod |
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic,
null,
configurationManagerType, new object[] { configSystem, true } );
}
}
public class DynamicConfigSystem :
System.Configuration.Internal.IInternalConfigSystem
{
private NameValueCollection appSettings;
private ConnectionStringSettingsCollection connectionStrings;
private Dictionary<string, object> sections;
#region IInternalConfigSystem Members
public DynamicConfigSystem(
NameValueCollection AppSettings,
ConnectionStringSettingsCollection ConnectionStrings,
Dictionary<string, object> Sections )
{
this.appSettings =
AppSettings != null ?
AppSettings : new NameValueCollection();
this.connectionStrings =
ConnectionStrings != null ?
ConnectionStrings : new ConnectionStringSettingsCollection();
this.sections =
Sections != null ? Sections : new Dictionary<string, object>();
}
public object GetSection( string sectionName )
{
if ( sectionName == "appSettings" )
return this.appSettings;
if ( sectionName == "connectionStrings" )
return this.connectionStrings;
if ( this.sections.ContainsKey( sectionName ) )
return this.sections[sectionName];
else
throw new ArgumentException( "Undefined configuration section" );
}
public void RefreshConfig( string sectionName )
{
}
public bool SupportsUserConfig
{
get
{
return true;
}
}
#endregion
}
Now I can have my custom configuration injected and there is no need to rewrite the existing code:
// create the configuration in memory
NameValueCollection appSettings = new NameValueCollection();
appSettings.Add( "foo", "bar" );
// inject the custom provider
ConfigurationManagerConfigurator.SetConfigurationSystem(
new DynamicConfigSystem( appSettings, null, null ) );
// prints "bar" - configuration is server by the built-in facade
Console.WriteLine( ConfigurationManager.AppSettings["foo"] );
4 comments:
Nice trick.
Unfortunately I could not make it work within ASP NET context.
If SetConfigurationSystem method is used anywhere in the page life cycle, an error is raised ("The configuration system has already been initialized").
Maybe the ConfigurationManager object is initialized by ASP NET worker process before any application request and this prevents the custom provider injection :(
@Umberto: add a line before the actual configuration is set: configurationManagerType.InvokeMember( "s_initState", System.Reflection.BindingFlags.SetField | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic, null, configurationManagerType, new object[] { 0 } );
This changes the internal state to "not initialized" thus allowing you to replace any existing configuration subsystem with your own.
Thanks: after adding your code I can successfully inject my custom provider in ASP NET context.
Now ConfigurationManager.AppSettings() property and ConfigurationManager.GetSection() method provide information from my custom provider.
When I try to access ConfigurationManager.ConnectionStrings() property, a ConfigurationErrorsException is raised ("The configuration section 'connectionStrings' has an unexpected declaration").
Problem solved.
In class DynamicConfigSystem replace
private ConnectionStringSettingsCollection connectionStrings;
with
private ConnectionStringsSection connectionStrings;
and
this.connectionStrings =
ConnectionStrings != null ?
ConnectionStrings : new ConnectionStringSettingsCollection();
with
this.connectionStrings =
ConnectionStrings != null ?
ConnectionStrings : new ConnectionStringsSection();
Post a Comment