Background
When working with a multiple environment architecture i.e. Development, QA, Staging and Production, one would always want to separate and isolate storage within each environment. Using Microsoft’s SlowCheetah, we can override our development configuration and specify environment specific configuration. But this leads to having confidential information like connection strings being committed to source control. This leads us to the question “How can I manage this confidential information in configuration files without having it stored in source control?”
Approach
The two main forms of configuration files in .NET are App.config for projects like console applications and Windows Services, and Web.config for Web Applications.
Configurations
Here’s an example of the connection strings section in our App.config and Web.config. This is what is going to be overridden at build time.
<connectionStrings>
<add
name="MySQLDatabase"
connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=immedia;Integrated Security=True;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
Now here is what our App.Production.config looks like. This shows the transformations that happen at build time when running the project in the Production configuration.
<connectionStrings>
<add
name="MySQLDatabase"
connectionString="Data Source=10.10.54.206;Initial Catalog=immedia;Id=Username;Password=Password"
providerName="System.Data.SqlClient"
xdt:Transform="Replace"
xdt:Locator="Match(name)"/>
</connectionStrings>
Now we can see that the IP address as well as the Username and Password for the production DB is in the configuration file which is now in source control. Anyone with access to this repository will now also have access to the SQL instance.
Using an external file for configuration
In .NET configuration files, it’s possible to specify an external file for connection strings by using the configSource attribute for the connectionStrings section of the config file.
Updating the Production config to the following:
<connectionStrings configSource="secrets.config" xdt:Transform="SetAttributes(configSource)">
<add xdt:Transform="RemoveAll" />
</connectionStrings>
We can specify an external file (secrets.config), that will not need to be added to source control. The contents of this secrets.config will contain the production specific connection strings.
Deployment
The only thing left now to accomplish is getting this secrets.config file onto the servers that our application is deployed to.
App.config
For console applications and Windows Services, getting the secrets.config file onto the server means we copy the secrets.config file (which is stored and managed on the build server) to the directory of the source code on the build server itself. We then proceed to Build, and Deploy. This will result in the secrets.config file also getting deployed with the application.
Web.config
For web applications, the process is slightly different. The reason is because when web applications are built to a deployment package using MsBuild.exe, the package that is created for deployment contains only the files that are referenced in the binaries. Since the secrets.config file is only referenced in the configuration file, this file will be removed from the package.
For us to let MsBuild know that it should also include the secrets.config file in the package we can add the following sections to the csproj file for the Web Application.
Note: Add it after Microsoft.CSharp.targets is imported.
<PropertyGroup>
<PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
IncludePluginFilesForMsdeploy;
$(PipelineCopyAllFilesToOneFolderForMsdeployDependsOn);
</PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
</PropertyGroup>
<Target Name="IncludePluginFilesForMsdeploy">
<ItemGroup>
<FileWrites Include="$(MSBuildProjectDirectory)\secrets.config" />
<_CustomFiles Include="$(MSBuildProjectDirectory)\secrets.config" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
This will include the secrets.config file in the package file.
All that’s left now is to deploy the package.
All the code referenced here can be found on BitBucket.
Comments and feedback will be highly appreciated; tweet me!