When you create a .Net Framework application in Visual Studio, an App.config file is generated for the project. In a web project (ASP.Net) the file is web.config (similar, but with some differences). For .Net Core, it can be created afterward.
Let's consider the following example which uses the configuration to resolve the connection to the database (this is a common use case for App.config).
using (var sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnStr1"].ConnectionString))
{
...
}
So where does this information come from and how does it persis in App.config?
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="ConnStr1" connectionString="LocalSqlServer: data source=127.0.0.1;Integrated Security=SSPI;Initial Catalog=aspnetdb"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
If this were a real server (possibly a production server), it is not wise to keep these credentials in source control. If a developer wants to have a local database for testing, fine, they can make their own App.config. Most development shops have a development database that everyone can use (often has good sample data for testing). Since this isn't real data, there isn't a concern if the credentials make it into source control.
The goal is to write an extension method that will pick the App.config value or the development connection string if it exists. The challenge is that ConfigurationManager.ConnectionStrings only lets you get values at runtime. Fortunately we can overcome this constraint using reflection.
/// <summary>
/// Various extensions for App.config
/// </summary>
public static partial class Extensions
{
/// <summary>
/// Adds a ConnectionStringSettings object at runtime
/// </summary>
/// <param name="collection">The collection to add to</param>
public static void AddSettings(this ConnectionStringSettingsCollection collection, ConnectionStringSettings settings)
{
if (collection == null) { return; }
if (settings == null) { return; }
try
{
typeof(ConfigurationElementCollection)
.GetField("bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(collection, false);
collection.Add(settings);
// Make it readonly again
typeof(ConfigurationElementCollection)
.GetField("bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(collection, true);
}
catch
{
// Ignored
}
}
/// <summary>
/// Adds the dev connection dynamically
/// </summary>
/// <param name="collection">The collection to add to</param>
public static ConnectionStringSettings MyConnection(this ConnectionStringSettingsCollection collection)
{
if (collection == null) { return null; }
var settings = new ConnectionStringSettings(
"ConnStr1",
"Data Source=devdb.crwirz.com;Integrated Security=SSPI;Initial Catalog=devdb;User id=test;Password=secret",
"System.Data.SqlClient");
var db = collection[settings.Name];
if (db == null)
{
collection.AddSettings(settings);
db = collection[settings.Name];
}
return db;
}
}
Now we can create our connection using the MyConnection extension.
using (var sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings.MyConnection().ConnectionString))
{
...
}
This provides a consistent (no magic strings) way of getting our connection string.