Thursday, January 5, 2012

Configuration files transformation for .net applications (both web and client)

Ever felt a need for different configuration files for different environments? This is a very common requirement where you want your configurations to adapt based on the environment it is built for. Let’s see how we have done it for different types of projects (web and client). What is available out of the box and what’s not?

We have three different environments for which we change the environment specific values in the configuration file and fire the build. That’s sounds good until we ran into issues described in next section.

What is the issue?
There were two issues that we came across:
1.    We have different projects which have environment specific configuration, whenever we need a build for new environment we have to manually change the values at tons of places which is cumbersome and error-prone. It will become impossible to do once more and more projects are added which have environment specific data. To add to the pain more environments are in place for which we need to build for every deployment cycle. 
2.    For web projects, config transformation is supported out of the box but for client applications, config transformation is not supported at all. In Step 6 of “Code and Steps” below, we have followed different approaches to address this issue.

How to resolve the issue?
While resolving the issue you need to identify the environment specific data. Some of the environment specific data that we generally have is server urls, services bindings, connection strings, etc. Try to keep them in the config files of the projects. You can change the value during build based on the target build environment.
For accomplishing this while building, you can have different build configuration for different environments. Taking into account say, you have two different environments for QA and Production. You need to do the following:
1.    You need to make environment specific build configuration and add that to the solution.
2.    Update configuration in each project which has value that changes with environment.
3.    For picking up the specific configuration in the code from config files you can :
a.    Either transform config files based on the build configuration selected.
b.    Or pick altogether a different config file every time based on the build configuration selected. (You need to do this as client applications do not support config transformation.)

Code and Steps for implementing the solution

First you need to create environment specific configuration data. For accomplishing this while building, we can have different build configuration for different environments. Say, you have two different environments for QA and Production. You need to do the following:
1.    Right click on Solution and Select “Configuration Manager” :
2.    In Configuration Manager, select Active Solution Configuration as “New” :
NOTE:  You need to modify Active Solution Platform based on your environment settings.

3.    In New Solution Configuration dialog, give the name for the configuration, say “QADeploy”. Just copy the settings from any configuration you think is the similar to the current one and then you can change the values of properties opening the project file. Similarly make configuration for production, say “ProdDeploy”.
4.    Now if you open the project file, you can see configuration section added, something like this:
NOTE : The properties may differ based on your Visual Studio settings.

5.    You can add more properties or can modify existing ones based on the environment specific needs.
6.    Now based on the build configuration you can either transform config files or you can include all together a different config file for different configuration. Let’s see one by one how you can accomplish this.

a.    First Approach : Config Transformation
This approach is useful in case of Web.config which supports config transformation out of the box. Follow the steps listed below:
i.      In Solution Explorer, right-click the Web.config file and then click Add Config Transforms.
ii.     Open the transform file for the build configuration that you want to work with :
iii.    Edit the transform file to specify the changes that should be made to the deployed Web.config file when you deploy by using that build configuration.
The following example shows how to use the Match locator and the SetAttributes transform attribute. The Match locator attribute identifies the add element in the connectionStrings section as the element to change. The SetAttributes transform attribute specifies that this element's connectionString attribute should be changed to "NewEnvironmentSQLServer".

For more information about how to write transform files, see Web.config Transformation Syntax for Web Application Project Deployment.
iv.   Save and close the transform file.

b.    Second Approach : Including different config based on Build Configuraiton
This approach is particularly useful in case of App.config which does not support config transformation. Follow the steps listed below for including different App.config for different build configuration
i.      Add a folder named Configs in your project.
ii.     Add a folder for each environment under this folder.
iii.    Copy your App.config from project and add it in each of the folder created.
iv.   Make changes to the config file based on the environment, e.g. - changing the database connection strings, etc.
v.    REMOVE the following code from the project file:
<None Include="App.config" />
<None Include="Configs\Prod\App.config" />
<None Include="Configs\QA\App.config" />

vi.   ADD the following code to project file for different configurations:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == Debug|AnyCPU' ">
    <None Include="App.config" />
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == Release|AnyCPU' ">
    <None Include="App.config" />
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'QADeploy|AnyCPU' ">
    <None Include="Configs\QA\App.config" />
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'ProdDeploy|AnyCPU' ">
    <None Include="Configs\Prod\App.config" />
NOTE : Platform may differ, it can be AnyCPU, x86, x64, etc. based on your settings. Basically in the code above, its defined from which folder to pick App.Config based on the BuildConfiguration property value.

7.    You are good to build your solution using the build configuration and your environment specific configuration will automatically be picked according to the configuration specified.