icelava.net

why be normal?
Welcome to icelava.net Sign in | Help
in Search

Multiple app.config files for deploying to different environments

Last post 06-24-2011, 14:30 by Anonymous. 6 replies.
Sort Posts: Previous Next
  •  05-09-2008, 6:14 2920

    Multiple app.config files for deploying to different environments

    One of the many great strides the ASP.NET team has taken to improve web developers' experience with Visual Studio came in the manifestation of the Web Deployment Project. The most important feature, at least in my eyes, is configuration section replacement. Keeping settings meant for different environments having it all consolidated into the correct web.config just got a whole lot more efficient and less error prone. This should have been in VS .NET 2002 right at the beginning of time, but I was just overjoyed nonetheless to finally have an "official" tool for that.

    The problem is, that only applies to web sites. We develop more than just web sites here; Windows Forms client applications, background Windows services, or just plain old command-line console programs. How are we going to continue coping with these types of projects? There are actually a variety of different (yet similar) ways development teams are practising out there. I am not too sure why the method I discovered has not been done and discussed by others before, so I thought I'd log it down first for others' benefit of reviewing and comparison with other methods.

    Know your destination environments

    First thing is to identify the target environments your project outputs need to be deployed at. In this scenario we are just going to take the regular Debug and Release configurations as those used by developers to build and test on their local workstations. In addition, we will just determine that there are two external environments needing deployment - SIT and UAT.

    This calls for separate app.config files carrying appropriate settings for the respective SIT and UAT servers. We are not looking at section replacement or external source reference here. This method simply duplicates the entire app.config file and changes the settings that need to be different. As such we shall name these files SIT.config and UAT.config. Fastest way is simply to copy and paste app.config directly back into the project, and rename the files. They can then be editted for their destination environments accordingly. Maintaining the uniformity of their structure should not be a problem.

    Next, the solution and output projects need to create new configuration choices with the exact same names as those given to the .config files.

    As with the screen shot, I have created "SIT" and "UAT" configurations for a number of Windows service projects, as well as for the solution itself. Remember that the solution configuration is independent from projects, and one can mix and match as required. That is why in the screenshot you can see a further "SIT_deploy" configuration for the solution; the projects themselves do not have that, and continue to use their own "SIT" configuration.

    In actuality, it is optional for the solution to have these configurations defined, but absolutely required for the projects. This will become apparent as we explore the backend mechanism that is helping us build our projects in the first place - MSBuild.

    MSBuild

    If you have not realised, .NET Framework 2.0 introduced MSBuild, a "brand new" build engine that can compile Visual Studio 2005 projects without Visual Studio itself. It resembles NAnt really closely, a favoured open-source build engine with the flexibility for highly customised build processes and sequences. The cool thing is 2005 project files are actually MSBuild files themselves, and learning to understand and edit their XML structure instead of relying on the visual Visual Studio properties interface will reap great power. I will not write anything further about MSBuild per se - that is too huge a topic for this post. You should refer to the MSDN site for a conceptual introduction to MSBuild and its features. What I want to achieve here is the steps to accomplish deploying the appropriate app.config file.

    Right click on one of the output projects, and select the Unload Project option. This will bring the project "offline", and it can be right-clicked again to expose the Edit function. This allows opening the project file in XML mode.

    Scroll all the way down. There should be two commented out MSBuild targets there. One of them is called AfterBuild. Uncomment the AfterBuild node and edit it to look like this

    <Target Name="AfterBuild" Condition="'$(Configuration)' == 'SIT' Or '$(Configuration)' == 'UAT'">
    <
    Delete Files="$(TargetDir)$(TargetFileName).config" />
    <
    Copy SourceFiles="$(ProjectDir)$(Configuration).config" DestinationFiles="$(TargetDir)$(TargetFileName).config" />
    </
    Target>

    What this does is to executed the AfterBuild task only if the configuration is "SIT" or "UAT". What did I state previously about naming the files and configurations exactly? So after a build is complete, the regular app.config file would have been placed in the output directory, renamed as <projectname>.exe.config. We should delete that file, since it is inappropriate for the SIT or UAT server. The moniker of $(TargetDir)$(TargetFileName).config will readily locate this file in the Delete task, and remove it. After that, we have to take the appropriate .config file based on the current configuration and convert that to replace the recently-deleted $(TargetDir)$(TargetFileName).config file. That is where $(ProjectDir)$(Configuration).config comes in to locate the file sitting in our project directory, and copy it to the current output directory.

    Once done, save and Reload the project. Attempt a build using the SIT or UAT configuration. The output should be similar to this sample pattern

    ------ Build started: Project: afterbuild, Configuration: UAT Any CPU ------
    Build started 9/5/2008 18:07:53.
    Target CoreResGen:
    No resources are out of date with respect to their source files. Skipping resource generation.

    Target _CopyAppConfigFile:
    Skipping target "_CopyAppConfigFile" because all output files are up-to-date with respect to the input files.

    Target CopyFilesToOutputDirectory:
    afterbuild -> C:\Projects\experiments\afterbuild\bin\UAT\afterbuild.exe

    Target AfterBuild:
    Deleting file "C:\Projects\experiments\afterbuild\bin\UAT\afterbuild.exe.config".
    Copying file from "C:\Projects\experiments\afterbuild\UAT.config" to "C:\Projects\experiments\afterbuild\bin\UAT\afterbuild.exe.config".

    Inspect the new <projectname>.exe.config and see if it contains the settings defined in UAT.config or SIT.config. Of course the MSBuild target could be expanded further to carry out more elaborate activities, but the straightforward method should suffice for a great number of build needs.

    Filed under: ,
  •  06-19-2008, 11:03 3096 in reply to 2920

    Re: Multiple app.config files for deploying to different environments

    Hi,

    Thanks for this great article. However, I have a problem when I want to publish my application using buit-in publish capability of the Visual Studio 2005.

    When I publish the site the config file is still app.config with delpy extension. Even when I run the application by pressing F5 it uses the app.config not any other configurations even when I choose the other mode of configuration.

    Any help would be appreciated.

  •  06-20-2008, 12:22 3102 in reply to 3096

    Re: Multiple app.config files for deploying to different environments

    Interesting. We use CruiseControl.NET to carry out the deployment (copying) to the actual servers, and do not use Visual Studio's publish function to achieve that. From what I see the Publish action launches a different set of MSBuild tasks to accomplish the move.

    Target _CopyFilesToPublishFolder:
    Task "FormatVersion"
    Done executing task "FormatVersion".
    Task "CreateProperty"
    Done executing task "CreateProperty".
    Task "CreateProperty"
    Done executing task "CreateProperty".
    Task "Copy"
    Creating directory "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1".

    Copying file from "bin\SIT\WindowsApplication1.exe.manifest" to "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1\WindowsApplication1.exe.manifest".

    Command:
    copy /y "bin\SIT\WindowsApplication1.exe.manifest" "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1\WindowsApplication1.exe.manifest"

    Copying file from "obj\SIT\WindowsApplication1.exe" to "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1\WindowsApplication1.exe.deploy".

    Command:
    copy /y "obj\SIT\WindowsApplication1.exe" "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1\WindowsApplication1.exe.deploy"

    Copying file from "App.config" to "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1\WindowsApplication1.exe.config.deploy".

    Command:
    copy /y "App.config" "bin\SIT\WindowsApplication1.publish\WindowsApplication1_1_0_0_1\WindowsApplication1.exe.config.deploy"

    Done executing task "Copy".

    That means it does not take the contents from the Output directory and in fact gathers from the obj and original source directories for the deployment content. Since the Publish action starts off a different chain of tasks, in particular the _CopyFilesToPublishFolder target in this case, I took the liberty of duplicating that target from C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets into the project's own csproj file. This lets us override the _CopyFilesToPublishFolder target, like what was done for the AfterBuild target.

    <Target Name="_CopyFilesToPublishFolder">

    <!-- Compute name of application folder, which includes the assembly name plus formatted application version.
    The application version is formatted to use "_" in place of "." chars (i.e. "1_0_0_0" instead of "1.0.0.0").
    This is done because some servers misinterpret "." as a file extension.
    -->

    <FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)" FormatType="Path">
    <
    Output TaskParameter="OutputVersion" PropertyName="_DeploymentApplicationVersionFragment"/>
    </
    FormatVersion>

    <CreateProperty Value="$(AssemblyName)_$(_DeploymentApplicationVersionFragment)">
    <
    Output TaskParameter="Value" PropertyName="_DeploymentApplicationFolderName" />
    </
    CreateProperty>

    <CreateProperty Value="$(PublishDir)$(_DeploymentApplicationFolderName)\">
    <
    Output TaskParameter="Value" PropertyName="_DeploymentApplicationDir" />
    </
    CreateProperty>

    <!-- Copy files to publish folder -->
    <
    Copy
    SourceFiles="@(_ApplicationManifestFinal);
    @(_DeploymentManifestEntryPoint);
    @(_DeploymentManifestFiles);
    @(ReferenceComWrappersToCopyLocal);
    @(ResolvedIsolatedComModules);
    @(_DeploymentLooseManifestFile)
    "

    DestinationFiles=
    "@(_ApplicationManifestFinal->'$(_DeploymentApplicationDir)%(TargetPath)');
    @(_DeploymentManifestEntryPoint->'$(_DeploymentApplicationDir)%(TargetPath)$(_DeploymentFileMappingExtension)');
    @(_DeploymentManifestFiles->'$(_DeploymentApplicationDir)%(TargetPath)$(_DeploymentFileMappingExtension)');
    @(ReferenceComWrappersToCopyLocal->'$(_DeploymentApplicationDir)%(FileName)%(Extension)$(_DeploymentFileMappingExtension)');
    @(ResolvedIsolatedComModules->'$(_DeploymentApplicationDir)%(FileName)%(Extension)$(_DeploymentFileMappingExtension)');
    @(_DeploymentLooseManifestFile->'$(_DeploymentApplicationDir)%(FileName)%(Extension)$(_DeploymentFileMappingExtension)')
    "

    SkipUnchangedFiles="true"/>

    <Copy
    SourceFiles="@(_DeploymentManifestDependencies)"
    DestinationFiles="@(_DeploymentManifestDependencies->'$(_DeploymentApplicationDir)%(TargetPath)$(_DeploymentFileMappingExtension)')"
    SkipUnchangedFiles="true"
    Condition="'%(_DeploymentManifestDependencies.DependencyType)'=='Install'"/>

    <Copy
    SourceFiles="@(_ReferenceScatterPaths)"
    DestinationFiles="@(_ReferenceScatterPaths->'$(_DeploymentApplicationDir)%(Filename)%(Extension)$(_DeploymentFileMappingExtension)')"
    SkipUnchangedFiles="true"/>

    <FormatUrl InputUrl="$(_DeploymentApplicationUrl)">
    <
    Output TaskParameter="OutputUrl" PropertyName="_DeploymentFormattedApplicationUrl"/>
    </
    FormatUrl>

    <FormatUrl InputUrl="$(_DeploymentComponentsUrl)">
    <
    Output TaskParameter="OutputUrl" PropertyName="_DeploymentFormattedComponentsUrl"/>
    </
    FormatUrl>

    <Delete Files="$(_DeploymentApplicationDir)$(TargetFileName).config.deploy" />
    <
    Copy SourceFiles="$(ProjectDir)$(Configuration).config" DestinationFiles="$(_DeploymentApplicationDir)$(TargetFileName).config.deploy"
    />

    </Target>

    I have left the sequence verbatim (best not mess up the nicely-written set Microsoft has already provided for us unless we have super knowledge for doing otherwise) except for the extra tasks at the end of the target. From my testing this appears to swap in the correct .config file.

  •  04-20-2009, 20:14 5629 in reply to 3102

    Re: Multiple app.config files for deploying to different environments

    Thank you very much! I'm using continuous integration with Hudson and Git and this solution is just perfect to have a automated deployment to the continous integration environment without editing any script or copying file manually.

     Great! 

  •  10-11-2009, 21:31 6100 in reply to 2920

    Re: Multiple app.config files for deploying to different environments

    Dear Ice,

    This post is brilliant, and has helped me to solve the "different config files for different environments" problem [I still can't figure out why it's so hard in the first place - I wish you could just say ot the System.Configuration.ConfigurationManager "use file arbitrary.config" and then have done with it].

    However, despite your brilliance, there's one massive irritation I'm encountering, probably just something silly I've overlooked:

     1. I followed your instructions, and now on build I do in fact get the correct config files for my chosen build/environment, correctly named in teh output directory.

    2. BUT, as soon as I press 'F11' to 'Debug/Step Into', the correct config is overwritten by the contents of app.config.

    Therefore all works perfectly UNLESS I try to step through my final release version just to check that all is good.  And call me paranoid, but that's something I like to do!

     

    Anyway I can stop this?

  •  10-13-2009, 23:16 6116 in reply to 6100

    Re: Multiple app.config files for deploying to different environments

    Anonymous:

    2. BUT, as soon as I press 'F11' to 'Debug/Step Into', the correct config is overwritten by the contents of app.config.

    Interesting observation. I did not encounter this because when stepping through code and debugging on my workstation, that is certainly not the UAT or production environment. My development workstation would most certainly rely on the original app.config settings. The SIT/UAT/Production configurations are meant for CruiseControl.NET to trigger, build, and deliver to the relevant servers, so it is a bit hard to test those settings on my local machine.

    :-)

  •  06-24-2011, 14:30 7606 in reply to 3102

    Re: Multiple app.config files for deploying to different environments

    I should be able to figure this out but wondered if anyone has needed to capture the $(Configuration) inside _CopyFilesToPublishFolder so that you could perform this action if only publishing in a certain configuration?

View as RSS news feed in XML
Powered by Community Server, by Telligent Systems