Maintaining local development or private (secret) configuration in .NET always was hard. There is no single path how to do it. Two days ago Scott Hanselman wrote an article about best practices for private config data. He describes existing way how to put configuration into an external file (which already existed since .NET 1.1 and 2.0 :) ), but there are still some open questions. In this article, I am going to show one way how to solve these configuration issues.
.NET configuration system has two options to include configuration from external file into Web.config or App.config:
- appSettings' file attribute where selected configuration file merges with appSettings section in Web.config,
- configSource attribute on any section where selected configuration file replaces the whole section.
Usually, you want to have a different configuration for connectionStrings and episerver.find sections and some appSettings values for different environments (including developer computer). As Scott Hanselman describes, you can put those configurations into a separate file and add this file to .gitignore that local configuration is not committed into the repository.
This works fine for appSettings and it's file attribute where missing configuration file from file attribute is ignored. But it doesn't work for sections with configSection attribute as it requires the configuration file. Also, sometimes it is fine to commit environment specific configuration transformation files, but if you do not have the main file, transformations cannot be applied. For example, you have connectionStrings.PROD.config, but do not commit connectionStrings.config, then transformations will fail on deployment or CI server. Developers also like to have default values setup in configuration files, so they just have to replace those with own values.
So the goals for this article is to show how to solve these issues:
- private developer configuration which never gets committed into the repository,
- default configuration values which are committed into the repository,
- support for environment specific transformation configuration files.
In this sample, I will show how to setup connectionStrings, episerver.find and appSettings sections. First of all, in the Web.config define external configuration files for all three sections and point those to developer's local configuration files as here:
<connectionStrings configSource="connectionStrings.dev.config" /> <appSettings file="appSettings.dev.config"> <add key="webpages:Version" value="184.108.40.206" /> <add key="webpages:Enabled" value="false" /> </appSettings> <episerver.find configSource="EPiServerFind.dev.config" />
Then add those developer's configurations in .gitignore:
Next create developer configuration files - connectionStrings.dev.config, appSettings.dev.config, EPiServerFind.dev.config and default configuration files - connectionStrings.config, appSettings.config, EPiServerFind.config.
When this is done, create transformation file for Web.config which will run transformations for all other environments on you CI or deployment server. This transformation file should change paths from development configuration files to default ones. We at Geta are using Octopus Deploy for deployment and by default, it always runs Web.Release.config transformation before deployment. So let's create it:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <system.web> <compilation xdt:Transform="RemoveAttributes(debug)" /> </system.web> <connectionStrings configSource="connectionStrings.config" xdt:Transform="SetAttributes(configSource)" /> <appSettings file="appSettings.config" xdt:Transform="SetAttributes(file)" /> <episerver.find configSource="EPiServerFind.config" xdt:Transform="SetAttributes(configSource)" /> </configuration>
Now if you need, you can provide configuration transformations for connectionStrings.config, appSettings.config and EPiServerFind.config files too.
This solution solves several issues and gives such benefits:
- developers can not accidentally commit local development configuration,
- developers have good defaults for their local configuration,
- environment specific configuration can remain in the repository,
- default configuration files are committed and are available for deployment tools to do required transformations (for example, for Octopus Deploy).