Imagine a situation: In Visual Studio, there is a solution that consists of 3 projects. In each of the projects in the app.config file, there is an open connection string of the database deployed in the production environment.

There was a need to work with this project in a team using VSTS.

I want to implement this scenario: The developer synchronizes his repository with this solution, and there are no connection lines in the app.config files, he should add connection lines to his local test database. After sending the code back to the repository, in the CI and CD process, the necessary connection lines to the working database are inserted into the app.config files.

How can this be realized?

4 answers 4

In each of the projects in the app.config file, there is an open connection string of the database deployed in the production environment.

It sounds dangerous. It turns out that by giving the developer access to the reading of the repository, you immediately trust him with access to the combat database. I think that access to the combat infrastructure should be as limited as possible. This means that it should be removed from the repository.

I propose to transfer key connection parameters (for example, login and password, or token, or whatever else may be) from the config to environment variables.

The use of this approach is described in great detail in the 12-factor application manifest .

  • Many CI servers can store such variables and initialize their environment before starting your application. GitLab CI , Jenkins and Travis are able to accurately (links lead to the documentation on features).

  • Access to view and edit secret variables will be given to users with the highest level of access.

  • If the variables in the build log are not displayed, they will not be available to the passerby. Just do not duplicate the command in the log to connect to the database.
  • You will also need to configure that secret variables are used only when the code from your release branch is complete.

The programmer will initialize the environment himself, you can write a script to help him.

  • Perhaps there is some specificity associated with the development under Windows, I do not know it well. I would be grateful for any comments and additions. - Nick Volynkin
  • "by giving the developer read access to the repository, you immediately trust him with access to the combat database" - knowing which server and with what name the base is spinning does not equal access to this very database. - andreycha

A bonus to all this. It is possible to transform app.config (as well as any other file) when building. Such functionality is by default in WCF service projects for web.config , but it can also be added to other types of projects.

The scheme is as follows: there is some default empty app.config , next to it are additional files called app.Debug.config , app.Release.config , app.Test.config and so on, which contain instructions for transforming the original app.config . Assuming that only certain build configurations are used in CI and CD (for example, only Release), then you can keep the necessary set of settings for working with the app.Release.config in app.Release.config . Developers can freely edit app.config locally and work with the Debug configuration (if developers need to work with Release, you can add a new CIRelease configuration that will only be used on the buildserver).

To use this approach is not in the WCF project, you need to make several frauds.

Create a ConfigurationTransform.targets file with the following content:

 <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" /> <PropertyGroup> <AllowedReferenceRelatedFileExtensions> $(AllowedReferenceRelatedFileExtensions); .dll.config </AllowedReferenceRelatedFileExtensions> </PropertyGroup> <PropertyGroup> <ResolveReferencesDependsOn> TransformConfig; $(ResolveReferencesDependsOn) </ResolveReferencesDependsOn> </PropertyGroup> <Target Name="TransformConfig" BeforeTargets="_CopyAppConfigFile" Condition="Exists('App.$(Configuration).config')"> <!--Создаём трансофрмированный app config в промежуточной директории.--> <TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" /> <!--Сообщаем, что для сборки нужно использовать только что сгенерированный файл.--> <ItemGroup> <AppConfigWithTargetPath Remove="App.config" /> <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config"> <TargetPath>$(TargetFileName).config</TargetPath> </AppConfigWithTargetPath> </ItemGroup> </Target> </Project> 

We put the file in the solution folder ( .sln level) and connect it immediately after Microsoft.CSharp.targets .

 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(SolutionDir)\ConfigurationTransform.targets" /> 

In principle, you can connect this task directly to .csproj without creating an additional file, but with an add. The file automatically solves the problem with the presence of several projects for which you need to transform the app.config .

Example. Original app.config :

 <?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <connectionStrings> <add name="Test" connectionString="" /> </connectionStrings> </configuration> 

Transformation file app.Release.config :

 <?xml version="1.0"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <connectionStrings> <add name="Test" connectionString="ProductionConnectionString" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/> </connectionStrings> </configuration> 

The transformation file uses the SetAttributes operation, which sets the attributes for the node, which is identified by the name attribute ( xdt:Locator="Match(name)" ). Other examples of transformations are here .

Voila, when building in the ConfigName configuration, if there is an app.ConfigName.config file next to app.config , it will be used for transformation and the build will be performed using the intermediate transformed file.

An example of a task from here .


Another bonus is a small life hack. To prevent the transformation files in Solution Explorer from taking up much space, they can be shown as children of the main app.config : Dependent transformation files in SolutionExplorer

To do this, in .csproj you need to correct the registration of the transformation files by adding the DependentUpon tag:

 <ItemGroup> <None Include="App.config" /> <None Include="App.Release.config"> <DependentUpon>App.config</DependentUpon> </None> </ItemGroup> 
  • Vooot, the very specifics about which I suspected. )) - Nick Volynkin
  • Instead of the App.config file’s hardcode, it would be worth using @ (AppConfigWithTargetPath) - this will ensure compatibility with third-party solutions that also change App.config. - Pavel Mayorov
  • And also: the instruction BeforeTargets="_CopyAppConfigFile" redundant if you have already specified TransformConfig in the ResolveReferencesDependsOn property. Although I would write in a simple way: BeforeTargets="ResolveReferences" instead of using the outdated DependsOn-properties mechanism. - Pavel Mayorov
  • By the way, a small clarification. If you have more than two release environments, don’t even think of creating a separate configuration for each with its own transformations! The build will be very slow. If other solutions for the transformation of configs after the assembly of the solution. - Pavel Mayorov

As already written by @NickVolynkin, holding the database connection strings in App.config is a bad practice. You can create a local configuration file, for example, ConnectStrings.config , in which to store connection strings, and which will not be in the repository (it is different for each developer). Also write a handler class for this configuration, and when creating a database connection, call methods of this class.

    Lines from app.config can be configSource to a separate file via the configSource attribute.

    Something like this is written in the web.config files:

     <connectionStrings configSource="bin\connectionstrings.config" /> 

    In the app.config files we write this:

     <connectionStrings configSource="connectionstrings.config" /> 

    In all projects, we include the connectionstrings.config file common to all as a link and set up its copying when building. In the project file you should get something like this:

     <Content Include="..\connectionstrings.config"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> 

    Then this file can be added to .gitignore , thereby allowing each developer to have his own version of this file. (Just don’t forget to put a file like connectionstrings.sample.config next to it so that developers don’t have to fill it from scratch with trial and error every time).

    When building this file will need to be generated on the side of the assembly server. It is also possible to apply the transformation to the configuration for the release assembly, replacing the connectionStrings section entirely.

    • You can, by the way, combine my and your decision. To store variables is hidden on the server, in the same place to generate a config from them for deployment to the battle. - Nick Volynkin
    • @NickVolynkin, Yeah, this is how it should be done in an amicable way - Pavel Mayorov
    • By the way, why am I campaigning for configs in environment variables, and not in files: 12factor.net/config - Nick Volynkin