Using Visual Studio .NET 2003, there are different options to dynamically use app/web.config settings depending on what environment the build is deployed on. One of which is in your code, use preprocessor directives like #if, #elif, and #endif. I don't like this approach, period. This is really ugly. What if you have 5 deployment environments? You will end up with values with keys "dbConnStringDev", "dbConnStringTest", "dbConnStringQA", "dbConnStringProd", etc.
Another option is have inside your app/web.config file, just create one key-value pair for each setting regardless of how many deploying environments you might have (eg. ), and then after each deployment, manually (or automate this by some scripts) go and edit the settings for all these. Well... this is just as bad as a single mistype would deem a deployment failed.
A third option would be, if you have separate a set of physical machines per deployment environment, one could use each machine's machine.config as the definitive setting storage, and then override them with local app/web.config in for example your developer's .NET projects. Question arises if for example you have five different deployment environments (dev, test, QA, stage, and prod) but you only have two boxes for your web services server across all of these environments: how do you direct traffic for dev, test, and QA to box A while stage and prod to box B. It's the same problem all over again.
The solution (well, not really) I have found so far is this. In the .csproj that could house a app/web.config file, you can find the following lines for each solution configuration:
<Config
Name = "Release"
AllowUnsafeBlocks = "false"
BaseAddress = "285212672"
CheckForOverflowUnderflow = "false"
ConfigurationOverrideFile = "app.release.config"
DefineConstants = "TRACE"
DocumentationFile = ""
DebugSymbols = "false"
FileAlignment = "4096"
IncrementalBuild = "false"
NoStdLib = "false"
NoWarn = ""
Optimize = "true"
OutputPath = "bin\Release\"
RegisterForComInterop = "false"
RemoveIntegerChecks = "false"
TreatWarningsAsErrors = "false"
WarningLevel = "4"
...
if you create a new .config file for each deployment environment that you have, then create a new solution configuration for each deployment envionment (eg. Debug, Release, QA, Stage, Production), then change that bolded line for each deployment into the appropriate .config file, then create a Setup project in your solution and build it with the solution configuration you want for your deployment, the output .msi will include *a* app.config file, with it being replaced with the file you specified in the above bolded text. So in the example above, app.release.config will be used and renamed to app.config when the output .msi is deployed onto the environment.
This is so far the cleanest solution for me. But it does tie you into using Setup project for your deployment configuration. And from what I have heard, .msi has not been a good deployment strategy for complex application deployment (yes, the fact that there is a relational database built into a .msi does not make it suit for enterprise level if it is complex to use).
Still looking into alternatives... on a side note, the fact that Microsoft is switching from "no-touch deployment" into "one-click deployment" means to me that the marketeers at MS is going one step to far in their marketing effort =P