views:

17149

answers:

12

I have a .NET application which has different configuration files for Debug and Release builds. E.g. the debug app.config file points to a development SQL Server which has debugging enabled and the release target points to the live SQL Server. There are also other settings, some of which are different in debug/release.

I currently use two separate configuration files (debug.app.config and release.app.config). I have a build event on the project which says if this is a release build then copy release.app.config to app.config, else copy debug.app.config to app.config.

The problem is that the application seems to get its settings from the settings.settings file, so I have to open settings.settings in Visual Studio which then prompts me that the settings have changed so I accept the changes, save settings.settings and have to rebuild to make it use the correct settings.

Is there a better/recommended/preferred method for achieving a similar effect? Or equally, have I approached this completely wrong and is there a better approach?

+6  A: 

My current employer solved this issue by first putting the dev level (debug, stage, live, etc) in the machine.config file. Then they wrote code to pick that up and use the right config file. That solved the issue with the wrong connection string after the app gets deployed.

They just recently wrote a central webservice that sends back the correct connection string from the value in the machine.config value.

Is this the best solution? Probably not, but it works for them.

hectorsosajr
Actually I think that's pretty damned elegant, since I like to keep the various versions of config visible within a solution, even if they aren't live.
annakata
This is a very intriguing solution. Would love to look over an example of this in action.
Mike at KBS
+9  A: 

To me it seems that you can benefit from the Visual Studio 2005 Web Deployment Projects.

With that, you can tell it to update/modify sections of your web.config file depending on the build configuration.

Take a look at this blog entry from Scott Gu for a quick overview/sample.

Magnus Johansson
+7  A: 

There is a related question here:

http://stackoverflow.com/questions/14893/improving-your-build-process

Config files come with a way to override the settings:

<appSettings file="Local.config">

Instead of checking in two files (or more), you only check in the default config file, and then on each target machine, you put a Local.config, with just the appSettings section that has the overrides for that particular machine.

If you are using config sections, the equivalent is:

configSource="Local.config"

Of course, it's a good idea to make backup copies of all the Local.config files from other machines and check them in somewhere, but not as a part of the actual solutions. Each developer puts an "ignore" on the Local.config file so it doesn't get checked in, which would overwrite everyone else's file.

(You don't actually have to call it "Local.config", that's just what I use)

Eric Z Beard
+10  A: 

From what I am reading, it sounds like you are using Visual Studio for your build process. Have you thought about using MSBuild and Nant instead?

Nant's xml syntax is a little weird but once you understand it, doing what you mentioned becomes pretty trivial.

<target name="build">
    <property name="config.type" value="Release" />

    <msbuild project="${filename}" target="Build" verbose="true" failonerror="true">
        <property name="Configuration" value="${config.type}" />
    </msbuild>

    <if test="${config.type == 'Debug'}">
        <copy file=${debug.app.config}" tofile="${app.config}" />
    </if>

    <if test="${config.type == 'Release'}">
        <copy file=${release.app.config}" tofile="${app.config}" />
    </if>

</target>
Steven Williams
+3  A: 

One of the solutions that worked me fine was using a WebDeploymentProject. I had 2/3 different web.config files in my site, and on publish, depending on the selected configuration mode (release/staging/etc...) I would copy over the Web.Release.config and rename it to web.config in the AfterBuild event, and delete the ones I don't need (Web.Staging.config for example).

<Target Name="AfterBuild">
    <!--Web.config -->
    <Copy Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " SourceFiles="$(SourceWebPhysicalPath)\Web.Release.config" DestinationFiles="$(OutputPath)\Web.config" />
    <Copy Condition=" '$(Configuration)|$(Platform)' == 'Staging|AnyCPU' " SourceFiles="$(SourceWebPhysicalPath)\Web.Staging.config" DestinationFiles="$(OutputPath)\Web.config" />
    <!--Delete extra files -->
    <Delete Files="$(OutputPath)\Web.Release.config" />
    <Delete Files="$(OutputPath)\Web.Staging.config" />
    <Delete Files="@(ProjFiles)" />
  </Target>
snomag
+7  A: 

We used to use Web Deployment projects but have since migrated to NAnt. Instead of branching and copying different setting files we currently embed the configuration values directly in the build script and inject them into our config files via xmlpoke tasks:

  <xmlpoke
    file="${stagingTarget}/web.config"
    xpath="/configuration/system.web/compilation/@debug"
    value="true"
  />

In either case, your config files can have whatever developer values you want and they'll work fine from within your dev environment without breaking your production systems. We've found that developers are less likely to arbitrarily change the build script variables when testing things out, so accidental misconfigurations have been rarer than with other techniques we've tried, though it's still necessary to add each var early in the process so that the dev value doesn't get pushed to prod by default.

jasondoucette
+3  A: 

You'll find another solution here: http://stackoverflow.com/questions/132885/best-way-to-switch-configuration-between-developmentuatprod-environments-in-asp#132934 which uses XSLT to transfor the web.config.

There are also some good examples on using NAnt.

Slace
+23  A: 

Any configuration that might differ across environments should stored at the machine level, not the application level. (More info on configuration levels.)

These are the kinds of configuration elements that I typically store at the machine level:

When each environment (developer, integration, test, stage, live) has its own unique settings in the c:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG directory, then you can promote your application code between environments without any post-build modifications.

And obviously, the contents of the machine-level CONFIG directory get version-controlled in a different repository or a different folder structure from your app. You can make your .config files more source-control friendly through intelligent use of configSource.

I've been doing this for 7 years, on over 200 ASP.NET applications at 25+ different companies. (Not trying to brag, just want to let you know that I've never seen a situation where this approach doesn't work.)

Portman
What about a situation where you don't control the web server and therefore can't change machine-level config? Examples include a 3rd-party web server or a web server shared amongst multiple departments in an enterprise.
jkohlhepp
Wouldn't work. But in the era of virtual machines, Amazon EC2, and $400 servers from Dell, does anybody really do anything serious on shared machines? Not trying to be callous at all -- I really think that if you're working on a shared web server you should reevaluate.
Portman
Most corporates I've worked at with internal sites host multiple applications on one server - there a reevaluation would have to be done at a corporate level
MPritch
Multiple applications on one server are fine so long as the apps are all in the same "environment". I.e., you wouldn't want the LIVE instance of App1 on the same server as the DEV instance of App2. For example, your SMTP settings would be shared across all of your applications. In production, you point to a real mail server; in development, you point to a file on disk.
Portman
+2  A: 

Like you I've also setup 'multi' app.config - eg app.configDEV, app.configTEST, app.config.LOCAL. I see some of the excellent alternative suggested, but if you like the way it works for you, I'd add the following:

I've a
<appSettings>
<add key = "Env" value = "[Local] "/> for each app I add this to the UI in the titlebar: from ConfigurationManager.AppSettings.Get("Env");

I just rename the config to the one I'm targetting (I've a project with 8 apps with lots of database/wcf config against 4 evenioments). To deploy with clickonce into each I change 4 seetings in the project and go. (this I'd love to automate)

My only gotcha is to rememeber to 'clean all' after a change, as the old config is 'stuck' after a manual rename. (Which i think WILL fix you setting.setting issue).

I find this works really well (one day I'll get time to look at MSBuild/NAnt)

tdrake
+2  A: 

Our proj has the same issue where we had to maintain configs for dev, qa, uat and prod. Here is what we followed (only applies if you are familiar with MSBuild):

Use MSBuild with the MSBuild Community tasks extension. It includes the 'XmlMassUpdate' task that can 'mass-update' entries in any XML file once you give it the correct node to start with.

To Implement:

1) You need to have one config file which will have your dev env entries; this is the config file in your solution.

2) You need to have a 'Substitutions.xml' file, that contains only the entries that are DIFFERENT (appSettings and ConnectionStrings mostly) for each environment. Entries that do not change across environment need not be put in this file. They can live in the web.config file of the solution and will not be touched by the task

3) In your build file, just call the XML mass update task and provide the right environment as a parameter.

See example below:

    <!-- Actual Config File -->
    <appSettings>
        <add key="ApplicationName" value="NameInDev"/>
        <add key="ThisDoesNotChange" value="Do not put in substitution file" />
    </appSettings>

    <!-- Substitutions.xml -->
    <configuration xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
      <substitutions>
        <QA>
           <appSettings>
            <add xmu:key="key" key="ApplicationName" value="NameInQA"/>
           </appSettings>            
        </QA>
        <Prod>
          <appSettings>
            <add xmu:key="key" key="ApplicationName" value="NameInProd"/>
          </appSettings>            
        </Prod>
     </substitutions>
    </configuration>


<!-- Build.xml file-->

    <Target Name="UpdateConfigSections">
            <XmlMassUpdate ContentFile="Path\of\copy\of\latest web.config" SubstitutionsFile="path\of\substitutionFile" ContentRoot="/configuration" SubstitutionsRoot="/configuration/substitutions/$(Environment)" />
        </Target>

replace '$Environment' with 'QA' or 'Prod' based on what env. you are building for. Note that you should work on a copy of a config file and not the actual config file itself to avoid any possible non-recoverable mistakes.

Just run the build file and then move the updated config file to your deployment environment and you are done!

For a better overview, read this:

http://blogs.microsoft.co.il/blogs/dorony/archive/2008/01/18/easy-configuration-deployment-with-msbuild-and-the-xmlmassupdate-task.aspx

desigeek
+20  A: 

This might help to some people dealing with Settings.settings and App.config: Watch out for GenerateDefaultValueInCode attribute in the Properties pane while editing any of the values in the Settings.settings grid in Visual Studio (Visual Studio 2008 in my case).

If you set GenerateDefaultValueInCode to True (True is the default here!), the default value is compiled into the EXE (or DLL), you can find it embeded in the file when you open it in a plain text editor.

I was working on a console application and if I had defaults in the EXE, the application always ignored the configuration file place in the same directory! Quite a nightmare and no information about this on the whole Internet.

Roman
This is precisely what happened to me over this past weekend. I pulled out a lot of hair trying to figure out why my app seemed to be ignoring my app.config file! It is supposed to connect to a web service and the service url is in my app.config. Unbeknownst to me, when I created the web reference, it also created a Settings.Settings file AND hardcoded the default value into the code. Even when I finally figured out (and removed) the settings file, that default value stayed in the hardcode and got embedded in the exe. VERY FRUSTRATING!! Thanks to this post, now I can get rid of that "feature"
Mike at KBS
+1 for posting this -- the same thing happened to me. Extremely frustrating.
Pandincus
another +1 for a great find!
Jeff.Crossett
+1  A: 

The answer on this page by Roman dated Aug 26 '09 at 8:44 is the CRITICAL one: If you want your setting to go into the app.config file, set its GenerateDefaultValueInCode attribute to False (the default is True).

Toddintr